mirror of
https://github.com/verilator/verilator.git
synced 2026-10-02 16:13:34 +02:00
@@ -79,6 +79,7 @@ Drew Risinger
|
||||
Drew Taussig
|
||||
Driss Hafdi
|
||||
Edgar E. Iglesias
|
||||
Edmund Lam
|
||||
Eddie Rydell
|
||||
em2machine
|
||||
emmettifelts
|
||||
@@ -330,6 +331,7 @@ Tymoteusz Blazejczyk
|
||||
Tyrone Marhguy
|
||||
Udaya Raj Subedi
|
||||
Udi Finkelstein
|
||||
UnsignedByte
|
||||
Unai Martinez-Corral
|
||||
Valentin Atepalikhin
|
||||
Varun Koyyalagunta
|
||||
|
||||
+3
-1
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
%Error: t/t_class_lparam_circular_bad.v:16:18: Variable's initial value is circular: 'a'
|
||||
: ... note: In instance 't'
|
||||
16 | localparam int a = a;
|
||||
| ^
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: t/t_class_lparam_circular_bad.v:21:18: Variable's initial value is circular: 'a'
|
||||
: ... note: In instance 't'
|
||||
21 | localparam int a = b;
|
||||
| ^
|
||||
%Error: t/t_class_lparam_circular_bad.v:27:18: Variable's initial value is circular: 'p'
|
||||
: ... note: In instance 't'
|
||||
27 | localparam int p = q;
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,43 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Negative test for the reference-cycle guard in V3Param's
|
||||
// constifyMemberValue. A class localparam whose value transitively
|
||||
// references itself is reached through a `class::member` Dot in a cell
|
||||
// parameter pin, so the fold recurses back into the same AstVar while it is
|
||||
// still being resolved. That must produce a clean user-facing error and
|
||||
// leave a Const behind, NOT recurse forever or crash the following width
|
||||
// pass on the still-unresolved Dot.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class Self #(parameter int W = 1);
|
||||
localparam int a = a;
|
||||
endclass
|
||||
|
||||
class Direct #(parameter int W = 1);
|
||||
// Two-step cycle: a -> b -> a
|
||||
localparam int a = b;
|
||||
localparam int b = a;
|
||||
endclass
|
||||
|
||||
class Indirect #(parameter int W = 1);
|
||||
// Three-step cycle through sibling lparams: p -> q -> r -> p
|
||||
localparam int p = q;
|
||||
localparam int q = r;
|
||||
localparam int r = p;
|
||||
endclass
|
||||
|
||||
module Sub #(parameter int P = 0) ();
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
typedef Self#(4) SCFG;
|
||||
typedef Direct#(4) DCFG;
|
||||
typedef Indirect#(4) ICFG;
|
||||
|
||||
Sub #(SCFG::a) u_self ();
|
||||
Sub #(DCFG::a) u_direct ();
|
||||
Sub #(ICFG::p) u_indirect ();
|
||||
endmodule
|
||||
@@ -0,0 +1,6 @@
|
||||
%Error: t/t_class_lparam_cycle_bad.v:25:20: Variable's initial value is circular: 'a'
|
||||
: ... note: In instance 't'
|
||||
25 | localparam int a = inner_a::v + b;
|
||||
| ^
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: Exiting due to
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,32 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// A class localparam whose value is deferred (contains a class::member Dot)
|
||||
// and also transitively references itself. V3Param's deferred-lparam fold
|
||||
// must break the reference cycle and report it cleanly, rather than
|
||||
// recursing forever or leaving an unresolved Dot that reaches V3Width as an
|
||||
// untyped node ("Node has no type" internal error).
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
module Sub #(parameter int WIDTH = 0) ();
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
virtual class A #(parameter int x = 0);
|
||||
localparam int v = x * 2;
|
||||
endclass
|
||||
|
||||
virtual class B #(parameter int y = 0);
|
||||
typedef A#(y + 1) inner_a;
|
||||
// 'a' is deferred (holds the inner_a::v Dot) and also references 'b';
|
||||
// 'b' references 'a' back -> cycle through the deferred fold.
|
||||
localparam int a = inner_a::v + b;
|
||||
localparam int b = a;
|
||||
endclass
|
||||
|
||||
typedef B#(5) BInst;
|
||||
|
||||
Sub #(BInst::a) m ();
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,294 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Consumers of a class-scope-resolved localparam (typedef alias of a
|
||||
// parameterized class, e.g. inst::b). V3LinkDot defers the inst::b Dot until
|
||||
// post-V3Param, so V3Param must resolve the deferred Dot everywhere such a
|
||||
// value can be consumed - following VarRef chains into deferred lparams, and
|
||||
// descending through typedefs and ParamTypeDTypes to reach buried Dots:
|
||||
// (1) module and interface instance parameter pins
|
||||
// (2) generate-for / -if / -case conditions
|
||||
// (3) typedef ranges, class type args, and $bits of class-scope typedefs
|
||||
// (4) nested class hierarchies (B2::width = inner_a::v)
|
||||
// (5) struct localparam field access (CFG::cfg.jt.cam_type)
|
||||
// (6) struct members typed by a class-scope typedef (CFG::data_t)
|
||||
// (7) `localparam type` bound to a child module's type parameter
|
||||
//
|
||||
// Plain localparam value chains are covered by t_class_lparam_chain.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
typedef struct packed {
|
||||
logic [7:0] cam_type;
|
||||
logic [7:0] depth;
|
||||
} inner_t;
|
||||
|
||||
typedef struct packed {
|
||||
inner_t jt;
|
||||
logic [7:0] tag;
|
||||
} cfg_t;
|
||||
|
||||
typedef struct packed {
|
||||
inner_t [1:0] entries;
|
||||
} table_t;
|
||||
|
||||
package pkg;
|
||||
virtual class C #(parameter int W = 1);
|
||||
typedef logic [W-1:0] data_t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
// Struct localparam whose fields derive from the class parameter, plus a
|
||||
// wrapper class whose own lparam reads a nested struct field.
|
||||
class SC #(parameter int W = 1);
|
||||
localparam cfg_t cfg = '{jt: '{cam_type: W[7:0], depth: W[7:0] + 8'd1},
|
||||
tag: W[7:0] + 8'd2};
|
||||
localparam logic [7:0] bits = W[7:0];
|
||||
localparam inner_t [1:0] entries = '{default: '{cam_type: W[7:0],
|
||||
depth: W[7:0] + 8'd1}};
|
||||
localparam table_t tbl = '{entries: '{default: '{cam_type: W[7:0],
|
||||
depth: W[7:0] + 8'd1}}};
|
||||
endclass
|
||||
|
||||
class SD #(parameter int W = 1);
|
||||
typedef SC#(W) CC;
|
||||
localparam int q = int'(CC::cfg.tag) + 100;
|
||||
endclass
|
||||
|
||||
class P #(parameter cfg_t p = '{default: 0});
|
||||
localparam cfg_t pp = p;
|
||||
endclass
|
||||
|
||||
module Sub #(parameter int WIDTH = 0) ();
|
||||
endmodule
|
||||
|
||||
// Two-level: outer module forwards its param to an inner cell, so the
|
||||
// deferred lparam value must flow through the cell-deparam chain.
|
||||
module SubL1 #(parameter int W = 0) ();
|
||||
Sub #(W) inner ();
|
||||
endmodule
|
||||
|
||||
interface SubIface #(parameter int IW = 1) ();
|
||||
logic [IW-1:0] data;
|
||||
endinterface
|
||||
|
||||
// Takes an interface port - must elaborate after the iface cell pin is
|
||||
// fully constified.
|
||||
module Consumer (SubIface si);
|
||||
endmodule
|
||||
|
||||
// Presence is observable, to confirm each generate flavour elaborated.
|
||||
module Tag #(parameter int ID = 0) ();
|
||||
endmodule
|
||||
|
||||
module Sink #(parameter int BITS = 1) ();
|
||||
logic [BITS-1:0] data;
|
||||
endmodule
|
||||
|
||||
// Parameterized wrapper so the class specialization is deferred until
|
||||
// V3Param processes the cell instance.
|
||||
module Mid #(parameter int W = 8) ();
|
||||
typedef SC#(W) CFG;
|
||||
// (5) single-level struct field access, then nested (lsb accumulation)
|
||||
Sub #(int'(CFG::cfg.tag)) u_tag ();
|
||||
Sub #(int'(CFG::cfg.jt.cam_type)) u_cam ();
|
||||
Sub #(int'(CFG::cfg.jt.depth)) u_depth ();
|
||||
Sub #(int'(CFG::bits[0])) u_bit ();
|
||||
Sub #(int'(CFG::bits[3:1])) u_part ();
|
||||
Sub #(int'(CFG::cfg.jt.cam_type[3])) u_field_bit ();
|
||||
Sub #(int'(CFG::cfg.jt.cam_type[3:1])) u_field_part ();
|
||||
Sub #(int'(CFG::entries[1].cam_type[3:1])) u_array_field_part ();
|
||||
Sub #(int'(CFG::tbl.entries[1].cam_type[3:1])) u_member_array_field_part ();
|
||||
// nested struct-field Dot buried in a wrapper class's lparam value
|
||||
typedef SD#(W) DD;
|
||||
Sub #(int'(DD::q)) u_q ();
|
||||
|
||||
// (6) struct member typed by a class-scope typedef from a parameterized
|
||||
// class, consumed by $bits on a cell pin. Without following typedefs the
|
||||
// member RefDType stays unlinked and $bits trips an internal error.
|
||||
typedef pkg::C#(W) PCFG;
|
||||
typedef struct packed {
|
||||
PCFG::data_t payload;
|
||||
logic v;
|
||||
} wrap_t;
|
||||
Sink #(.BITS($bits(wrap_t))) u_sink ();
|
||||
endmodule
|
||||
|
||||
module PairHolder #(parameter cfg_t cfg = '{default: 0}) ();
|
||||
typedef P#(cfg) PALIAS;
|
||||
localparam logic [7:0] tag_val = PALIAS::pp.tag;
|
||||
localparam logic [7:0] cam_val = PALIAS::pp.jt.cam_type;
|
||||
endmodule
|
||||
|
||||
// (7) The pin's RefDType has no typedefp() to follow - it points at the
|
||||
// enclosing ParamTypeDType - so the deferred walk must descend refDTypep().
|
||||
module TChild #(parameter type T = logic, parameter int EXP = 1) (input T a_i);
|
||||
initial `checkh($bits(T), EXP);
|
||||
endmodule
|
||||
|
||||
module TFwd #(parameter type T = logic, parameter int EXP = 1) ();
|
||||
TChild #(.T(T), .EXP(EXP)) u (.a_i('0));
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
virtual class C #(parameter int a = 0);
|
||||
localparam int b = a;
|
||||
typedef logic [a-1:0] inner_t;
|
||||
// localparam derived from a typedef inside the same class
|
||||
localparam int width = $bits(inner_t);
|
||||
endclass
|
||||
|
||||
// Two-level: B2's lparam value contains a class::member Dot of its own
|
||||
virtual class A2 #(parameter int x = 0);
|
||||
localparam int v = x * 2;
|
||||
endclass
|
||||
virtual class B2 #(parameter int y = 0);
|
||||
typedef A2#(y + 1) inner_a;
|
||||
localparam int width = inner_a::v; // nested Dot inside B2's lparam
|
||||
// `sib` is deferred (holds the Dot); `sib_use` reads it as a sibling, so
|
||||
// the sibling must be folded before this member's value can constify.
|
||||
localparam int sib = inner_a::v;
|
||||
localparam int sib_use = sib + 1;
|
||||
endclass
|
||||
// Three-level: C2 wraps B2 wraps A2
|
||||
virtual class C2 #(parameter int z = 0);
|
||||
typedef B2#(z * 3) inner_b;
|
||||
localparam int total = inner_b::width; // double-nested Dot
|
||||
endclass
|
||||
|
||||
typedef C#(3) c3;
|
||||
typedef C#(4) c4;
|
||||
typedef C#(5) c5;
|
||||
typedef C#(8) c8;
|
||||
typedef C#(13) c13;
|
||||
typedef B2#(5) BInst;
|
||||
typedef C2#(2) CInst;
|
||||
|
||||
// Deferred lparams (value contains a class::member Dot)
|
||||
localparam int b3 = c3::b;
|
||||
localparam int b4 = c4::b;
|
||||
localparam int b5 = c5::b;
|
||||
localparam int b8 = c8::b;
|
||||
localparam int b13 = c13::b;
|
||||
// Chained: value references another deferred lparam
|
||||
localparam int c8_ref = b8;
|
||||
localparam int d8_ref = c8_ref + 1;
|
||||
|
||||
// (4) nested class hierarchies
|
||||
localparam int two_level = BInst::width; // = A2#(6)::v = 12
|
||||
localparam int three_level = CInst::total; // = B2#(6)::width = A2#(7)::v = 14
|
||||
localparam int nested_chain = BInst::width;
|
||||
|
||||
// (1) module pins: bare VarRef, multi-lparam chain, expressions, a Dot
|
||||
// mixed with an lparam, two-level forwarding, and nested-class Dots.
|
||||
Sub #(b8) m_bare ();
|
||||
Sub #(d8_ref) m_chain ();
|
||||
Sub #(b4 + b5) m_expr ();
|
||||
Sub #(c4::b + b5) m_mix ();
|
||||
SubL1 #(b8) m_l1 ();
|
||||
Sub #(BInst::width) m_pin_direct ();
|
||||
Sub #(CInst::total) m_pin_deep ();
|
||||
Sub #(BInst::width + 1) m_pin_expr ();
|
||||
Sub #(nested_chain) m_pin_chain ();
|
||||
// sibling-lparam fold: BInst::sib_use = A2#(6)::v + 1 = 13
|
||||
Sub #(BInst::sib_use) m_pin_sibling ();
|
||||
|
||||
// (1) interface pins, plus an iface bound to a module port
|
||||
SubIface #(b8) i_bare ();
|
||||
SubIface #(c8_ref) i_chain ();
|
||||
SubIface #(b8 + b13) i_expr ();
|
||||
Consumer cons (.si(i_bare));
|
||||
|
||||
// (2) generate-for / -if / -case driven by deferred lparams
|
||||
for (genvar i = 0; i < b3; i++) begin : gf
|
||||
Tag #(100 + i) inst ();
|
||||
end
|
||||
if (b5 > b3) begin : gi_t
|
||||
Tag #(200) inst ();
|
||||
end else begin : gi_f
|
||||
Tag #(201) inst ();
|
||||
end
|
||||
case (b5)
|
||||
3: begin : gc Tag #(303) inst (); end
|
||||
5: begin : gc Tag #(305) inst (); end
|
||||
default: begin : gc Tag #(399) inst (); end
|
||||
endcase
|
||||
|
||||
// (3) typedef range from a deferred lparam; class type-arg using a
|
||||
// deferred lparam; class-scope typedef via $bits; and a typedef whose
|
||||
// packed range is built from class-scope Dots directly (no lparam).
|
||||
typedef logic [b8-1:0] data_t;
|
||||
data_t data_value;
|
||||
typedef C#(b8) c_from_def;
|
||||
localparam int from_def_b = c_from_def::b;
|
||||
localparam int via_bits = c8::width;
|
||||
logic [b8-1:0] wide_bus;
|
||||
typedef logic [(c8::b + c8::b - 1):0] direct_use_t;
|
||||
Sub #(.WIDTH($bits(direct_use_t))) u_sub ();
|
||||
|
||||
// (5)/(6) struct field access and class-scope-typed struct members
|
||||
Mid #(.W(8)) u_mid ();
|
||||
PairHolder #(.cfg('{jt: '{cam_type: 8'd7, depth: 8'd3}, tag: 8'd11})) u_ph ();
|
||||
|
||||
// (7) `localparam type` from a class typedef, bound to a type parameter
|
||||
localparam type t_plain = pkg::C#(12)::data_t;
|
||||
typedef pkg::C#(9) alias_c;
|
||||
localparam type t_alias = alias_c::data_t;
|
||||
TChild #(.T(t_plain), .EXP(12)) u_plain (.a_i('0));
|
||||
TChild #(.T(cfg_t), .EXP(24)) u_tstruct (.a_i('0));
|
||||
TChild #(.T(t_alias), .EXP(9)) u_alias (.a_i('0));
|
||||
TFwd #(.T(t_plain), .EXP(12)) u_fwd ();
|
||||
|
||||
initial begin
|
||||
`checkh(b3, 32'd3);
|
||||
`checkh(b4, 32'd4);
|
||||
`checkh(b5, 32'd5);
|
||||
`checkh(b8, 32'd8);
|
||||
`checkh(b13, 32'd13);
|
||||
`checkh(c8_ref, 32'd8);
|
||||
`checkh(d8_ref, 32'd9);
|
||||
// (1) interface pins took the deferred widths
|
||||
`checkh($bits(i_bare.data), 32'd8);
|
||||
`checkh($bits(i_chain.data), 32'd8);
|
||||
`checkh($bits(i_expr.data), 32'd21);
|
||||
// (3) typedefs / $bits
|
||||
`checkh(from_def_b, 32'd8);
|
||||
`checkh(via_bits, 32'd8);
|
||||
data_value = '1;
|
||||
`checkh(data_value, 8'hff);
|
||||
wide_bus = '1;
|
||||
`checkh(wide_bus, 8'hff);
|
||||
`checkh($bits(direct_use_t), 32'd16);
|
||||
// (4) nested classes
|
||||
`checkh(two_level, 32'd12);
|
||||
`checkh(three_level, 32'd14);
|
||||
`checkh(nested_chain, 32'd12);
|
||||
`checkh(m_pin_sibling.WIDTH, 32'd13);
|
||||
// (5) struct fields: tag = W+2, cam_type = W, depth = W+1
|
||||
`checkh(u_mid.u_tag.WIDTH, 32'd10);
|
||||
`checkh(u_mid.u_cam.WIDTH, 32'd8);
|
||||
`checkh(u_mid.u_depth.WIDTH, 32'd9);
|
||||
`checkh(u_mid.u_bit.WIDTH, 32'd0);
|
||||
`checkh(u_mid.u_part.WIDTH, 32'd4);
|
||||
`checkh(u_mid.u_field_bit.WIDTH, 32'd1);
|
||||
`checkh(u_mid.u_field_part.WIDTH, 32'd4);
|
||||
`checkh(u_mid.u_array_field_part.WIDTH, 32'd4);
|
||||
`checkh(u_mid.u_member_array_field_part.WIDTH, 32'd4);
|
||||
`checkh(u_mid.u_q.WIDTH, 32'd110);
|
||||
`checkh(u_ph.tag_val, 8'd11);
|
||||
`checkh(u_ph.cam_val, 8'd7);
|
||||
// (6) $bits(wrap_t) = 8 (payload) + 1 (v) = 9
|
||||
`checkh($bits(u_mid.u_sink.data), 32'd9);
|
||||
// (7) localparam type widths
|
||||
`checkh($bits(t_plain), 32'd12);
|
||||
`checkh($bits(t_alias), 32'd9);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,87 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Generate conditions that specialize a parameterized class *inline*
|
||||
// (`C#(N)::b`, `$bits(C#(N))`) rather than through an intermediate
|
||||
// localparam or typedef alias.
|
||||
//
|
||||
// V3Param's visit(AstGenIf/GenBlock/GenCase) iterates the condition
|
||||
// before folding it, which queues the class reference in m_cellps via
|
||||
// visitCellOrClassRef. Folding then destroys those nodes outright
|
||||
// (V3Width deletes a resolved $bits() tree; the untaken arm is
|
||||
// deleteTree()'d) instead of deferring via pushDeletep, so the queued
|
||||
// pointers must be dropped or processWorkQ()'s drain loop dereferences
|
||||
// freed memory -- giving a bogus "Expected module parameterization"
|
||||
// fatal, or a segfault on the generate-case flavour.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
package P;
|
||||
virtual class cfg #(
|
||||
parameter int W = 8
|
||||
);
|
||||
localparam int width = W;
|
||||
typedef logic [W-1:0] data_t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
// Cell whose presence is observable, to confirm each generate
|
||||
// flavour elaborated the arm we expect.
|
||||
module Tag #(
|
||||
parameter int ID
|
||||
) ();
|
||||
initial $write("Tag ID=%0d\n", ID);
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
// Class in the compilation unit, reached with a single '::'
|
||||
virtual class C #(
|
||||
parameter int a
|
||||
);
|
||||
localparam int b = a;
|
||||
endclass
|
||||
|
||||
// (1) Generate-if cond: inline specialization, single '::'
|
||||
if (C#(5)::b > C#(3)::b) begin : gi_t
|
||||
Tag #(200) inst ();
|
||||
end else begin : gi_f
|
||||
Tag #(201) inst ();
|
||||
end
|
||||
|
||||
// (2) Generate-if cond: inline specialization through a package, two '::'
|
||||
if (P::cfg#(12)::width == 12) begin : gi_pkg
|
||||
Tag #(212) inst ();
|
||||
end
|
||||
|
||||
// (3) Generate-if cond: $bits() of an inline specialization. No Dot at
|
||||
// all -- the queued node is the AstClassRefDType itself, and V3Width
|
||||
// deletes the whole $bits() tree once it folds.
|
||||
if ($bits(P::cfg#(6)::data_t) == 6) begin : gi_bits
|
||||
Tag #(206) inst ();
|
||||
end
|
||||
|
||||
// (4) Genvar bound = inline specialization
|
||||
for (genvar i = 0; i < C#(3)::b; i++) begin : gf
|
||||
Tag #(100 + i) inst ();
|
||||
end
|
||||
|
||||
// (5) Generate-case selector = inline specialization
|
||||
case (P::cfg#(5)::width)
|
||||
3: begin : gc Tag #(303) inst (); end
|
||||
5: begin : gc Tag #(305) inst (); end
|
||||
default: begin : gc Tag #(399) inst (); end
|
||||
endcase
|
||||
|
||||
initial begin
|
||||
`checkh(C#(3)::b, 32'd3);
|
||||
`checkh(P::cfg#(12)::width, 32'd12);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,21 @@
|
||||
%Error-UNSUPPORTED: t/t_class_lparam_nonparam_bad.v:26:14: dotted expressions in parameters
|
||||
: ... note: In instance 't'
|
||||
: ... Suggest use a typedef
|
||||
26 | Sub #(CFG::notaparam) u_prop ();
|
||||
| ^~~~~~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error: t/t_class_lparam_nonparam_bad.v:26:14: Can't convert defparam value to constant: Param '__paramNumber1' of 'u_prop'
|
||||
: ... note: In instance 't'
|
||||
26 | Sub #(CFG::notaparam) u_prop ();
|
||||
| ^~~~~~~~~
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error-UNSUPPORTED: t/t_class_lparam_nonparam_bad.v:27:14: dotted expressions in parameters
|
||||
: ... note: In instance 't'
|
||||
: ... Suggest use a typedef
|
||||
27 | Sub #(CFG::alsonotaparam) u_static ();
|
||||
| ^~~~~~~~~~~~~
|
||||
%Error: t/t_class_lparam_nonparam_bad.v:27:14: Can't convert defparam value to constant: Param '__paramNumber1' of 'u_static'
|
||||
: ... note: In instance 't'
|
||||
27 | Sub #(CFG::alsonotaparam) u_static ();
|
||||
| ^~~~~~~~~~~~~
|
||||
%Error: Exiting due to
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,28 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Negative test for the member-kind guard in V3Param's
|
||||
// substituteParamMember. A `class::member` Dot in a cell parameter pin
|
||||
// that names a member which is not a parameter (a plain class property, or
|
||||
// a static one) has no constant value to substitute, so V3Param must leave
|
||||
// the Dot alone and let the normal unsupported-dotted-parameter diagnostic
|
||||
// fire, rather than substituting or crashing.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class C #(parameter int W = 1);
|
||||
int notaparam;
|
||||
static int alsonotaparam;
|
||||
localparam int good = W;
|
||||
endclass
|
||||
|
||||
module Sub #(parameter int P = 0) ();
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
typedef C#(4) CFG;
|
||||
|
||||
Sub #(CFG::notaparam) u_prop ();
|
||||
Sub #(CFG::alsonotaparam) u_static ();
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,59 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Repeated references to the same deferred localparam. V3Param's
|
||||
// resolveDeferredDotsReachableFrom walks a pin expression collecting
|
||||
// deferred lparams to descend into, and must visit each one only once even
|
||||
// when a single expression (or several pins on the same cell) references it
|
||||
// many times, including via a diamond where two intermediate lparams both
|
||||
// lead back to the same deferred one.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
class Inner #(parameter int V = 1);
|
||||
localparam int v = V;
|
||||
endclass
|
||||
|
||||
module Sub #(
|
||||
parameter int P = 0,
|
||||
parameter int Q = 0
|
||||
) ();
|
||||
localparam int GOTP = P;
|
||||
localparam int GOTQ = Q;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
// Deferred: value is a class::member Dot.
|
||||
localparam int d = Inner#(7)::v;
|
||||
|
||||
// Diamond: both lead back to the single deferred `d`.
|
||||
localparam int left = d + 1;
|
||||
localparam int right = d + 2;
|
||||
|
||||
// Same deferred lparam reached repeatedly within one pin expression...
|
||||
Sub #(
|
||||
.P(d + d + d),
|
||||
.Q(left + right)
|
||||
) u_repeat ();
|
||||
|
||||
// ...and across two pins of the same cell.
|
||||
Sub #(
|
||||
.P(d),
|
||||
.Q(d)
|
||||
) u_bothpins ();
|
||||
|
||||
initial begin
|
||||
`checkh(u_repeat.GOTP, 32'd21);
|
||||
`checkh(u_repeat.GOTQ, 32'd17);
|
||||
`checkh(u_bothpins.GOTP, 32'd7);
|
||||
`checkh(u_bothpins.GOTQ, 32'd7);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,60 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Sibling-localparam recursion in V3Param's constifyMemberValue. When a
|
||||
// `class::member` Dot resolves to an lparam whose own value is not yet a
|
||||
// Const because it references *sibling* lparams of the same class, those
|
||||
// siblings must be folded first (deepest-first) so the whole chain
|
||||
// constifies. Covers values that reach the class::member Dot only
|
||||
// indirectly, e.g. `one = base` where `base = Inner#(W)::v`, including
|
||||
// multi-level chains and one expression referencing several siblings.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
class Inner #(parameter int V = 1);
|
||||
localparam int v = V;
|
||||
endclass
|
||||
|
||||
class C #(parameter int W = 1);
|
||||
// Only `base` holds the class::member Dot directly.
|
||||
localparam int base = Inner#(W)::v;
|
||||
// `one` is a bare VarRef to a sibling that is not yet Const.
|
||||
localparam int one = base;
|
||||
// Multi-level: two -> one -> base -> Inner::v
|
||||
localparam int two = one + 1;
|
||||
// One value referencing several not-yet-Const siblings at once.
|
||||
localparam int sum = base + one + two;
|
||||
// A non-param member: substituteParamMember must leave it alone.
|
||||
int notaparam;
|
||||
endclass
|
||||
|
||||
module Sub #(
|
||||
parameter int P = 0
|
||||
) ();
|
||||
localparam int GOT = P;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
typedef C#(5) CFG;
|
||||
|
||||
// Each pin drags a different point of the sibling chain through V3Param.
|
||||
Sub #(CFG::base) u_base ();
|
||||
Sub #(CFG::one) u_one ();
|
||||
Sub #(CFG::two) u_two ();
|
||||
Sub #(CFG::sum) u_sum ();
|
||||
|
||||
initial begin
|
||||
`checkh(u_base.GOT, 32'd5);
|
||||
`checkh(u_one.GOT, 32'd5);
|
||||
`checkh(u_two.GOT, 32'd6);
|
||||
`checkh(u_sum.GOT, 32'd16);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,19 @@
|
||||
%Error-UNSUPPORTED: t/t_class_lparam_struct_field_chain_bad.v:36:27: Unsupported: Member call on object 'SEL' which is a 'BASICDTYPE 'logic''
|
||||
: ... note: In instance 't.u'
|
||||
36 | Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct ();
|
||||
| ^~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error-UNSUPPORTED: t/t_class_lparam_struct_field_chain_bad.v:36:12: Unsupported: static cast to 'int' from 'VOIDDTYPE'
|
||||
: ... note: In instance 't.u'
|
||||
36 | Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct ();
|
||||
| ^
|
||||
%Error: t/t_class_lparam_struct_field_chain_bad.v:36:12: Can't convert defparam value to constant: Param '__paramNumber1' of 'u_nonstruct'
|
||||
: ... note: In instance 't.u'
|
||||
36 | Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct ();
|
||||
| ^
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: t/t_class_lparam_struct_field_chain_bad.v:38:26: Member 'nosuchfield' not found in structure
|
||||
: ... note: In instance 't.u'
|
||||
38 | Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember ();
|
||||
| ^~~~~~~~~~~
|
||||
%Error: Exiting due to
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,43 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Negative test for the struct-localparam field-access folding in V3Param
|
||||
// (resolveDotToTypedef). Malformed outer `.field` chains on a class
|
||||
// localparam value must produce clean user-facing errors, NOT an internal
|
||||
// error or a crash in the following width pass. Exercises the two
|
||||
// mismatch guards in the field-chain walk:
|
||||
// (1) `.field` applied to a non-struct value
|
||||
// (2) field name not a member of the struct
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
typedef struct packed {
|
||||
logic [7:0] cam_type;
|
||||
logic [7:0] depth;
|
||||
} inner_t;
|
||||
|
||||
typedef struct packed {
|
||||
inner_t jt;
|
||||
logic [7:0] tag;
|
||||
} cfg_t;
|
||||
|
||||
class C #(parameter int W = 1);
|
||||
localparam cfg_t cfg = '{jt: '{cam_type: W[7:0], depth: W[7:0] + 8'd1},
|
||||
tag: W[7:0] + 8'd2};
|
||||
endclass
|
||||
|
||||
module Sub #(parameter int WIDTH = 0) ();
|
||||
endmodule
|
||||
|
||||
module Mid #(parameter int W = 8) ();
|
||||
typedef C#(W) CFG;
|
||||
// (1) `.bogus` on the scalar field `tag`: dotting into a non-struct
|
||||
Sub #(int'(CFG::cfg.tag.bogus)) u_nonstruct ();
|
||||
// (2) `.nosuchfield` not a member of struct `jt`
|
||||
Sub #(int'(CFG::cfg.jt.nosuchfield)) u_nomember ();
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
Mid #(.W(8)) u ();
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary', '--assert'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,48 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
class types_c #(
|
||||
parameter int W = 8
|
||||
);
|
||||
typedef logic [W-1:0] t;
|
||||
typedef logic signed [W-1:0] type1_t;
|
||||
typedef type1_t [W-1:0] type2_t;
|
||||
endclass
|
||||
|
||||
module type_scope #(
|
||||
parameter type C = types_c#(8)
|
||||
) ();
|
||||
C::t value;
|
||||
child #(.T(C::t)) u_child (.a_i('0));
|
||||
initial assert ($bits(value) == 8);
|
||||
endmodule
|
||||
|
||||
module child #(
|
||||
parameter type T = logic
|
||||
) (
|
||||
input T a_i
|
||||
);
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
localparam int W = 8;
|
||||
|
||||
// Class-scoped type via a localparam type, passed to a child type parameter.
|
||||
localparam type t_param = types_c#(W)::t;
|
||||
child #(.T(t_param)) u (.a_i('0));
|
||||
type_scope u_type_scope ();
|
||||
|
||||
// Class-scoped type via a module-body typedef alias of a nested typedef.
|
||||
typedef types_c#(W)::type2_t t_alias;
|
||||
t_alias v;
|
||||
|
||||
initial begin
|
||||
v = '0;
|
||||
assert ($bits(t_param) == 8);
|
||||
assert ($bits(t_alias) == 64);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=["--binary"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,90 @@
|
||||
// DESCRIPTION: Verilator: Unpacked struct values as class parameters
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
package P;
|
||||
typedef struct {
|
||||
int depth;
|
||||
} memory_config_t;
|
||||
|
||||
typedef struct {
|
||||
memory_config_t memory;
|
||||
int counters;
|
||||
} config_t;
|
||||
|
||||
virtual class cfg #(
|
||||
parameter config_t C
|
||||
);
|
||||
localparam int WIDTH = $clog2(C.memory.depth);
|
||||
localparam int COUNTERS = C.counters;
|
||||
typedef logic [WIDTH-1:0] data_t;
|
||||
endclass
|
||||
|
||||
localparam config_t PKG_CFG = '{memory: '{depth: 8192}, counters: 6};
|
||||
typedef cfg#(PKG_CFG) PkgCfg;
|
||||
endpackage
|
||||
|
||||
package Q;
|
||||
localparam P::config_t CFG = '{memory: '{depth: 1024}, counters: 3};
|
||||
typedef P::cfg#(CFG) CrossPkgCfg;
|
||||
endpackage
|
||||
|
||||
module Sub #(
|
||||
parameter P::config_t C
|
||||
) (
|
||||
input P::cfg#(C)::data_t din
|
||||
);
|
||||
initial begin
|
||||
`checkh($bits(din), $clog2(C.memory.depth));
|
||||
`checkh(C.counters, P::cfg#(C)::COUNTERS);
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
localparam P::config_t CFG_A = '{memory: '{depth: 4096}, counters: 4};
|
||||
localparam P::config_t CFG_B = '{memory: '{depth: 32768}, counters: 7};
|
||||
// Equal to CFG_A, so must select the same Sub specialization.
|
||||
localparam P::config_t CFG_A2 = '{memory: '{depth: 4096}, counters: 4};
|
||||
// Differs from CFG_A only in a nested field, so must not share with it.
|
||||
localparam P::config_t CFG_C = '{memory: '{depth: 4096}, counters: 5};
|
||||
|
||||
P::cfg#(CFG_A)::data_t a;
|
||||
P::cfg#(CFG_B)::data_t b;
|
||||
P::PkgCfg::data_t pkg;
|
||||
Q::CrossPkgCfg::data_t cross_pkg;
|
||||
|
||||
P::cfg#(CFG_A2)::data_t a2;
|
||||
P::cfg#(CFG_C)::data_t c;
|
||||
|
||||
Sub #(.C(CFG_A)) sub (.din(a));
|
||||
Sub #(.C(CFG_A2)) sub_same (.din(a2));
|
||||
Sub #(.C(CFG_C)) sub_diff (.din(c));
|
||||
|
||||
initial begin
|
||||
a = '0;
|
||||
b = '0;
|
||||
pkg = '0;
|
||||
cross_pkg = '0;
|
||||
a2 = '0;
|
||||
c = '0;
|
||||
`checkh($bits(a), 12);
|
||||
`checkh($bits(b), 15);
|
||||
`checkh($bits(pkg), 13);
|
||||
`checkh($bits(cross_pkg), 10);
|
||||
`checkh(P::cfg#(CFG_A)::COUNTERS, 4);
|
||||
`checkh(P::cfg#(CFG_B)::COUNTERS, 7);
|
||||
// Equal struct values must specialize identically, unequal ones distinctly.
|
||||
`checkh($bits(a2), $bits(a));
|
||||
`checkh(P::cfg#(CFG_A2)::COUNTERS, 4);
|
||||
`checkh(P::cfg#(CFG_C)::COUNTERS, 5);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
test.top_filename = "t/t_class_param_struct.v"
|
||||
|
||||
test.compile(verilator_flags2=["--binary --debug-collision"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,120 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Chained class scope resolution in *type* position: `pkg::cls::t` and
|
||||
// `pkg::cls#(P)::t`.
|
||||
//
|
||||
// V3LinkDot's visit(AstRefDType) resolves the scope operand, but only handled a
|
||||
// single AstClassOrPackageRef. With two '::' the operand is an AstDot holding a
|
||||
// ref per scope, which fell to an E_UNSUPPORTED ("Multiple '::' package/class
|
||||
// reference") followed by "Can't find typedef". Value position (`pkg::cls::n`
|
||||
// in a localparam) already worked -- only the type path was missing.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
package P;
|
||||
class Plain;
|
||||
typedef logic [7:0] t;
|
||||
localparam int n = 8;
|
||||
endclass
|
||||
|
||||
class Par #(
|
||||
parameter int W = 8
|
||||
);
|
||||
typedef logic [W-1:0] t;
|
||||
localparam int n = W;
|
||||
endclass
|
||||
|
||||
class Outer;
|
||||
class Inner;
|
||||
typedef logic [6:0] t;
|
||||
endclass
|
||||
endclass
|
||||
|
||||
// Traits-class shape: types derived from a struct config parameter
|
||||
typedef struct packed {
|
||||
int depth;
|
||||
int counters;
|
||||
} config_t;
|
||||
|
||||
virtual class cfg #(
|
||||
parameter config_t c
|
||||
);
|
||||
localparam int width = $clog2(c.depth);
|
||||
localparam int counters = c.counters;
|
||||
typedef logic [width-1:0] data_t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
// Class-scoped types crossing a module boundary, as a port and inside a
|
||||
// packed struct alongside type-parameter members.
|
||||
module Sub #(
|
||||
parameter P::config_t cfg,
|
||||
parameter type stk_t = logic
|
||||
) (
|
||||
input P::cfg#(cfg)::data_t din,
|
||||
input logic [P::cfg#(cfg)::counters-1:0] mask,
|
||||
input stk_t stk,
|
||||
output P::cfg#(cfg)::data_t dout
|
||||
);
|
||||
typedef struct packed {
|
||||
P::cfg#(cfg)::data_t d;
|
||||
stk_t s;
|
||||
} packed_t;
|
||||
packed_t p;
|
||||
always_comb p = '{d: din, s: stk};
|
||||
always_comb dout = p.d;
|
||||
initial begin
|
||||
`checkh($bits(din), 12);
|
||||
`checkh($bits(mask), 4);
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
// (1) pkg::cls::type -- no parameters
|
||||
P::Plain::t a;
|
||||
// (2) pkg::cls#(P)::type -- parameterized
|
||||
P::Par#(12)::t b;
|
||||
// (3) through a typedef
|
||||
typedef P::Par#(5)::t c_t;
|
||||
c_t c;
|
||||
// (4) an arbitrarily deep class scope
|
||||
P::Outer::Inner::t d;
|
||||
// (5) value position (already worked; guards against regressing it)
|
||||
localparam int na = P::Plain::n;
|
||||
localparam int nb = P::Par#(12)::n;
|
||||
|
||||
localparam P::config_t CFG = '{depth: 4096, counters: 4};
|
||||
typedef struct packed { logic [7:0] hi; } stk_t;
|
||||
P::cfg#(CFG)::data_t din, dout;
|
||||
|
||||
Sub #(.cfg(CFG), .stk_t(stk_t)) sub (
|
||||
.din (din),
|
||||
.mask ('0),
|
||||
.stk ('0),
|
||||
.dout (dout)
|
||||
);
|
||||
|
||||
initial begin
|
||||
a = '0;
|
||||
b = '0;
|
||||
c = '0;
|
||||
d = '0;
|
||||
din = '0;
|
||||
`checkh($bits(a), 8);
|
||||
`checkh($bits(b), 12);
|
||||
`checkh($bits(c), 5);
|
||||
`checkh($bits(d), 7);
|
||||
`checkh($bits(dout), 12);
|
||||
`checkh(na, 32'd8);
|
||||
`checkh(nb, 32'd12);
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,5 @@
|
||||
%Error-UNSUPPORTED: t/t_class_scope_chain_alias_bad.v:38:6: Unsupported: Multiple '::' package/class reference
|
||||
38 | Q::AliasPC::inner::u e;
|
||||
| ^~~~~~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error: Exiting due to
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,48 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// A middle segment of a `::` chain that aliases a parameterized class which is
|
||||
// not yet specialized has no reachable module to resolve the following segment
|
||||
// in. This is legal SystemVerilog that Verilator does not yet support, so it
|
||||
// must report gracefully rather than hit an internal assertion.
|
||||
//
|
||||
// Kept separate from t_class_scope_chain_bad.v because the errors there abort
|
||||
// the pass before a later declaration would be reached.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
package P;
|
||||
typedef struct packed {
|
||||
int depth;
|
||||
} config_t;
|
||||
|
||||
virtual class PC #(
|
||||
parameter config_t c
|
||||
);
|
||||
localparam int width = $clog2(c.depth);
|
||||
virtual class inner;
|
||||
typedef logic [width-1:0] u;
|
||||
endclass
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
package Q;
|
||||
localparam P::config_t cfgv = '{depth: 16};
|
||||
typedef P::PC#(cfgv) AliasPC;
|
||||
endpackage
|
||||
|
||||
// Referencing from an instantiated submodule is what leaves the alias
|
||||
// unspecialized at the time the chain is resolved.
|
||||
module Leaf ();
|
||||
Q::AliasPC::inner::u e;
|
||||
initial if ($bits(e) != 4) $stop;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
Leaf leaf ();
|
||||
initial begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -0,0 +1,14 @@
|
||||
%Error: t/t_class_scope_chain_bad.v:26:9: Can't find typedef/interface: 'nosuch'
|
||||
26 | P::K::nosuch a;
|
||||
| ^~~~~~
|
||||
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
|
||||
%Error: t/t_class_scope_chain_bad.v:28:6: Package/class for 'class/package reference' not found: 'NoSuchClass'
|
||||
28 | P::NoSuchClass::t b;
|
||||
| ^~~~~~~~~~~
|
||||
%Error: t/t_class_scope_chain_bad.v:30:3: Package/class for 'class/package reference' not found: 'NoSuchPkg'
|
||||
30 | NoSuchPkg::K::t c;
|
||||
| ^~~~~~~~~
|
||||
%Error: t/t_class_scope_chain_bad.v:32:9: Package/class for 'class/package reference' not found: 't'
|
||||
32 | P::K::t::deeper d;
|
||||
| ^
|
||||
%Error: Exiting due to
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('linter')
|
||||
|
||||
test.lint(fails=True, expect_filename=test.golden_filename)
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,37 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// Error cases for chained class scope resolution in type position. Each stage
|
||||
// of `pkg::cls::t` can fail to resolve; the diagnostic should name the segment
|
||||
// that actually failed, and should not also emit the "Multiple '::'" message.
|
||||
//
|
||||
// Arbitrarily deep nesting is supported; if an intermediate segment is not a
|
||||
// class or package, it should receive the same lookup diagnostic as any other
|
||||
// unresolved scope.
|
||||
//
|
||||
// Each declaration reports exactly once: once a segment fails, resolution of
|
||||
// that reference stops rather than also reporting the trailing type name.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
package P;
|
||||
class K;
|
||||
typedef logic [7:0] t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
module t;
|
||||
// Inner name is not a member of the (resolved) class
|
||||
P::K::nosuch a;
|
||||
// Middle scope does not exist
|
||||
P::NoSuchClass::t b;
|
||||
// Outer scope does not exist
|
||||
NoSuchPkg::K::t c;
|
||||
// Deeper than one nested scope is still unsupported
|
||||
P::K::t::deeper d;
|
||||
initial begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,79 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// `outer_pkg::Alias::member`, where `Alias` is a typedef in `outer_pkg` that
|
||||
// aliases a parameterized class from another package.
|
||||
//
|
||||
// Until the class is specialized in V3Param the alias's RHS is still a
|
||||
// RefDType carrying its own scope, so classOrPackageSkipp() walks to null.
|
||||
// Code that tested only Skipp therefore treated the (already resolved) ref as
|
||||
// unresolved and re-looked-up its bare name in the referencing module's scope,
|
||||
// where `Alias` is not visible - reporting "Package/class for ':: reference'
|
||||
// not found". Resolving from a submodule is what exposes it; with the
|
||||
// referencing module as top the enclosing scope happens to still find it.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
package base_pkg;
|
||||
typedef struct packed {
|
||||
int depth;
|
||||
int meta;
|
||||
} config_t;
|
||||
|
||||
virtual class cfg #(
|
||||
parameter config_t c
|
||||
);
|
||||
localparam int width = $clog2(c.depth);
|
||||
typedef logic [width-1:0] data_t;
|
||||
typedef struct packed {
|
||||
logic [c.meta-1:0] m;
|
||||
data_t d;
|
||||
} meta_t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
package top_pkg;
|
||||
localparam base_pkg::config_t cfgv = '{depth: 512, meta: 4};
|
||||
// Typedef alias of a parameterized class, in a different package
|
||||
typedef base_pkg::cfg #(cfgv) AliasCFG;
|
||||
endpackage
|
||||
|
||||
// Alias-scoped type as a port, and in the body, of an instantiated submodule
|
||||
module Leaf (
|
||||
input top_pkg::AliasCFG::meta_t in_meta,
|
||||
output top_pkg::AliasCFG::data_t out_data
|
||||
);
|
||||
top_pkg::AliasCFG::meta_t body_meta;
|
||||
always_comb begin
|
||||
body_meta = in_meta;
|
||||
out_data = body_meta.d;
|
||||
end
|
||||
initial begin
|
||||
`checkh($bits(in_meta), 13);
|
||||
`checkh($bits(out_data), 9);
|
||||
end
|
||||
endmodule
|
||||
|
||||
module Mid ();
|
||||
top_pkg::AliasCFG::meta_t m;
|
||||
top_pkg::AliasCFG::data_t d;
|
||||
Leaf leaf (
|
||||
.in_meta (m),
|
||||
.out_data (d)
|
||||
);
|
||||
initial m = '0;
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
Mid mid ();
|
||||
initial begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,112 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// `$bits()` of a signal whose type comes from a parameterized class
|
||||
// (`CFG::data_t`, via a typedef alias of `cls#(P)`).
|
||||
//
|
||||
// V3LinkDot defers linking a RefDType whose scope is a parameterized class -
|
||||
// it can only resolve once the class is specialized in V3Param. V3Param
|
||||
// resolves such RefDTypes where it knows to look (cell pin types, typedefs
|
||||
// reachable from a pin), but a plain module-level signal is not one of those
|
||||
// places; its RefDType stays unlinked until V3Width runs normally.
|
||||
//
|
||||
// `$bits(sig)` breaks that: constifyParamsEdit -> widthParamsEdit runs
|
||||
// V3Width early, during V3Param, to fold the parameter expression. Widthing
|
||||
// the VarRef forces the Var to be widthed, which calls skipRefp() on the
|
||||
// still-unlinked RefDType, giving "REFDTYPE 'data_t' not linked to type".
|
||||
// Using the signal ordinarily (assignment) does not trigger it, because
|
||||
// nothing forces the early width pass.
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilog_format: off
|
||||
`define stop $stop
|
||||
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0)
|
||||
// verilog_format: on
|
||||
|
||||
package pkg;
|
||||
typedef struct packed {
|
||||
int depth;
|
||||
} config_t;
|
||||
|
||||
virtual class cfg #(
|
||||
parameter config_t c
|
||||
);
|
||||
localparam int width = $clog2(c.depth);
|
||||
typedef logic [width-1:0] data_t;
|
||||
endclass
|
||||
endpackage
|
||||
|
||||
// Child with a type parameter defaulted from another parameter, as in a
|
||||
// sample-and-hold style utility module
|
||||
module Holder #(
|
||||
parameter int width = 0,
|
||||
parameter type data_t = logic [width-1:0]
|
||||
) (
|
||||
input data_t dat_i,
|
||||
output data_t dat_o
|
||||
);
|
||||
always_comb dat_o = dat_i;
|
||||
endmodule
|
||||
|
||||
module Sub #(
|
||||
parameter pkg::config_t cfg
|
||||
) ();
|
||||
typedef pkg::cfg #(cfg) CFG;
|
||||
CFG::data_t src, dst;
|
||||
typedef CFG::data_t unpacked_t[3:1];
|
||||
typedef CFG::data_t dynamic_t[];
|
||||
typedef CFG::data_t queue_t[$];
|
||||
typedef CFG::data_t associative_t[int];
|
||||
unpacked_t unpacked_data;
|
||||
dynamic_t dynamic_data;
|
||||
queue_t queue_data;
|
||||
associative_t associative_data;
|
||||
|
||||
// (1) $bits() of a class-scoped-typed signal, in a localparam
|
||||
localparam int Bits = $bits(src);
|
||||
localparam int UnpackedBits = $bits(unpacked_data);
|
||||
|
||||
// (2) $bits() of a class-scoped-typed signal, as a cell parameter pin
|
||||
Holder #(.width($bits(src))) holder (
|
||||
.dat_i (src),
|
||||
.dat_o (dst)
|
||||
);
|
||||
|
||||
// (3) Same, inside a generate arm
|
||||
if (CFG::width > 0) begin : gen_arm
|
||||
CFG::data_t arm_src, arm_dst;
|
||||
Holder #(.width($bits(arm_src))) armHolder (
|
||||
.dat_i (arm_src),
|
||||
.dat_o (arm_dst)
|
||||
);
|
||||
initial arm_src = '0;
|
||||
end
|
||||
|
||||
initial begin
|
||||
src = '0;
|
||||
unpacked_data = '{default: '1};
|
||||
dynamic_data = new[1];
|
||||
dynamic_data[0] = '1;
|
||||
queue_data.push_back('1);
|
||||
associative_data[3] = '1;
|
||||
`checkh(Bits, 32'd9);
|
||||
`checkh(UnpackedBits, 32'd27);
|
||||
`checkh($bits(src), 32'd9);
|
||||
`checkh($bits(dst), 32'd9);
|
||||
`checkh($bits(dynamic_data[0]), 32'd9);
|
||||
`checkh($bits(queue_data[0]), 32'd9);
|
||||
`checkh($bits(associative_data[3]), 32'd9);
|
||||
end
|
||||
endmodule
|
||||
|
||||
module t;
|
||||
localparam pkg::config_t CFGV = '{depth: 512};
|
||||
Sub #(.cfg(CFGV)) sub ();
|
||||
|
||||
initial begin
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
@@ -5,19 +5,6 @@
|
||||
%Error: t/t_parse_sync_bad2.v:17:7: Can't find typedef/interface: 'Invalid1'
|
||||
17 | Invalid1 invalid1;
|
||||
| ^~~~~~~~
|
||||
%Error-UNSUPPORTED: t/t_parse_sync_bad2.v:18:12: Unsupported: Multiple '::' package/class reference
|
||||
18 | pkg::cls::defi valid1;
|
||||
| ^~~
|
||||
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
|
||||
%Error: t/t_parse_sync_bad2.v:18:17: Can't find typedef/interface: 'defi'
|
||||
18 | pkg::cls::defi valid1;
|
||||
| ^~~~
|
||||
%Error-UNSUPPORTED: t/t_parse_sync_bad2.v:19:12: Unsupported: Multiple '::' package/class reference
|
||||
19 | pkg::cls::defu valid2;
|
||||
| ^~~
|
||||
%Error: t/t_parse_sync_bad2.v:19:17: Can't find typedef/interface: 'defu'
|
||||
19 | pkg::cls::defu valid2;
|
||||
| ^~~~
|
||||
%Error: t/t_parse_sync_bad2.v:20:7: Can't find typedef/interface: 'Invalid2'
|
||||
20 | Invalid2 invalid2;
|
||||
| ^~~~~~~~
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
# --public-params makes the struct parameter's members get registered for VPI,
|
||||
# which previously emitted a 'const void*' -> 'void*' conversion and so failed
|
||||
# to compile the generated C++.
|
||||
test.compile(verilator_flags2=["--binary --public-params"])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
@@ -0,0 +1,38 @@
|
||||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain.
|
||||
// SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
// verilator lint_off UNUSEDPARAM
|
||||
|
||||
package P;
|
||||
typedef struct {
|
||||
int depth;
|
||||
} memory_config_t;
|
||||
|
||||
typedef struct {
|
||||
memory_config_t memory;
|
||||
int counters;
|
||||
} config_t;
|
||||
endpackage
|
||||
|
||||
module t;
|
||||
// A public unpacked-struct parameter is emitted 'static const', so the
|
||||
// registration of its members must cast away const like the whole
|
||||
// parameter's registration does.
|
||||
localparam P::config_t CFG = '{memory: '{depth: 8192}, counters: 6};
|
||||
// An unpacked array of structs registers members through a separate path,
|
||||
// which used to null-dereference when asking whether it is a literal type.
|
||||
localparam P::memory_config_t MEM_ARR[2] = '{'{depth: 16}, '{depth: 32}};
|
||||
|
||||
initial begin
|
||||
if (CFG.memory.depth != 8192) $stop;
|
||||
if (CFG.counters != 6) $stop;
|
||||
// Selected at runtime; indexing an array-of-structs inside a constant
|
||||
// expression is a separate, still-unsupported case.
|
||||
if (MEM_ARR[1].depth != 32) $stop;
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
endmodule
|
||||
Reference in New Issue
Block a user