Fix wide select expansion and substitution (#6341) (#6345)

This commit is contained in:
Geza Lore 2025-08-30 15:34:39 +01:00 committed by GitHub
parent 14ec6258d9
commit c33f2b42aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 204 additions and 80 deletions

View File

@ -1863,6 +1863,7 @@ class AstVar final : public AstNode {
bool m_isHideProtected : 1; // Verilog protected
bool m_noReset : 1; // Do not do automated reset/randomization
bool m_noSubst : 1; // Do not substitute out references
bool m_substConstOnly : 1; // Only substitute if constant
bool m_overridenParam : 1; // Overridden parameter by #(...) or defparam
bool m_trace : 1; // Trace this variable
bool m_isLatched : 1; // Not assigned in all control paths of combo always
@ -1912,6 +1913,7 @@ class AstVar final : public AstNode {
m_isHideProtected = false;
m_noReset = false;
m_noSubst = false;
m_substConstOnly = false;
m_overridenParam = false;
m_trace = false;
m_isLatched = false;
@ -2068,6 +2070,8 @@ public:
bool noReset() const { return m_noReset; }
void noSubst(bool flag) { m_noSubst = flag; }
bool noSubst() const { return m_noSubst; }
void substConstOnly(bool flag) { m_substConstOnly = flag; }
bool substConstOnly() const { return m_substConstOnly; }
void overriddenParam(bool flag) { m_overridenParam = flag; }
bool overriddenParam() const { return m_overridenParam; }
void trace(bool flag) { m_trace = flag; }

View File

@ -77,10 +77,14 @@ class ExpandVisitor final : public VNVisitor {
AstNode* m_stmtp = nullptr; // Current statement
// STATE - across all visitors
AstCFunc* m_funcp = nullptr; // Current function
VDouble0 m_statWides; // Statistic tracking
VDouble0 m_statWideWords; // Statistic tracking
VDouble0 m_statWideLimited; // Statistic tracking
// STATE - for current function
size_t m_nTmps = 0; // Sequence numbers for temopraries
// METHODS
// Use state that ExpandOkVisitor calculated
bool isImpure(AstNode* nodep) {
@ -147,6 +151,19 @@ class ExpandVisitor final : public VNVisitor {
addWordAssign(placep, word, placep->lhsp(), rhsp);
}
AstVar* addLocalTmp(AstNode* placep, const char* namep, AstNodeExpr* valuep) {
const std::string name = "__V"s + namep + "_"s + std::to_string(m_nTmps);
FileLine* const flp = placep->fileline();
AstVar* const tmpp = new AstVar{flp, VVarType::STMTTEMP, name, valuep->dtypep()};
tmpp->funcLocal(true);
tmpp->isInternal(true);
tmpp->noReset(true);
tmpp->substConstOnly(true);
m_funcp->addInitsp(tmpp);
insertBefore(placep, new AstAssign{flp, new AstVarRef{flp, tmpp, VAccess::WRITE}, valuep});
return tmpp;
}
static void fixCloneLvalue(AstNode* nodep) {
// In AstSel transforms, we call clone() on VarRefs that were lvalues,
// but are now being used on the RHS of the assignment
@ -213,31 +230,54 @@ class ExpandVisitor final : public VNVisitor {
return newp;
}
static AstNodeExpr* newWordSel(FileLine* fl, AstNodeExpr* fromp, AstNodeExpr* lsbp,
// Return expression indexing the word that contains 'lsbp' + the given word offset
static AstNodeExpr* newWordIndex(AstNodeExpr* lsbp, uint32_t wordOffset = 0) {
// This is indexing a WordSel, so a 32 bit constants are fine
FileLine* const flp = lsbp->fileline();
if (AstConst* constp = VN_CAST(lsbp, Const)) {
return new AstConst{flp, wordOffset + VL_BITWORD_E(constp->toUInt())};
}
if (lsbp->backp()) lsbp = lsbp->cloneTreePure(false);
AstNodeExpr* const wwl2p = new AstConst{flp, VL_EDATASIZE_LOG2}; // Word width log 2
AstNodeExpr* indexp = new AstShiftR{flp, lsbp, wwl2p, VL_EDATASIZE};
if (wordOffset) indexp = new AstAdd{flp, new AstConst{flp, wordOffset}, indexp};
return indexp;
}
// Return word of fromp that contains word indexp + the given word offset.
static AstNodeExpr* newWordSelWord(FileLine* flp, AstNodeExpr* fromp, AstNodeExpr* indexp,
uint32_t wordOffset = 0) {
// Return equation to get the VL_BITWORD of a constant or non-constant
UASSERT_OBJ(fromp->isWide(), fromp, "Only need AstWordSel on wide from's");
if (wordOffset >= static_cast<uint32_t>(fromp->widthWords())) {
if (AstConst* const constp = VN_CAST(indexp, Const)) {
indexp = nullptr;
wordOffset += constp->toUInt();
}
// e.g. "logic [95:0] var[0]; logic [0] sel; out = var[sel];"
// Squash before C++ to avoid getting a C++ compiler warning
// (even though code would be unreachable as presumably a
// AstCond is protecting above this node.
return new AstConst{fl, AstConst::SizedEData{}, 0};
} else {
AstNodeExpr* wordp;
FileLine* const lfl = lsbp->fileline();
if (VN_IS(lsbp, Const)) {
wordp = new AstConst{lfl, wordOffset + VL_BITWORD_E(VN_AS(lsbp, Const)->toUInt())};
} else {
wordp = new AstShiftR{lfl, lsbp->cloneTreePure(true),
new AstConst{lfl, VL_EDATASIZE_LOG2}, VL_EDATASIZE};
if (wordOffset
!= 0) { // This is indexing a arraysel, so a 32 bit constant is fine
wordp = new AstAdd{lfl, new AstConst{lfl, wordOffset}, wordp};
if (wordOffset >= static_cast<uint32_t>(fromp->widthWords())) {
return new AstConst{flp, AstConst::SizedEData{}, 0};
}
if (fromp->backp()) fromp = fromp->cloneTreePure(false);
// If indexp was constant, just use it
if (!indexp) return new AstWordSel{flp, fromp, new AstConst{flp, wordOffset}};
// Otherwise compute at runtime
if (indexp->backp()) indexp = indexp->cloneTreePure(false);
if (wordOffset) indexp = new AstAdd{flp, new AstConst{flp, wordOffset}, indexp};
return new AstWordSel{flp, fromp, indexp};
}
return new AstWordSel{fl, fromp, wordp};
}
// Return word of fromp that contains bit lsbp + the given word offset.
static AstNodeExpr* newWordSelBit(FileLine* flp, AstNodeExpr* fromp, AstNodeExpr* lsbp,
uint32_t wordOffset = 0) {
return newWordSelWord(flp, fromp, newWordIndex(lsbp, wordOffset));
}
static AstNodeExpr* newSelBitBit(AstNodeExpr* lsbp) {
@ -346,6 +386,10 @@ class ExpandVisitor final : public VNVisitor {
// VISITORS
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_funcp);
VL_RESTORER(m_nTmps);
m_funcp = nodep;
m_nTmps = 0;
iterateChildren(nodep);
// Constant fold here, as Ast size can likely be reduced
@ -414,8 +458,7 @@ class ExpandVisitor final : public VNVisitor {
FileLine* const nfl = nodep->fileline();
FileLine* const lfl = nodep->lsbp()->fileline();
FileLine* const ffl = nodep->fromp()->fileline();
AstNodeExpr* lowwordp
= newWordSel(ffl, nodep->fromp()->cloneTreePure(true), nodep->lsbp());
AstNodeExpr* lowwordp = newWordSelBit(ffl, nodep->fromp(), nodep->lsbp());
if (nodep->isQuad() && !lowwordp->isQuad()) {
lowwordp = new AstCCast{nfl, lowwordp, nodep};
}
@ -428,10 +471,8 @@ class ExpandVisitor final : public VNVisitor {
= std::min<uint32_t>(nodep->widthConst(), VL_EDATASIZE) - 1;
AstNodeExpr* const midMsbp = new AstAdd{lfl, new AstConst{lfl, midMsbOffset},
nodep->lsbp()->cloneTreePure(true)};
AstNodeExpr* midwordp = // SEL(from,[midwordnum])
newWordSel(ffl, nodep->fromp()->cloneTreePure(true), midMsbp, 0);
// newWordSel clones the index, so delete it
VL_DO_DANGLING(midMsbp->deleteTree(), midMsbp);
AstNodeExpr* midwordp = newWordSelBit(ffl, nodep->fromp(), midMsbp, 0);
if (!midMsbp->backp()) VL_DO_DANGLING(midMsbp->deleteTree(), midMsbp);
if (nodep->isQuad() && !midwordp->isQuad()) {
midwordp = new AstCCast{nfl, midwordp, nodep};
}
@ -455,10 +496,8 @@ class ExpandVisitor final : public VNVisitor {
const uint32_t hiMsbOffset = nodep->widthConst() - 1;
AstNodeExpr* const hiMsbp = new AstAdd{lfl, new AstConst{lfl, hiMsbOffset},
nodep->lsbp()->cloneTreePure(true)};
AstNodeExpr* hiwordp = // SEL(from,[hiwordnum])
newWordSel(ffl, nodep->fromp()->cloneTreePure(true), hiMsbp);
// newWordSel clones the index, so delete it
VL_DO_DANGLING(hiMsbp->deleteTree(), hiMsbp);
AstNodeExpr* hiwordp = newWordSelBit(ffl, nodep->fromp(), hiMsbp);
if (!hiMsbp->backp()) VL_DO_DANGLING(hiMsbp->deleteTree(), hiMsbp);
if (nodep->isQuad() && !hiwordp->isQuad()) {
hiwordp = new AstCCast{nfl, hiwordp, nodep};
}
@ -497,41 +536,110 @@ class ExpandVisitor final : public VNVisitor {
bool expandWide(AstNodeAssign* nodep, AstSel* rhsp) {
UASSERT_OBJ(nodep->widthMin() == rhsp->widthConst(), nodep, "Width mismatch");
if (!doExpandWide(nodep)) return false;
if (VN_IS(rhsp->lsbp(), Const) && VL_BITBIT_E(rhsp->lsbConst()) == 0) {
const int lsb = rhsp->lsbConst();
// Simplify the index, in case it becomes a constant
V3Const::constifyEditCpp(rhsp->lsbp());
// If it's a constant select and aligned, we can just copy the words
if (AstConst* const lsbConstp = VN_CAST(rhsp->lsbp(), Const)) {
const uint32_t lsb = lsbConstp->toUInt();
if (VL_BITBIT_E(lsb) == 0) {
UINFO(8, " Wordize ASSIGN(SEL,align) " << nodep);
const uint32_t word = VL_BITWORD_E(lsb);
for (int w = 0; w < nodep->widthWords(); ++w) {
addWordAssign(nodep, w, newAstWordSelClone(rhsp->fromp(), w + VL_BITWORD_E(lsb)));
addWordAssign(nodep, w, newAstWordSelClone(rhsp->fromp(), w + word));
}
return true;
} else {
}
}
UINFO(8, " Wordize ASSIGN(EXTRACT,misalign) " << nodep);
FileLine* const nfl = nodep->fileline();
FileLine* const rfl = rhsp->fileline();
FileLine* const ffl = rhsp->fromp()->fileline();
FileLine* const lfl = rhsp->lsbp()->fileline();
for (int w = 0; w < nodep->widthWords(); ++w) {
// Grab lowest bits
AstNodeExpr* const lowwordp
= newWordSel(rfl, rhsp->fromp()->cloneTreePure(true), rhsp->lsbp(), w);
AstNodeExpr* const lowp
= new AstShiftR{rfl, lowwordp, newSelBitBit(rhsp->lsbp()), VL_EDATASIZE};
// Upper bits
const V3Number zero{nodep, VL_EDATASIZE, 0};
AstNodeExpr* const midwordp = // SEL(from,[1+wordnum])
newWordSel(ffl, rhsp->fromp()->cloneTreePure(true), rhsp->lsbp(), w + 1);
AstNodeExpr* const midshiftp
= new AstSub{lfl, new AstConst{lfl, VL_EDATASIZE}, newSelBitBit(rhsp->lsbp())};
AstNodeExpr* const midmayp = new AstShiftL{rfl, midwordp, midshiftp, VL_EDATASIZE};
AstNodeExpr* const midp = new AstCond{
rfl, new AstEq{rfl, new AstConst{rfl, 0}, newSelBitBit(rhsp->lsbp())},
new AstConst{rfl, zero}, midmayp};
AstNodeExpr* const newp = new AstOr{nfl, midp, lowp};
addWordAssign(nodep, w, newp);
FileLine* const flp = rhsp->fileline();
// Use fresh set of temporaries
++m_nTmps;
// Compute word index of LSB, store to temporary if not constant
AstNodeExpr* wordIdxp = newWordIndex(rhsp->lsbp()->cloneTreePure(false));
wordIdxp = V3Const::constifyEditCpp(wordIdxp);
if (!VN_IS(wordIdxp, Const)) {
AstVar* const tmpp = addLocalTmp(nodep, "ExpandSel_WordIdx", wordIdxp);
wordIdxp = new AstVarRef{flp, tmpp, VAccess::READ};
}
// Compute shift amounts and mask, store to temporaries if not constants
AstNodeExpr* loShftp = nullptr;
AstNodeExpr* hiShftp = nullptr;
AstNodeExpr* hiMaskp = nullptr;
if (AstConst* const lsbConstp = VN_CAST(rhsp->lsbp(), Const)) {
const uint32_t bitOffset = VL_BITBIT_E(lsbConstp->toUInt());
// Must be unaligned, otherwise we would have handled it above
UASSERT_OBJ(bitOffset, nodep, "Missed aligned wide select");
loShftp = new AstConst{flp, bitOffset};
hiShftp = new AstConst{flp, VL_EDATASIZE - bitOffset};
hiMaskp = nullptr;
} else {
// Compute Low word shift amount: bottom bits of lsb index
AstNodeExpr* const lsbp = rhsp->lsbp()->cloneTreePure(false);
loShftp = new AstAnd{flp, new AstConst{flp, VL_SIZEBITS_E}, lsbp};
AstVar* const loTmpp = addLocalTmp(nodep, "ExpandSel_LoShift", loShftp);
loShftp = new AstVarRef{flp, loTmpp, VAccess::READ};
// Compute if aligned: Low word shift amount is 0
AstNodeExpr* const zerop = new AstConst{flp, 0};
AstNodeExpr* alignedp = new AstEq{flp, zerop, loShftp->cloneTreePure(false)};
AstVar* const alignedTmpp = addLocalTmp(nodep, "ExpandSel_Aligned", alignedp);
alignedp = new AstVarRef{flp, alignedTmpp, VAccess::READ};
// Computed High word shift amount: 0 if aligned else VL_EDATASIZE - Low word shift
AstNodeExpr* const edsp = new AstConst{flp, VL_EDATASIZE};
AstNodeExpr* const subp = new AstSub{flp, edsp, loShftp->cloneTreePure(false)};
hiShftp = new AstCond{flp, alignedp, new AstConst{flp, 0}, subp};
AstVar* const hiTmpp = addLocalTmp(nodep, "ExpandSel_HiShift", hiShftp);
hiShftp = new AstVarRef{flp, hiTmpp, VAccess::READ};
// Compute the High word mask: 0 if aligned, ones otherwise
hiMaskp = new AstCond{flp, alignedp->cloneTreePure(false),
new AstConst{flp, AstConst::SizedEData{}, 0},
new AstConst{flp, AstConst::SizedEData{}, -1ULL}};
AstVar* const maskTmpp = addLocalTmp(nodep, "ExpandSel_HiMask", hiMaskp);
hiMaskp = new AstVarRef{flp, maskTmpp, VAccess::READ};
}
// Create each word of the selected result
AstNodeExpr* const fromp = rhsp->fromp();
const int selWords = nodep->widthWords();
for (int w = 0; w < selWords; ++w) {
// Grab bits from word 'VL_BITWORD_E(lsb) + w'
AstNodeExpr* const loWordp = newWordSelWord(fromp->fileline(), fromp, wordIdxp, w);
AstNodeExpr* const loShftClonep = loShftp->cloneTreePure(false);
AstNodeExpr* const lop = new AstShiftR{flp, loWordp, loShftClonep, VL_EDATASIZE};
// Grab bits from word 'VL_BITWORD_E(lsb) + w + 1'
AstNodeExpr* hiWordp = newWordSelWord(fromp->fileline(), fromp, wordIdxp, w + 1);
// For the last word of the result, avoid an OOB access when the Sel is not OOB
if (w == selWords - 1) {
const uint32_t max = fromp->widthWords() - w - 1;
AstNodeExpr* const maxp = new AstConst{flp, max};
AstNodeExpr* const condp = new AstGte{flp, wordIdxp->cloneTreePure(false), maxp};
AstNodeExpr* const zerop = new AstConst{flp, AstConst::SizedEData{}, 0};
hiWordp = new AstCond{flp, condp, zerop, hiWordp};
}
AstNodeExpr* const hiShftClonep = hiShftp->cloneTreePure(false);
AstNodeExpr* const hiPartp = new AstShiftL{flp, hiWordp, hiShftClonep, VL_EDATASIZE};
AstNodeExpr* const hip
= hiMaskp ? new AstAnd{flp, hiPartp, hiMaskp->cloneTreePure(false)} : hiPartp;
// Combine them
addWordAssign(nodep, w, new AstOr{nodep->fileline(), hip, lop});
}
// Delete parts not captured during construction
if (!wordIdxp->backp()) VL_DO_DANGLING(wordIdxp->deleteTree(), wordIdxp);
if (!loShftp->backp()) VL_DO_DANGLING(loShftp->deleteTree(), loShftp);
if (!hiShftp->backp()) VL_DO_DANGLING(hiShftp->deleteTree(), hiShftp);
if (hiMaskp && !hiMaskp->backp()) VL_DO_DANGLING(hiMaskp->deleteTree(), hiMaskp);
return true;
}
}
bool expandLhs(AstNodeAssign* nodep, AstSel* lhsp) {
// Possibilities
@ -614,7 +722,7 @@ class ExpandVisitor final : public VNVisitor {
UINFO(8, " ASSIGNSEL(varlsb,wide,1bit) " << nodep);
AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack();
AstNodeExpr* const destp = lhsp->fromp()->unlinkFrBack();
AstNodeExpr* oldvalp = newWordSel(lfl, destp->cloneTreePure(true), lhsp->lsbp());
AstNodeExpr* oldvalp = newWordSelBit(lfl, destp, lhsp->lsbp());
fixCloneLvalue(oldvalp);
if (!ones) {
oldvalp = new AstAnd{
@ -631,7 +739,7 @@ class ExpandVisitor final : public VNVisitor {
AstNodeExpr* const shiftp = new AstAnd{nfl, lhsp->lsbp()->cloneTreePure(true),
new AstConst{nfl, VL_EDATASIZE - 1}};
AstNode* const newp = new AstAssign{
nfl, newWordSel(nfl, destp, lhsp->lsbp()),
nfl, newWordSelBit(nfl, destp, lhsp->lsbp()),
new AstOr{lfl, oldvalp, new AstShiftL{lfl, rhsp, shiftp, VL_EDATASIZE}}};
insertBefore(nodep, newp);
return true;

View File

@ -118,15 +118,21 @@ public:
}
// ACCESSORS
AstNodeExpr* substWhole(AstNode* errp) {
if (!m_varp->isWide() && !m_whole.m_complex && m_whole.m_assignp && !m_wordAssign) {
if (m_varp->isWide()) return nullptr;
if (m_whole.m_complex) return nullptr;
if (!m_whole.m_assignp) return nullptr;
if (m_wordAssign) return nullptr;
const AstNodeAssign* const assp = m_whole.m_assignp;
UASSERT_OBJ(assp, errp, "Reading whole that was never assigned");
AstNodeExpr* const rhsp = assp->rhsp();
// AstCvtPackedToArray can't be anywhere else than on the RHS of assignment
if (VN_IS(assp->rhsp(), CvtPackedToArray)) return nullptr;
return assp->rhsp();
} else {
return nullptr;
}
if (VN_IS(rhsp, CvtPackedToArray)) return nullptr;
// Check if only substitute if constant
if (m_varp->substConstOnly() && !VN_IS(rhsp, Const)) return nullptr;
// Substitute it
return rhsp;
}
// Return what to substitute given word number for
AstNodeExpr* substWord(AstNode* errp, int word) {
@ -227,7 +233,7 @@ class SubstVisitor final : public VNVisitor {
int m_ops = 0; // Number of operators on assign rhs
int m_assignStep = 0; // Assignment number to determine var lifetime
const AstCFunc* m_funcp = nullptr; // Current function we are under
VDouble0 m_statSubsts; // Statistic tracking
size_t m_nSubst = 0; // Number of substitutions performed
enum {
SUBST_MAX_OPS_SUBST = 30, // Maximum number of ops to substitute in
@ -251,7 +257,10 @@ class SubstVisitor final : public VNVisitor {
VL_RESTORER(m_ops);
m_ops = 0;
m_assignStep++;
const size_t nSubstBefore = m_nSubst;
iterateAndNextNull(nodep->rhsp());
if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(nodep->rhsp());
if (VN_IS(nodep->rhsp(), Const)) m_ops = 0;
bool hit = false;
if (AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) {
if (isSubstVar(varrefp->varp())) {
@ -292,11 +301,14 @@ class SubstVisitor final : public VNVisitor {
UINFOTREE(6, newp, "", "w_new");
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
++m_statSubsts;
++m_nSubst;
}
void visit(AstWordSel* nodep) override {
if (!m_funcp) return;
const size_t nSubstBefore = m_nSubst;
iterate(nodep->bitp());
// Simplify in case it was substituted and became constant
if (nSubstBefore != m_nSubst) V3Const::constifyEditCpp(nodep->bitp());
AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef);
const AstConst* const constp = VN_CAST(nodep->bitp(), Const);
if (varrefp && isSubstVar(varrefp->varp()) && varrefp->access().isReadOnly() && constp) {
@ -380,7 +392,7 @@ public:
// CONSTRUCTORS
explicit SubstVisitor(AstNode* nodep) { iterate(nodep); }
~SubstVisitor() override {
V3Stats::addStat("Optimizations, Substituted temps", m_statSubsts);
V3Stats::addStat("Optimizations, Substituted temps", m_nSubst);
UASSERT(m_entries.empty(), "References outside functions");
}
};