Fix deferring parameters for class::localparam access (#7746) (#7748)

This commit is contained in:
Edmund Lam
2026-09-15 21:17:26 -04:00
committed by GitHub
parent c9e3f59f26
commit 529d98918a
45 changed files with 1940 additions and 91 deletions
+3 -1
View File
@@ -2374,7 +2374,9 @@ bool AstNodeDType::isLiteralType() const VL_MT_STABLE {
if (const auto* const dtypep = VN_CAST(skipRefp(), BasicDType)) {
return dtypep->keyword().isLiteralType();
} else if (const auto* const dtypep = VN_CAST(skipRefp(), UnpackArrayDType)) {
return dtypep->basicp()->isLiteralType();
// basicp() is null for e.g. an array of unpacked structs, which is not literal
const AstBasicDType* const basicp = dtypep->basicp();
return basicp && basicp->isLiteralType();
} else if (const auto* const dtypep = VN_CAST(skipRefp(), StructDType)) {
// Currently all structs are packed, later this can be expanded to
// 'forall members _.isLiteralType()'
+11 -5
View File
@@ -421,13 +421,19 @@ class EmitCSyms final : EmitCBaseVisitorConst {
stmt += protect("__Vscopep_" + svd.m_scopeName);
stmt += needsEntSize ? "->varInsertSized(\"" : "->varInsert(\"";
stmt += V3OutFormatter::quoteNameControls(prettyName) + '"';
stmt += ", &(";
stmt += VIdProtect::protectIf(scopep->nameDotless(), scopep->protect());
stmt += ".";
stmt += cName;
stmt += "), false, ";
const std::string varName
= VIdProtect::protectIf(scopep->nameDotless(), scopep->protect()) + "." + cName;
// A parameter is emitted 'static const', so its members are const too and
// need the same cast the whole-parameter insert uses.
if (svd.m_varp->isParam()) {
stmt += ", const_cast<void*>(static_cast<const void*>(&(";
stmt += varName;
stmt += "))), true, ";
} else {
stmt += ", &(";
stmt += varName;
stmt += "), false, ";
}
const std::string entSize
= needsEntSize
? "sizeof(" + varName + ") / " + std::to_string(getUnpackedElements(dtypep))
+71 -12
View File
@@ -1030,9 +1030,11 @@ public:
static bool checkIfClassOrPackage(const VSymEnt* const symp) {
if (VN_IS(symp->nodep(), Class) || VN_IS(symp->nodep(), Package)) return true;
// Helper: check if a RefDType might resolve to a class later
const auto checkUnresolvedRef = [](const AstRefDType* refp) -> bool {
return refp && !refp->typeofp() && !refp->classOrPackageOpp();
// Helper: check if a RefDType might resolve to a class later.
const auto isPotentialClassRef = [](const AstRefDType* refp) -> bool {
return refp && !refp->typeofp()
&& (!refp->classOrPackageOpp()
|| VN_IS(refp->classOrPackageOpp(), ClassOrPackageRef));
};
// Helper: allow types that can represent a class/package handle or an unresolved ref.
@@ -1046,7 +1048,7 @@ public:
const AstNodeDType* dtypep = typedefp->subDTypep();
if (!dtypep) dtypep = typedefp->childDTypep();
if (VN_IS(dtypep, ClassRefDType)) return true;
if (checkUnresolvedRef(VN_CAST(dtypep, RefDType))) return true;
if (isPotentialClassRef(VN_CAST(dtypep, RefDType))) return true;
} else if (const AstParamTypeDType* const paramTypep
= VN_CAST(symp->nodep(), ParamTypeDType)) {
// Before V3Param the declared default is in childDTypep (possibly
@@ -1058,8 +1060,9 @@ public:
childp = reqp->lhsp();
}
const AstNode* const checkp = childp ? childp : paramTypep->skipRefp();
if (isValidTypeNode(checkp)) return true;
if (checkUnresolvedRef(VN_CAST(checkp, RefDType))) return true;
if (isValidTypeNode(checkp) || isPotentialClassRef(VN_CAST(checkp, RefDType))) {
return true;
}
}
return false;
}
@@ -4850,7 +4853,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
VL_RESTORER_COPY(m_ds);
VL_RESTORER(m_pinSymp);
if (!nodep->classOrPackageSkipp() && nodep->name() != "local::") {
if (!nodep->classOrPackageSkipp() && !nodep->classOrPackageNodep()
&& nodep->name() != "local::") {
const bool deferIfUnresolved = m_statep->forPrimary() && m_insideClassExtParam;
m_statep->resolveClassOrPackage(m_ds.m_dotSymp, nodep, m_ds.m_dotPos != DP_PACKAGE,
false, ":: reference", deferIfUnresolved);
@@ -5998,6 +6002,54 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
m_ds.m_dotSymp = VL_RESTORER_PREV(m_curSymp);
}
// Collect the ClassOrPackageRefs in a chained scope operand in source order.
// The grammar builds a scope operand only from packageClassScopeItem, which
// always yields a ClassOrPackageRef, nested in Dots, so nothing else appears.
static void collectScopeRefs(AstNode* nodep, std::vector<AstClassOrPackageRef*>& refps) {
if (AstClassOrPackageRef* const refp = VN_CAST(nodep, ClassOrPackageRef)) {
refps.push_back(refp);
return;
}
AstDot* const dotp = VN_CAST(nodep, Dot);
UASSERT_OBJ(dotp, nodep, "Non-scope node in package/class scope operand");
collectScopeRefs(dotp->lhsp(), refps);
collectScopeRefs(dotp->rhsp(), refps);
}
// Resolve a chained class-scope operand (`pkg::outer::inner` in
// `pkg::outer::inner::t`) and reduce it to its innermost ClassOrPackageRef.
// Returns false, having reported, if a segment could not be resolved.
bool reduceScopeDot(AstRefDType* nodep, AstDot* scopeDotp) {
std::vector<AstClassOrPackageRef*> refps;
collectScopeRefs(scopeDotp, refps);
VSymEnt* scopeSymp = m_ds.m_dotSymp;
for (size_t i = 0; i < refps.size(); ++i) {
AstClassOrPackageRef* const refp = refps[i];
if (!refp->classOrPackageSkipp() && !refp->classOrPackageNodep()
&& !m_statep->resolveClassOrPackage(scopeSymp, refp, i == 0, false,
"class/package reference")) {
return false; // Error already reported
}
if (i + 1 == refps.size()) break;
// A middle segment that resolved to a node with no reachable module
// (e.g. a typedef alias of a not-yet-specialized parameterized class)
// gives nothing to look the next segment up in.
AstNodeModule* const modp = refp->classOrPackageSkipp();
if (!modp) {
refp->v3warn(E_UNSUPPORTED, "Unsupported: Multiple '::' package/class reference");
return false;
}
scopeSymp = m_statep->getNodeSym(modp);
}
AstClassOrPackageRef* const innerp = refps.back();
innerp->unlinkFrBack();
VL_DO_DANGLING(pushDeletep(scopeDotp->unlinkFrBack()), scopeDotp);
nodep->classOrPackageOpp(innerp);
return true;
}
void visit(AstRefDType* nodep) override {
if (auto* const typeOfp = nodep->typeofp()) {
@@ -6064,7 +6116,16 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
LINKDOT_VISIT_START();
UINFO(5, indent() << "visit " << nodep);
if (AstNode* const cpackagep = nodep->classOrPackageOpp()) {
if (nodep->classOrPackageOpp()) {
// Resolve a chained scope (`pkg::cls::t`, `pkg::cls#(P)::t`) from outside in.
// On failure reduceScopeDot has reported, so stop before using the operand.
if (AstDot* const scopeDotp = VN_CAST(nodep->classOrPackageOpp(), Dot)) {
if (!reduceScopeDot(nodep, scopeDotp)) return;
}
// Re-read: reduceScopeDot may have replaced the operand
AstNode* const cpackagep = nodep->classOrPackageOpp();
UASSERT_OBJ(!VN_IS(cpackagep, Dot), cpackagep,
"Package/class scope operand should be reduced by now");
if (AstClassOrPackageRef* const cpackagerefp = VN_CAST(cpackagep, ClassOrPackageRef)) {
iterate(cpackagerefp);
const AstClass* const clsp = VN_CAST(cpackagerefp->classOrPackageNodep(), Class);
@@ -6083,7 +6144,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
const bool doDefaultTypedef = !(m_resolvingTypedef && m_statep->forPrimary());
if (!cpackagerefp->classOrPackageSkipp(doDefaultTypedef)) {
if (!cpackagerefp->classOrPackageSkipp(doDefaultTypedef)
&& !cpackagerefp->classOrPackageNodep()) {
VSymEnt* const foundp = m_statep->resolveClassOrPackage(
m_ds.m_dotSymp, cpackagerefp, true, false, "class/package reference");
if (!foundp) return;
@@ -6105,9 +6167,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
<< "'\n"
<< cpackagerefp->warnMore() + "... Suggest '.' instead of '::'");
}
} else {
cpackagep->v3warn(E_UNSUPPORTED,
"Unsupported: Multiple '::' package/class reference");
}
VL_DO_DANGLING(pushDeletep(cpackagep->unlinkFrBack()), cpackagep);
}
+258 -60
View File
@@ -341,6 +341,10 @@ class ParamProcessor final {
return st;
}
static bool isAggregateParamValue(const AstNode* nodep) {
return VN_IS(nodep, InitArray) || VN_IS(nodep, ConsPackUOrStruct);
}
static string paramValueString(const AstNode* nodep) {
if (const AstRefDType* const refp = VN_CAST(nodep, RefDType)) {
nodep = refp->skipRefToNonRefp();
@@ -387,6 +391,14 @@ class ParamProcessor final {
key += ",";
}
key += "}";
} else if (const AstConsPackUOrStruct* const structp = VN_CAST(nodep, ConsPackUOrStruct)) {
key += "{";
for (const AstConsPackMember* memberp = structp->membersp(); memberp;
memberp = VN_AS(memberp->nextp(), ConsPackMember)) {
key += paramValueString(memberp->rhsp());
key += ",";
}
key += "}";
} else if (const AstClassRefDType* const classRefp = VN_CAST(nodep, ClassRefDType)) {
// For parameterized class types, use the original class name (without specialization
// suffix) plus the actual type parameter values. This ensures equivalent class types
@@ -1196,15 +1208,105 @@ class ParamProcessor final {
}
}
// Fold a param/lparam member's value to a Const in place. Resolves any
// class::member Dots in the value first (including those buried behind
// VarRefs to sibling lparams that are not yet const, e.g. `one = base`
// where `base = inner_a::v`), resolving siblings deepest-first so the
// whole chain constifies. `inProgress` guards against reference cycles.
void constifyMemberValue(AstVar* varp, std::set<AstVar*>& inProgress) {
if (!varp->valuep() || VN_IS(varp->valuep(), Const)) return;
if (!inProgress.insert(varp).second) {
// Re-entered while still resolving varp: its value transitively
// references itself. Report and replace the value with a Const, as
// otherwise the unresolved Dot reaches V3Width as an untyped node.
varp->v3error("Variable's initial value is circular: " << varp->prettyNameQ());
varp->valuep()->unlinkFrBack()->deleteTree();
varp->valuep(new AstConst{varp->fileline(), AstConst::Signed32{}, 0});
return;
}
// First resolve sibling lparams this value references, so their Dots
// are folded before we inline them here.
std::set<AstVar*> siblings;
varp->valuep()->foreach([&](const AstVarRef* refp) {
AstVar* const refVarp = refp->varp();
if (refVarp && refVarp->isParam() && refVarp->valuep()
&& !VN_IS(refVarp->valuep(), Const)) {
siblings.insert(refVarp);
}
});
for (AstVar* const sibp : siblings) constifyMemberValue(sibp, inProgress);
// Resolve class-scoped references and any member selections they expose.
resolveDeferredDotsReachableFrom(varp->valuep(), m_modp);
V3Const::constifyParamsEdit(varp);
inProgress.erase(varp);
}
// Return the member name on a deferred Dot RHS, including a selected member.
static AstParseRef* memberParseRef(AstNode* nodep) {
if (AstParseRef* const refp = VN_CAST(nodep, ParseRef)) return refp;
AstNodePreSel* const selp = VN_CAST(nodep, NodePreSel);
return selp ? VN_CAST(selp->fromp(), ParseRef) : nullptr;
}
// Replace a deferred Dot with valuep, preserving any select on the member name.
static void replaceMemberDot(AstDot* dotp, AstParseRef* memberRefp, AstNodeExpr* valuep) {
if (memberRefp == dotp->rhsp()) {
dotp->replaceWith(valuep);
} else {
AstNodePreSel* const selp = VN_AS(dotp->rhsp(), NodePreSel);
if (AstAttrOf* const attrp = selp->attrp()) {
if (AstNode* const oldFromp = attrp->fromp()) {
oldFromp->replaceWith(valuep->cloneTree(false));
VL_DO_DANGLING(oldFromp->deleteTree(), oldFromp);
}
}
memberRefp->replaceWith(valuep);
VL_DO_DANGLING(memberRefp->deleteTree(), memberRefp);
dotp->replaceWith(dotp->rhsp()->unlinkFrBack());
}
VL_DO_DANGLING(dotp->deleteTree(), dotp);
}
// True for an expression produced while lowering a deferred struct-member chain.
static bool isDeferredMemberBase(const AstNodeExpr* nodep) {
if (VN_IS(nodep, MemberSel)) return true;
if (const AstNodePreSel* const selp = VN_CAST(nodep, NodePreSel)) {
const AstNodeDType* const fromDTypep = selp->fromp()->dtypep();
const AstNodeArrayDType* const arrayDTypep
= fromDTypep ? VN_CAST(fromDTypep->skipRefOrNullp(), NodeArrayDType) : nullptr;
if (arrayDTypep) {
const AstNodeDType* const elemDTypep = arrayDTypep->subDTypep();
return elemDTypep && VN_IS(elemDTypep->skipRefOrNullp(), NodeUOrStructDType);
}
return isDeferredMemberBase(selp->fromp());
}
const AstNodeDType* const dtypep = nodep->dtypep();
return dtypep && VN_IS(dtypep->skipRefOrNullp(), NodeUOrStructDType);
}
// Lower a Dot exposed after its inner class::member reference was substituted.
static void resolveMemberDot(AstDot* dotp) {
AstNodeExpr* const lhsp = VN_CAST(dotp->lhsp(), NodeExpr);
AstParseRef* const memberRefp = memberParseRef(dotp->rhsp());
if (!lhsp || !memberRefp || !isDeferredMemberBase(lhsp)) return;
AstMemberSel* const newp = new AstMemberSel{memberRefp->fileline(), lhsp->unlinkFrBack(),
VFlagChildDType{}, memberRefp->name()};
replaceMemberDot(dotp, memberRefp, newp);
}
// Helper to resolve DOT to RefDType for class type references.
// If the class is parameterized and not yet specialized, specialize it first.
// This handles cases like: iface #(param_class#(value)::typedef_name)
void resolveDotToTypedef(AstNode* exprp) {
AstDot* const dotp = VN_CAST(exprp, Dot);
if (!dotp) return;
UINFO(9, "Resolve deferred Dot: " << dotp);
AstClassOrPackageRef* const classRefp = VN_CAST(dotp->lhsp(), ClassOrPackageRef);
if (!classRefp) return;
AstParseRef* const parseRefp = VN_CAST(dotp->rhsp(), ParseRef);
if (!classRefp) {
resolveMemberDot(dotp);
return;
}
AstParseRef* const parseRefp = memberParseRef(dotp->rhsp());
if (!parseRefp) return;
const AstClass* lhsClassp = VN_CAST(classRefp->classOrPackageSkipp(), Class);
@@ -1248,37 +1350,37 @@ class ParamProcessor final {
if (!lhsClassp) return;
AstNode* const memberp = m_memberMap.findMember(lhsClassp, parseRefp->name());
UINFO(9, "Resolve deferred class member: " << parseRefp->name() << " -> " << memberp);
if (AstTypedef* const tdefp = VN_CAST(memberp, Typedef)) {
if (parseRefp != dotp->rhsp()) return;
AstRefDType* const refp = new AstRefDType{dotp->fileline(), tdefp->name()};
refp->typedefp(tdefp);
dotp->replaceWith(refp);
VL_DO_DANGLING(dotp->deleteTree(), dotp);
} else if (AstVar* const varp = VN_CAST(memberp, Var)) {
// Param/lparam member: substitute its constant value so the caller's constify can
// succeed.
if (varp->isParam() && varp->valuep()) {
if (!VN_IS(varp->valuep(), Const)) V3Const::constifyParamsEdit(varp);
if (AstConst* const constp = VN_CAST(varp->valuep(), Const)) {
dotp->replaceWith(constp->cloneTree(false));
VL_DO_DANGLING(dotp->deleteTree(), dotp);
}
}
substituteParamMember(dotp, parseRefp, varp);
}
}
// Resolve an unresolved RefDType whose classOrPackageOp targets a parameterized
// class or a typedef alias of one. Specializes the class and links the typedef.
void resolveParamClassRefDType(AstNodeDType* dtypep) {
// Recurse into struct/union members for buried RefDTypes
if (AstNodeUOrStructDType* const sup = VN_CAST(dtypep, NodeUOrStructDType)) {
for (AstMemberDType* memp = sup->membersp(); memp;
memp = VN_AS(memp->nextp(), MemberDType)) {
resolveParamClassRefDType(memp->subDTypep());
}
return;
// Substitute a class param/lparam member's constant value for dotp. The
// reverse-order deferred-Dot walk lowers any outer fields to MemberSels.
void substituteParamMember(AstDot* const dotp, AstParseRef* const memberRefp,
AstVar* const varp) {
if (!varp->isParam() || !varp->valuep()) return;
if (!VN_IS(varp->valuep(), Const)) {
std::set<AstVar*> inProgress;
constifyMemberValue(varp, inProgress);
}
AstRefDType* const refp = dtypep ? VN_CAST(dtypep, RefDType) : nullptr;
if (!refp) return;
AstConst* const constp = VN_CAST(varp->valuep(), Const);
if (!constp) return;
AstConst* const newp = constp->cloneTree(false);
newp->dtypep(varp->subDTypep());
replaceMemberDot(dotp, memberRefp, newp);
}
// Resolve one class-scoped RefDType. The generic deferred walk finds these
// through every dtype wrapper and follows out-of-tree typedef references.
void resolveParamClassRefDType(AstRefDType* refp) {
if (refp->typedefp() || refp->refDTypep()) return;
AstClassOrPackageRef* const classRefp
@@ -1422,30 +1524,32 @@ class ParamProcessor final {
}
}
// Include the pin's value in the specialization name, so cells passing equal
// values share one module clone.
void nameByPinValue(AstPin* pinp, AstNodeModule* srcModp, AstVar* modvarp, string& longnamer,
bool& any_overridesr) {
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(pinp->exprp());
any_overridesr = true;
}
void cellPinCleanup(AstNode* nodep, AstPin* pinp, AstPin* paramsp, AstNodeModule* srcModp,
string& longnamer, bool& any_overridesr) {
if (!pinp->exprp()) return; // No-connect
if (AstVar* const modvarp = pinp->modVarp()) {
resolveParamClassRefDType(modvarp->subDTypep());
resolveDeferredDotsReachableFrom(modvarp->subDTypep(), m_modp);
if (!modvarp->isGParam()) {
pinp->v3fatalSrc("Attempted parameter setting of non-parameter: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
} else if (VN_IS(pinp->exprp(), InitArray) && arraySubDTypep(modvarp->subDTypep())) {
// Array assigned to array
AstNode* const exprp = pinp->exprp();
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp);
any_overridesr = true;
} else if (VN_IS(pinp->exprp(), InitArray)) {
// Array assigned to scalar parameter. Treat the InitArray as a constant
// integer array and include it in the module name. Constantify nested
// expressions before mangling the value number.
V3Const::constifyParamsEdit(pinp->exprp());
longnamer
+= "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(pinp->exprp());
any_overridesr = true;
nameByPinValue(pinp, srcModp, modvarp, longnamer, any_overridesr);
} else {
UINFO(9, "cellPinCleanup: before constify " << pinp << " " << modvarp);
V3Const::constifyParamsEdit(pinp->exprp());
if (isAggregateParamValue(pinp->exprp())) {
nameByPinValue(pinp, srcModp, modvarp, longnamer, any_overridesr);
return;
}
// Cast/CastSize default values are not yet folded by V3Width.
// Constify here so the comparison below sees a Const node.
// Other node kinds are handled in the branches above.
@@ -1606,15 +1710,10 @@ class ParamProcessor final {
if (normedNamep) VL_DO_DANGLING(normedNamep->deleteTree(), normedNamep);
}
} else if (AstParamTypeDType* const modvarp = pinp->modPTypep()) {
// Handle DOT with ParseRef RHS (e.g., p_class#(8)::p_type)
// by this point ClassOrPackageRef should be updated to point to the specialized class.
resolveDotToTypedef(pinp->exprp());
resolveParamClassRefDType(modvarp->subDTypep());
// Also resolve through the pin expression's typedef chain
if (const AstRefDType* const pinRefp = VN_CAST(pinp->exprp(), RefDType)) {
if (const AstTypedef* const tdefp = pinRefp->typedefp())
resolveParamClassRefDType(tdefp->subDTypep());
}
// Resolve the pin and declared type through arbitrary dtype wrappers
// and typedef chains before widthing either one.
resolveDeferredDotsReachableFrom(pinp->exprp(), m_modp);
resolveDeferredDotsReachableFrom(modvarp->subDTypep(), m_modp);
AstNodeDType* rawTypep = VN_CAST(pinp->exprp(), NodeDType);
// Guard against widthing a struct/union still owned by a
@@ -2155,6 +2254,67 @@ class ParamProcessor final {
return newClassp;
}
// Resolve deferred class-scoped references in post-order. Resolution may
// deparameterize a class and delete its parameter pins, so no pointer to a
// child may remain pending when its parent is resolved.
class DeferredResolverVisitor final : public VNVisitor {
ParamProcessor& m_processor;
std::set<const AstNode*> m_reachedDecls;
bool firstReach(const AstNode* const declp) { return m_reachedDecls.insert(declp).second; }
void iterateParamType(AstParamTypeDType* const paramTypep) {
if (firstReach(paramTypep)) iterate(paramTypep);
}
void iterateTypedef(AstTypedef* const tdefp) {
if (firstReach(tdefp)) iterate(tdefp->subDTypep());
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
void visit(AstClassOrPackageRef* nodep) override {
iterateChildren(nodep);
AstNode* const targetp = nodep->classOrPackageNodep();
if (AstTypedef* const tdefp = VN_CAST(targetp, Typedef)) {
iterateTypedef(tdefp);
} else if (AstParamTypeDType* const paramTypep = VN_CAST(targetp, ParamTypeDType)) {
iterateParamType(paramTypep);
}
}
void visit(AstDot* nodep) override {
iterateChildren(nodep);
m_processor.resolveDotToTypedef(nodep);
}
void visit(AstRefDType* nodep) override {
iterateChildren(nodep);
AstTypedef* const tdefp = nodep->typedefp();
if (tdefp) {
iterateTypedef(tdefp);
} else if (AstParamTypeDType* const paramTypep
= VN_CAST(nodep->refDTypep(), ParamTypeDType)) {
iterateParamType(paramTypep);
}
m_processor.resolveParamClassRefDType(nodep);
}
void visit(AstVarRef* nodep) override {
iterateChildren(nodep);
AstVar* const varp = nodep->varp();
if (firstReach(varp)) {
iterate(varp->subDTypep());
const auto& deferredVarps = v3Global.rootp()->deferredParamVarps();
if (varp->varType() == VVarType::LPARAM && deferredVarps.count(varp)) {
UASSERT_OBJ(varp->valuep(), varp, "VarRef should have non-null valuep");
iterate(varp->valuep());
}
}
}
public:
DeferredResolverVisitor(ParamProcessor& processor, AstNode* rootp)
: m_processor{processor} {
iterate(rootp);
}
};
public:
// After an interface cell inside parentModp has been deparameterized
// (rewired from template to clone), retarget REFDTYPEs that still
@@ -2181,6 +2341,16 @@ public:
});
}
// Resolve deferred class-scoped values and types reachable from rootp. In
// addition to AST children, follow typedefs, parameter types, deferred
// localparam values, and referenced-variable dtypes. This makes the walk
// independent of the particular dtype wrappers around a RefDType.
void resolveDeferredDotsReachableFrom(AstNode* rootp, const AstNodeModule* modp) {
VL_RESTORER(m_modp);
m_modp = modp;
DeferredResolverVisitor{*this, rootp};
}
AstNodeModule* nodeDeparam(AstNode* nodep, AstNodeModule* srcModp, AstNodeModule* modp,
const string& someInstanceName) {
// Return new or reused de-parameterized module
@@ -2221,14 +2391,9 @@ public:
}
// Create new module name with _'s between the constants
UINFOTREE(10, nodep, "", "cell");
// Resolve `class::member` Dots in pin values so constify sees Consts.
// Single-pass: filter-collect class-scoped Dots, then reverse-iterate so inner
// Dots resolve before outer (vector stays empty for cells with no class Dots).
std::vector<AstDot*> dotps;
nodep->foreach([&](AstDot* dotp) {
if (VN_IS(dotp->lhsp(), ClassOrPackageRef)) dotps.push_back(dotp);
});
for (auto it = dotps.rbegin(); it != dotps.rend(); ++it) resolveDotToTypedef(*it);
// Resolve `class::member` Dots in pin values, and in any deferred
// lparam reachable from the pin tree, so constify sees Consts.
resolveDeferredDotsReachableFrom(nodep, modp);
// Evaluate all module constants
V3Const::constifyParamsEdit(nodep);
// Set name for warnings for when we param propagate the module
@@ -2583,6 +2748,7 @@ class ParamVisitor final : public VNVisitor {
VarsByName m_modIfaceRefs; // Interface-ref Vars in current module, keyed by name
bool m_modIfaceRefsDone = false; // m_modIfaceRefs has been gathered for m_modp
string m_generateHierName; // Generate portion of hierarchy name
bool m_inGenerateCond = false; // Traversing a generate condition; see iterateGenerateCond
// METHODS
@@ -2895,7 +3061,9 @@ class ParamVisitor final : public VNVisitor {
// Visit parameters in the instantiation.
iterateChildren(nodep);
m_cellps.emplace(!isIface, nodep);
// A generate condition is folded and deleted before the drain loop runs, so
// queueing from one only leaves a dangling pointer. See iterateGenerateCond.
if (!m_inGenerateCond) m_cellps.emplace(!isIface, nodep);
}
// VISITORS
@@ -2949,7 +3117,14 @@ class ParamVisitor final : public VNVisitor {
}
void visit(AstRefDType* nodep) override {
if (isCircularType(nodep)) {
const bool isCircular = isCircularType(nodep);
// A module-body `typedef C#(Cfg)::t alias` the deferred pin/param walk
// never reached would survive to V3Width unlinked. If its alias chain is
// broken, run the same deferred resolution here.
if (!isCircular && nodep->typedefp() && !nodep->skipRefOrNullp()) {
m_processor.resolveDeferredDotsReachableFrom(nodep, m_modp);
}
if (isCircular) {
nodep->v3error("Typedef's type is circular: " << nodep->prettyName());
} else if (nodep->typedefp() && nodep->subDTypep()
&& (VN_IS(nodep->subDTypep()->skipRefOrNullp(), IfaceRefDType)
@@ -3100,6 +3275,7 @@ class ParamVisitor final : public VNVisitor {
v3Global.rootp()->pushDeferredParamVarp(nodep);
return;
}
m_processor.resolveDeferredDotsReachableFrom(nodep, m_modp);
V3Const::constifyParamsEdit(nodep);
}
}
@@ -3311,9 +3487,24 @@ class ParamVisitor final : public VNVisitor {
}
// Generate Statements
// Traverse a generate condition (if/case expression, for init/cond/inc).
//
// m_cellps assumes a queued ref outlives the drain loop, which holds elsewhere
// because deletion is deferred. A generate condition doesn't defer and folds
// immediately, so we can't queue. Anything queued here would be left dangling.
// Refs in the surviving arm are queued when it is recursed.
void iterateGenerateCond(AstNode* nodep) {
VL_RESTORER(m_inGenerateCond);
m_inGenerateCond = true;
iterateAndNextNull(nodep);
// The condition may reference deferred lparams whose Dots are still pending.
m_processor.resolveDeferredDotsReachableFrom(nodep, m_modp);
}
void visit(AstGenIf* nodep) override {
UINFO(9, " GENIF " << nodep);
iterateAndNextNull(nodep->condp());
iterateGenerateCond(nodep->condp());
// We suppress errors when widthing params since short-circuiting in
// the conditional evaluation may mean these error can never occur. We
// then make sure that short-circuiting is used by constifyParamsEdit.
@@ -3347,9 +3538,9 @@ class ParamVisitor final : public VNVisitor {
UINFO(9, " BEGIN " << nodep);
UINFO(9, " GENFOR " << forp);
// Visit child nodes before unrolling
iterateAndNextNull(forp->initsp());
iterateAndNextNull(forp->condp());
iterateAndNextNull(forp->incsp());
iterateGenerateCond(forp->initsp());
iterateGenerateCond(forp->condp());
iterateGenerateCond(forp->incsp());
V3Width::widthParamsEdit(forp); // Param typed widthing will NOT recurse the body
// Outer wrapper around generate used to hold genvar, and to ensure genvar
// doesn't conflict in V3LinkDot resolution with other genvars
@@ -3381,8 +3572,15 @@ class ParamVisitor final : public VNVisitor {
UINFO(9, " GENCASE " << nodep);
bool hit = false;
AstNode* keepp = nullptr;
iterateAndNextNull(nodep->exprp());
iterateGenerateCond(nodep->exprp());
V3Case::caseLint(nodep);
// Case-item expressions must also be linked before widthing the case.
for (AstGenCaseItem* itemp = nodep->itemsp(); itemp;
itemp = VN_AS(itemp->nextp(), GenCaseItem)) {
for (AstNode* ep = itemp->condsp(); ep; ep = ep->nextp()) {
m_processor.resolveDeferredDotsReachableFrom(ep, m_modp);
}
}
V3Width::widthParamsEdit(nodep); // Param typed widthing will NOT recurse the
// body, don't trigger errors yet.
V3Const::constifyParamsEdit(nodep->exprp()); // exprp may change
@@ -3392,7 +3590,7 @@ class ParamVisitor final : public VNVisitor {
itemp = VN_AS(itemp->nextp(), GenCaseItem)) {
for (AstNode* ep = itemp->condsp(); ep;) {
AstNode* const nextp = ep->nextp(); // May edit list
iterateAndNextNull(ep);
iterateGenerateCond(ep);
VL_DO_DANGLING(V3Const::constifyParamsEdit(ep), ep); // ep may change
ep = nextp;
}