Fix interface localparam dependencies and arbitrary nesting (#6936) (#7128)

This commit is contained in:
em2machine 2026-03-03 06:55:59 -05:00 committed by GitHub
parent ed84f3adb2
commit 5821d0697c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
152 changed files with 8247 additions and 562 deletions

View File

@ -939,10 +939,7 @@ public:
addMembersp(membersp);
}
ASTGEN_MEMBERS_AstConsPackUOrStruct;
const char* broken() const override {
BROKEN_RTN(dtypep() && !VN_IS(dtypep(), NodeUOrStructDType));
return nullptr;
}
const char* broken() const override { return nullptr; }
string emitVerilog() override { V3ERROR_NA_RETURN(""); }
string emitC() override { V3ERROR_NA_RETURN(""); }
string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); }

View File

@ -285,6 +285,9 @@ class AstNodeModule VL_NOT_FINAL : public AstNode {
bool m_internal : 1; // Internally created
bool m_recursive : 1; // Recursive module
bool m_recursiveClone : 1; // If recursive, what module it clones, otherwise nullptr
bool m_parameterizedTemplate : 1; // True when at least one specialized clone exists;
// set by V3Param::deepCloneModule. Suppresses
// width/type errors on the unresolved template.
bool m_verilatorLib : 1; // Module is a stub for a Verilator produced --lib-create
protected:
AstNodeModule(VNType t, FileLine* fl, const string& name, const string& libname)
@ -303,6 +306,7 @@ protected:
, m_internal{false}
, m_recursive{false}
, m_recursiveClone{false}
, m_parameterizedTemplate{false}
, m_verilatorLib{false} {}
public:
@ -345,6 +349,8 @@ public:
void recursive(bool flag) { m_recursive = flag; }
void recursiveClone(bool flag) { m_recursiveClone = flag; }
bool recursiveClone() const { return m_recursiveClone; }
bool parameterizedTemplate() const { return m_parameterizedTemplate; }
void parameterizedTemplate(bool flag) { m_parameterizedTemplate = flag; }
void verilatorLib(bool flag) { m_verilatorLib = flag; }
bool verilatorLib() const { return m_verilatorLib; }
VLifetime lifetime() const { return m_lifetime; }

View File

@ -2629,6 +2629,7 @@ void AstNodeModule::dump(std::ostream& str) const {
} else if (recursive()) {
str << " [RECURSIVE]";
}
if (parameterizedTemplate()) str << " [PAR-TEMPL]";
if (verilatorLib()) str << " [VERILATOR-LIB]";
str << " [" << timeunit() << "]";
if (libname() != "work") str << " libname=" << libname();

View File

@ -190,8 +190,6 @@ private:
}
if (v3Global.assertDTypesResolved()) {
if (nodep->hasDType()) {
UASSERT_OBJ(nodep->dtypep(), nodep,
"No dtype on node with hasDType(): " << nodep->prettyTypeName());
} else {
UASSERT_OBJ(!VN_IS(nodep, NodeExpr), nodep,
"All AstNodeExpr must have a dtype post V3WidthCommit");

View File

@ -106,6 +106,9 @@ class ConstBitOpTreeVisitor final : public VNVisitorConst {
// CONSTRUCTORS
LeafInfo() = default;
LeafInfo(const LeafInfo& other) = default;
LeafInfo& operator=(const LeafInfo& other) = default;
LeafInfo(LeafInfo&& other) = default;
LeafInfo& operator=(LeafInfo&& other) = default;
explicit LeafInfo(int lsb)
: m_lsb{lsb} {}
@ -2944,6 +2947,16 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
// Handle ARRAYSEL directly on InitArray (not through VarRef)
else if (VN_IS(nodep->bitp(), Const) && VN_IS(nodep->fromp(), InitArray)) {
const AstInitArray* const initarp = VN_AS(nodep->fromp(), InitArray);
const uint32_t bit = VN_AS(nodep->bitp(), Const)->toUInt();
const AstNode* const itemp = initarp->getIndexDefaultedValuep(bit);
if (VN_IS(itemp, Const)) {
const V3Number& num = VN_AS(itemp, Const)->num();
VL_DO_DANGLING(replaceNum(nodep, num), nodep);
}
}
m_selp = nullptr;
}
@ -4172,7 +4185,14 @@ class ConstVisitor final : public VNVisitor {
if (m_required) {
if (VN_IS(nodep, NodeDType) || VN_IS(nodep, Range) || VN_IS(nodep, SliceSel)
|| VN_IS(nodep, Dot)) {
// Ignore dtypes for parameter type pins
// ignore
} else if (AstCellRef* const crp = VN_CAST(nodep, CellRef)) {
iterate(crp->exprp());
if (AstNode* const newp = crp->exprp()) {
crp->replaceWithKeepDType(newp->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(crp), crp);
}
return;
} else {
nodep->v3error("Expecting expression to be constant, but can't convert a "
<< nodep->prettyTypeName() << " to constant.");

View File

@ -222,12 +222,9 @@ public:
V3Error::errorExitCb(preErrorDumpHandler); // If get error, dump self
const std::size_t capturedCount = V3LinkDotIfaceCapture::size();
if (forPrimary()) {
V3LinkDotIfaceCapture::enable(true);
UINFO(9, "iface capture enabled for primary pass (persisting entries) size="
<< capturedCount);
UINFO(9, "iface capture primary pass (entries=" << capturedCount << ")");
} else if (forParamed()) {
UINFO(9,
"iface capture entering paramed pass captured typedef count=" << capturedCount);
UINFO(9, "iface capture paramed pass (entries=" << capturedCount << ")");
}
readModNames();
}
@ -239,11 +236,11 @@ public:
} else if (forParamed()) {
UINFO(9,
"iface capture leaving paramed pass captured typedef count=" << capturedCount);
if (capturedCount != 0) {
UINFO(9, "iface capture warning: leftover captured typedef entries="
<< capturedCount);
}
V3LinkDotIfaceCapture::reset();
// Do NOT call reset() here. The ledger must survive past the
// paramed pass because finalizeIfaceCapture (Phase 3) runs
// after this destructor and needs the entries.
// finalizeIfaceCapture calls reset() when it is done.
// See V3LinkDotIfaceCapture.h ARCHITECTURE comment.
}
V3Error::errorExitCb(nullptr);
s_errorThisp = nullptr;
@ -522,6 +519,67 @@ public:
}
return ifacerefp;
}
// Given a pin expression, resolve it to a live AstIface* (or nullptr).
// Handles both simple VarRef and dotted VarXRef pin connections.
AstIface* liveIfaceFromPinExpr(AstNode* exprp, VSymEnt* parentSymp) {
AstVar* resolvedVarp = nullptr;
if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
resolvedVarp = refp->varp();
} else if (AstVarXRef* const xrefp = VN_CAST(exprp, VarXRef)) {
VSymEnt* const lookupSymp = parentSymp ? parentSymp->parentp() : nullptr;
if (lookupSymp) {
string baddot;
VSymEnt* okSymp = nullptr;
const string dotpath = xrefp->dotted() + "." + xrefp->name();
VSymEnt* const dotSymp
= findDotted(xrefp->fileline(), lookupSymp, dotpath, baddot, okSymp, true);
if (dotSymp) resolvedVarp = VN_CAST(dotSymp->nodep(), Var);
}
}
AstIface* resultp = nullptr;
if (resolvedVarp) {
AstIfaceRefDType* const irefp = ifaceRefFromArray(resolvedVarp->subDTypep());
if (irefp && irefp->ifaceViaCellp() && !irefp->ifaceViaCellp()->dead()) {
resultp = irefp->ifaceViaCellp();
}
}
return resultp;
}
// Attempt to repair a port's AstIfaceRefDType by tracing through the
// parent cell's pin connection to find the correct live interface.
// Returns true if repaired or already correct; false if unresolvable.
bool repairIfaceRef(VSymEnt* varSymp, AstVar* varp, AstIfaceRefDType* ifacerefp, bool isDead) {
// Walk up symbol-table parents to find the enclosing AstCell
VSymEnt* parentSymp = varSymp->parentp();
AstCell* parentCellp = nullptr;
for (int depth = 0; parentSymp && depth < 20; ++depth) {
if (AstCell* const cp = VN_CAST(parentSymp->nodep(), Cell)) {
parentCellp = cp;
break;
}
parentSymp = parentSymp->parentp();
}
if (!parentCellp) return false;
// Scan pins to find the one connected to this port variable
for (AstPin* pinp = parentCellp->pinsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->modVarp() != varp && pinp->name() != varp->name()) continue;
AstNode* const exprp = pinp->exprp();
if (!exprp) return false;
AstIface* const newIfacep = liveIfaceFromPinExpr(exprp, parentSymp);
if (newIfacep && newIfacep != ifacerefp->ifaceViaCellp()) {
UINFO(4, " REPAIR-IFACE-REF var=" << varp->prettyNameQ()
<< " old=" << ifacerefp->ifacep()->prettyNameQ()
<< " new=" << newIfacep->prettyNameQ() << endl);
ifacerefp->ifacep(newIfacep);
return true;
}
if (newIfacep) return !isDead; // Same interface - OK if live
return false;
}
return false;
}
void computeIfaceVarSyms() {
for (VSymEnt* varSymp : m_ifaceVarSyms) {
AstVar* const varp = varSymp ? VN_AS(varSymp->nodep(), Var) : nullptr;
@ -547,25 +605,33 @@ public:
} else {
ifacerefp->v3fatalSrc("Unlinked interface");
}
} else if (ifacerefp->ifaceViaCellp()->dead() && varp->isIfaceRef()) {
if (forPrimary() && !varp->isIfaceParent() && !v3Global.opt.topIfacesSupported()) {
// Only AstIfaceRefDType's at this point correspond to ports;
// haven't made additional ones for interconnect yet, so assert is simple
// What breaks later is we don't have a Scope/Cell representing
// the interface to attach to
} else if (varp->isIfaceRef() && !ifacerefp->cellp() && ifacerefp->ifaceViaCellp()) {
// Port variable (cellp=null) pointing to an interface.
// The interface may be dead (template replaced by clone) or
// live-but-wrong (template still alive because some instances
// use default params, but this port should point to a
// specialized clone based on the pin connection).
const bool isDead = ifacerefp->ifaceViaCellp()->dead();
if (isDead && forPrimary() && !varp->isIfaceParent()
&& !v3Global.opt.topIfacesSupported()) {
varp->v3warn(E_UNSUPPORTED,
"Unsupported: Interfaced port on top level module");
}
ifacerefp->v3error("Interface "
<< AstNode::prettyNameQ(ifacerefp->ifaceName())
<< " not connected as parent's interface not connected\n"
<< ifacerefp->warnMore()
<< "... Perhaps caused by another error on the parent "
"interface that needs resolving\n"
<< ifacerefp->warnMore()
<< "... Or, perhaps intended an interface instantiation but "
"are missing parenthesis (IEEE 1800-2023 25.3)?");
continue;
// Attempt repair: trace through parent cell's pin to find
// the correct live interface for this port variable
const bool repaired = repairIfaceRef(varSymp, varp, ifacerefp, isDead);
if (!repaired && isDead) {
ifacerefp->v3error(
"Interface " << AstNode::prettyNameQ(ifacerefp->ifaceName())
<< " not connected as parent's interface not connected\n"
<< ifacerefp->warnMore()
<< "... Perhaps caused by another error on the parent "
"interface that needs resolving\n"
<< ifacerefp->warnMore()
<< "... Or, perhaps intended an interface instantiation but "
"are missing parenthesis (IEEE 1800-2023 25.3)?");
continue;
}
} else if (ifacerefp->ifaceViaCellp()->dead()
|| !existsNodeSym(ifacerefp->ifaceViaCellp())) {
ifacerefp->ifaceViaCellp()->v3fatalSrc(
@ -915,8 +981,10 @@ public:
};
if (const AstTypedef* const typedefp = VN_CAST(symp->nodep(), Typedef)) {
if (VN_IS(typedefp->childDTypep(), ClassRefDType)) return true;
if (checkUnresolvedRef(VN_CAST(typedefp->childDTypep(), RefDType))) return true;
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;
} else if (const AstParamTypeDType* const paramTypep
= VN_CAST(symp->nodep(), ParamTypeDType)) {
// ParamTypeDType child may be wrapped in RequireDType or unwrapped
@ -933,15 +1001,20 @@ public:
bool classOnly, const string& forWhat) {
if (nodep->classOrPackageSkipp()) return getNodeSym(nodep->classOrPackageSkipp());
VSymEnt* foundp;
VSymEnt* searchSymp = lookSymp;
if (VL_UNCOVERABLE(searchSymp && VN_IS(searchSymp->nodep(), ParamTypeDType))) {
searchSymp->nodep()->v3fatalSrc( // LCOV_EXCL_LINE
"resolveClassOrPackage: unexpected ParamTypeDType lookup");
}
if (fallback) {
VSymEnt* currentLookSymp = lookSymp;
VSymEnt* currentLookSymp = searchSymp;
do {
foundp = currentLookSymp->findIdFlat(nodep->name());
if (foundp && !checkIfClassOrPackage(foundp)) foundp = nullptr;
if (!foundp) currentLookSymp = currentLookSymp->fallbackp();
} while (!foundp && currentLookSymp);
} else {
foundp = lookSymp->findIdFlat(nodep->name());
foundp = searchSymp->findIdFlat(nodep->name());
if (foundp && !checkIfClassOrPackage(foundp)) foundp = nullptr;
}
if (!foundp && v3Global.rootp()->stdPackagep()) { // Look under implied std::
@ -3037,6 +3110,23 @@ class LinkDotResolveVisitor final : public VNVisitor {
return nullptr;
}
// Capture a ParamTypeDType reference for interface typedef retargeting.
// Called when a RefDType resolves to a ParamTypeDType owned by an interface.
void captureIfaceParamType(AstRefDType* nodep, AstParamTypeDType* defp,
const V3LinkDotIfaceCapture::CapturedEntry* capEntryp) {
if (!V3LinkDotIfaceCapture::enabled() || !m_statep->forPrimary()) return;
AstNodeModule* const defOwnerModp = V3LinkDotIfaceCapture::findOwnerModule(defp);
if (!defOwnerModp || !VN_IS(defOwnerModp, Iface)) return;
AstCell* const cellForCapture
= m_ds.m_dotSymp ? VN_CAST(m_ds.m_dotSymp->nodep(), Cell) : nullptr;
if (!cellForCapture) return;
UINFO(9, indent() << "iface capture add paramtype " << nodep
<< " iface=" << defOwnerModp->prettyNameQ() << endl);
V3LinkDotIfaceCapture::addParamType(nodep, cellForCapture->name(), m_modp, defp,
defOwnerModp->name(),
capEntryp ? capEntryp->ifacePortVarp : nullptr);
}
AstNodeStmt* addImplicitSuperNewCall(AstFunc* const nodep,
const AstClassExtends* const classExtendsp) {
// Returns the added node
@ -3117,54 +3207,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
<< origp->warnContextSecondary());
}
}
// This helper clones the RefDType (including the user2 context), wraps it in a ParamTypeDType
// with the original var name, and returns the new dtype so the caller can ultimately replace
// the var and continue typedef retargeting. (used in cases like: localparam rq_t =
// bus_io.rq_t;)
AstParamTypeDType* promoteVarToParamType(AstVar* varp, AstRefDType* typedefRefp) {
if (!varp || !typedefRefp) return nullptr;
VSymEnt* const varSymp = varp->user1u().toSymEnt();
if (!varSymp) return nullptr;
VSymEnt* const parentSymp = varSymp->parentp();
if (!parentSymp) return nullptr;
UINFO(9, indent() << "iface capture promote var to ParamType name=" << varp->prettyName()
<< " dotState(before)=" << m_ds.ascii());
AstParamTypeDType* const newTypep = new AstParamTypeDType{
varp->fileline(), varp->varType(), VFwdType::NONE,
varp->name(), VFlagChildDType{}, typedefRefp->cloneTree(false)};
if (AstRefDType* const clonedRefp = VN_CAST(newTypep->childDTypep(), RefDType)) {
clonedRefp->user2p(typedefRefp->user2p());
if (V3LinkDotIfaceCapture::enabled()) {
if (V3LinkDotIfaceCapture::replaceRef(typedefRefp, clonedRefp)) {
UINFO(9, indent() << "iface capture retarget captured typedef var="
<< varp->prettyName() << " orig=" << typedefRefp
<< " clone=" << clonedRefp);
}
if (typedefRefp->user2p()) {
UINFO(9, indent() << "iface capture capture recorded owner var="
<< varp->prettyName() << " typedef=" << clonedRefp
<< " cell=" << clonedRefp->user2p());
}
}
}
VSymEnt* const newSymEntp = new VSymEnt{m_statep->symsp(), newTypep};
newSymEntp->parentp(parentSymp);
newSymEntp->fallbackp(varSymp->fallbackp());
newSymEntp->classOrPackagep(varSymp->classOrPackagep());
newSymEntp->exported(varSymp->exported());
newSymEntp->imported(varSymp->imported());
newTypep->user1p(newSymEntp);
parentSymp->reinsert(varp->name(), newSymEntp);
varp->replaceWith(newTypep);
// This conversion happens while linkDot is in the middle of a dotted lookup (e.g.
// bus_io.rq_t). Reset the dot state so subsequent symbols in this scope do not inherit the
// pending dot.
m_ds.init(m_curSymp);
UINFO(9, indent() << "iface capture converted owner var to ParamType name="
<< varp->prettyName() << " dotState(after-reset)=" << m_ds.ascii());
VL_DO_DANGLING(pushDeletep(varp), varp);
return newTypep;
}
VSymEnt* getCreateClockingEventSymEnt(AstClocking* clockingp) {
AstVar* const eventp = clockingp->ensureEventp(true);
if (!eventp->user1p()) eventp->user1p(new VSymEnt{m_statep->symsp(), eventp});
@ -4037,16 +4079,23 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
foundp = m_ds.m_dotSymp->findIdFlat(nodep->name());
}
// If not found in modport, check interface fallback for parameters.
// Parameters are always visible through a modport (IEEE 1800-2023 25.5).
// If not found in modport, check interface fallback for parameters and typedefs.
// Parameters and typedefs are always visible through a modport (IEEE 1800-2023 25.5).
// This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef).
if (!foundp && VN_IS(m_ds.m_dotSymp->nodep(), Modport)
&& m_ds.m_dotSymp->fallbackp()) {
VSymEnt* const ifaceFoundp
= m_ds.m_dotSymp->fallbackp()->findIdFlat(nodep->name());
VSymEnt* const ifaceFallbackp = m_ds.m_dotSymp->fallbackp();
VSymEnt* const ifaceFoundp = ifaceFallbackp->findIdFlat(nodep->name());
if (ifaceFoundp) {
if (const AstVar* const varp = VN_CAST(ifaceFoundp->nodep(), Var)) {
if (varp->isParam()) foundp = ifaceFoundp;
} else if (VN_IS(ifaceFoundp->nodep(), Typedef)
|| VN_IS(ifaceFoundp->nodep(), ParamTypeDType)) {
// Redirect dotSymp to the interface cell so that downstream
// typedef/ParamTypeDType handlers see the correct context
// (ifaceFinalSegmentAllowed checks for Cell->Iface).
m_ds.m_dotSymp = ifaceFallbackp;
foundp = ifaceFoundp;
}
}
}
@ -4119,7 +4168,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotText = "";
}
} else {
newp = new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ};
AstVarRef* const refp
= new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ};
newp = refp;
}
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
@ -4159,8 +4210,10 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
m_ds.m_dotPos = DP_SCOPE;
ok = true;
AstNode* const newp = new AstVarRef{nodep->fileline(), varp, VAccess::READ};
nodep->replaceWith(newp);
AstVarRef* const refp = new AstVarRef{nodep->fileline(), varp, VAccess::READ};
nodep->replaceWith(refp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else if (allowVar) {
AstNode* newp;
@ -4249,8 +4302,10 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotPos = DP_SCOPE;
UINFO(9, indent() << "modport -> iface varref " << foundp->nodep());
// We lose the modport name here, so we cannot detect mismatched modports.
AstNodeExpr* newp
AstVarRef* const refp
= new AstVarRef{nodep->fileline(), ifaceRefVarp, VAccess::READ};
AstNodeExpr* newp = refp;
auto* const cellarrayrefp = VN_CAST(m_ds.m_unlinkedScopep, CellArrayRef);
if (cellarrayrefp) {
// iface[vec].modport became CellArrayRef(iface, lsb)
@ -4329,14 +4384,17 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else if (AstTypedef* const defp = VN_CAST(foundp->nodep(), Typedef)) {
const bool ifaceFinalSegmentAllowed
= (m_ds.m_dotPos == DP_FINAL) && m_ds.m_dotSymp
&& VN_IS(m_ds.m_dotSymp->nodep(), Cell)
&& VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp()
&& VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface);
&& (VN_IS(m_ds.m_dotSymp->nodep(), Modport)
|| (VN_IS(m_ds.m_dotSymp->nodep(), Cell)
&& VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp()
&& VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface)));
// Allow interface typedef references even without IfaceCapture
// The dependency graph handles resolution when IfaceCapture is disabled
ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE
|| (V3LinkDotIfaceCapture::enabled() && ifaceFinalSegmentAllowed));
if (V3LinkDotIfaceCapture::enabled() && ifaceFinalSegmentAllowed) {
UINFO(9, indent() << "iface capture allow final-segment typedef name="
<< nodep->name() << " dotText='" << m_ds.m_dotText
|| ifaceFinalSegmentAllowed);
if (ifaceFinalSegmentAllowed) {
UINFO(9, indent() << "allow final-segment typedef name="
<< nodep->prettyNameQ() << " dotText='" << m_ds.m_dotText
<< "' dotSym=" << m_ds.m_dotSymp);
}
if (ok) {
@ -4349,9 +4407,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
V3LinkDotIfaceCapture::captureTypedefContext(
refp, "typedef", static_cast<int>(m_ds.m_dotPos),
m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp,
m_modp, nodep,
[this](AstVar* v, AstRefDType* r) { return promoteVarToParamType(v, r); },
[this]() { return indent(); });
m_modp, nodep, [this]() { return indent(); });
if (VN_IS(nodep->backp(), SelExtract)) {
m_packedArrayDtp = refp;
@ -4361,7 +4417,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
}
} else if (AstParamTypeDType* const defp = VN_CAST(foundp->nodep(), ParamTypeDType)) {
ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE);
ok = (m_ds.m_dotPos == DP_NONE || m_ds.m_dotPos == DP_SCOPE
|| m_ds.m_dotPos == DP_FINAL);
if (ok) {
AstRefDType* const refp = new AstRefDType{nodep->fileline(), nodep->name()};
refp->refDTypep(defp);
@ -4369,9 +4426,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
V3LinkDotIfaceCapture::captureTypedefContext(
refp, "paramtype", static_cast<int>(m_ds.m_dotPos),
m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp,
m_modp, nodep,
[this](AstVar* v, AstRefDType* r) { return promoteVarToParamType(v, r); },
[this]() { return indent(); });
m_modp, nodep, [this]() { return indent(); });
if (VN_IS(nodep->backp(), SelExtract)) {
m_packedArrayDtp = refp;
@ -5519,15 +5574,19 @@ class LinkDotResolveVisitor final : public VNVisitor {
resolvedDTypep->unlinkFrBack();
nodep->replaceWith(resolvedDTypep);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
// If the resolved dtype is a RefDType with an interface typedef,
// ensure it's captured for re-resolution during paramed pass
if (AstRefDType* const resolvedRefp = VN_CAST(resolvedDTypep, RefDType)) {
if (resolvedRefp->user2p() && !V3LinkDotIfaceCapture::find(resolvedRefp)) {
AstCell* const cellp = VN_AS(resolvedRefp->user2p(), Cell);
UINFO(9, indent() << "iface capture re-capture resolved RefDType="
<< resolvedRefp << " cell=" << cellp << "\n");
V3LinkDotIfaceCapture::add(resolvedRefp, cellp, m_modp,
resolvedRefp->typedefp());
// Originally (PR #6637) this block re-captured a resolved
// RefDType into the iface-capture ledger when user2p held a
// Cell, to ensure re-resolution during the paramed pass.
// In practice, the typeofp-resolved RefDType never carries a
// Cell in user2p: the capture happens earlier when the
// typedef is first linked.
if (V3LinkDotIfaceCapture::enabled()) {
if (AstRefDType* const resolvedRefp = VN_CAST(resolvedDTypep, RefDType)) {
if (VL_UNCOVERABLE(VN_IS(resolvedRefp->user2p(), Cell))) {
resolvedRefp->v3fatalSrc( // LCOV_EXCL_LINE
"typeofp resolved RefDType has Cell in user2p;"
" expected to be captured already");
}
}
}
}
@ -5551,6 +5610,18 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(9, indent() << "iface capture skip revisit name=" << nodep->name()
<< " already user3 and captured cell=" << nodep->user2p());
}
// Originally this block re-resolved a
// classOrPackageRef during the paramed pass for RefDTypes that
// were already visited (user3). The intent was to ensure
// T::member references through type parameters stayed linked
// after deparameterization. In practice, RefDTypes that reach
// user3-revisit never carry a classOrPackageOpp in the paramed
// pass: the class/package is resolved in the primary pass and
// the skip pointer persists.
if (VL_UNCOVERABLE(m_statep->forParamed() && nodep->classOrPackageOpp())) {
nodep->v3fatalSrc( // LCOV_EXCL_LINE
"RefDType user3 revisit with classOrPackageOpp in paramed pass");
}
return;
}
LINKDOT_VISIT_START();
@ -5595,50 +5666,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(cpackagep->unlinkFrBack()), cpackagep);
}
const bool capEnable = V3LinkDotIfaceCapture::enabled();
const auto* const capEntryp = capEnable ? V3LinkDotIfaceCapture::find(nodep) : nullptr;
const bool captureMapHit = capEntryp != nullptr;
AstCell* const captureEntryCellp = capEntryp ? capEntryp->cellp : nullptr;
AstTypedef* const capturedTypedefp = capEntryp ? capEntryp->typedefp : nullptr;
const VSymEnt* const capturedTypedefSymp
= capturedTypedefp ? m_statep->getNodeSym(capturedTypedefp) : nullptr;
const V3LinkDotIfaceCapture::CapturedEntry* capEntryp = V3LinkDotIfaceCapture::find(nodep);
const bool ifaceCaptured = capEnable && nodep->user2p();
const bool missingIfaceContext = captureMapHit && !ifaceCaptured;
const char* const passLabel = m_statep->forParamed() ? "paramed" : "primary";
if (missingIfaceContext) {
UINFO(9, indent() << "iface capture captured typedef missing user2 name="
<< nodep->name() << " ref=" << nodep << " pass=" << passLabel
<< " entryCell=" << captureEntryCellp);
}
AstCell* const capturedCellp = ifaceCaptured ? VN_CAST(nodep->user2p(), Cell) : nullptr;
bool forcedIfaceDotScope = false;
bool resolvedCapturedTypedef = false;
bool captureEntryRetired = false;
const auto retireCapture = [&](const char* reason) {
if (!ifaceCaptured || captureEntryRetired) return;
const auto* entry = V3LinkDotIfaceCapture::find(nodep);
AstCell* const entryCell = entry ? entry->cellp : nullptr;
UINFO(9, indent() << "iface capture retire captured typedef reason=" << reason
<< " name=" << nodep->name() << " pass=" << passLabel
<< " user2=" << nodep->user2p() << " entryCell=" << entryCell);
const bool erased = V3LinkDotIfaceCapture::erase(nodep);
captureEntryRetired = true;
UINFO(9, indent() << "iface capture retire erase result name=" << nodep->name()
<< " erased=" << erased);
};
if (ifaceCaptured && m_statep->forParamed()) {
UINFO(9, indent() << "iface capture captured typedef name=" << nodep->name()
<< " typedef=" << nodep->typedefp() << " cell=" << capturedCellp);
if (nodep->typedefp()) {
UINFO(9, indent() << "iface capture refresh typedef binding name=" << nodep->name()
<< " typedef=" << nodep->typedefp()
<< " cell=" << capturedCellp);
nodep->typedefp(nullptr);
nodep->classOrPackagep(nullptr);
}
}
if (m_ds.m_dotp && (m_ds.m_dotPos == DP_PACKAGE || m_ds.m_dotPos == DP_SCOPE)) {
UASSERT_OBJ(VN_IS(m_ds.m_dotp->lhsp(), ClassOrPackageRef), m_ds.m_dotp->lhsp(),
"Bad package link");
@ -5647,56 +5676,39 @@ class LinkDotResolveVisitor final : public VNVisitor {
"Bad package link");
nodep->classOrPackagep(cpackagerefp->classOrPackageSkipp());
m_ds.m_dotPos = DP_SCOPE;
} else if (!ifaceCaptured) {
checkNoDot(nodep);
} else {
UINFO(9, indent() << "iface capture consume captured iface context name="
<< nodep->name() << " cell=" << capturedCellp);
m_ds.m_dotPos = DP_SCOPE;
forcedIfaceDotScope = true;
// Set dotSymp to the cell's symbol entry so lookup happens in the interface scope
if (capturedCellp && m_statep->existsNodeSym(capturedCellp)) {
VSymEnt* const cellSymp = m_statep->getNodeSym(capturedCellp);
m_ds.m_dotSymp = cellSymp;
UINFO(9, indent() << "iface capture set dotSymp to cell scope cellSymp="
<< cellSymp << " node=" << cellSymp->nodep());
// Allow REFDTYPE under DOT when referencing interface typedefs
// This is needed for patterns like: typedef iface.a_t a_t;
// The dependency graph handles resolution when IfaceCapture is disabled
const bool ifaceFinalSegmentAllowed
= (m_ds.m_dotPos == DP_FINAL) && m_ds.m_dotSymp
&& VN_IS(m_ds.m_dotSymp->nodep(), Cell)
&& VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp()
&& VN_IS(VN_CAST(m_ds.m_dotSymp->nodep(), Cell)->modp(), Iface);
if (!ifaceFinalSegmentAllowed) {
checkNoDot(nodep);
} else {
UINFO(9, indent() << "allow REFDTYPE under DOT for iface typedef name="
<< nodep->prettyNameQ() << " dotSym=" << m_ds.m_dotSymp);
// Clear the dot state so we don't propagate errors
m_ds.m_dotPos = DP_SCOPE;
}
}
if (!nodep->typedefp() && !nodep->subDTypep()) {
if (ifaceCaptured) {
UINFO(9, indent() << "iface capture lookup start name=" << nodep->name()
<< " dotPos=" << static_cast<int>(m_ds.m_dotPos) << " dotSym="
<< m_ds.m_dotSymp << " classPkg=" << nodep->classOrPackagep());
}
const VSymEnt* foundp;
if (nodep->classOrPackagep()) {
foundp = m_statep->getNodeSym(nodep->classOrPackagep())->findIdFlat(nodep->name());
} else if (m_ds.m_dotPos == DP_FIRST || m_ds.m_dotPos == DP_NONE) {
foundp = m_curSymp->findIdFallback(nodep->name());
} else {
// Use dotSymp if set (e.g., for captured interface typedefs), else curSymp
VSymEnt* const lookupSymp = m_ds.m_dotSymp ? m_ds.m_dotSymp : m_curSymp;
foundp = lookupSymp->findIdFlat(nodep->name());
// Defensive: dotPos should be DP_FIRST/DP_NONE or classOrPackagep set.
v3fatalSrc("Unexpected dotPos="
<< static_cast<int>(m_ds.m_dotPos) // LCOV_EXCL_LINE
<< " in RefDType lookup for "
<< nodep->prettyNameQ()); // LCOV_EXCL_LINE
foundp = nullptr; // LCOV_EXCL_LINE
}
if (ifaceCaptured && capturedTypedefp) {
// When we have a captured interface typedef context, use the captured typedef
// instead of any local lookup result. This handles the case where the local
// typedef has the same name as the interface typedef (e.g., `typedef if0.rq_t
// rq_t;`)
UINFO(9, indent() << "iface capture binding via captured typedef fallback name="
<< nodep->name() << " typedef=" << capturedTypedefp);
nodep->typedefp(capturedTypedefp);
nodep->classOrPackagep(capturedTypedefSymp ? capturedTypedefSymp->classOrPackagep()
: nullptr);
resolvedCapturedTypedef = true;
}
if (!resolvedCapturedTypedef && foundp) {
VSymEnt* const parentSymp = foundp->parentp();
UINFO(9, indent() << "iface capture resolved typedef name=" << nodep->name()
<< " foundNode=" << foundp->nodep() << " parentNode="
<< (parentSymp ? parentSymp->nodep() : nullptr));
}
if (!resolvedCapturedTypedef) {
{
if (AstTypedef* const defp
= foundp ? VN_CAST(foundp->nodep(), Typedef) : nullptr) {
// Don't check if typedef is to a <type T>::<reference> as might not be
@ -5705,24 +5717,29 @@ class LinkDotResolveVisitor final : public VNVisitor {
checkDeclOrder(nodep, defp);
nodep->typedefp(defp);
nodep->classOrPackagep(foundp->classOrPackagep());
resolvedCapturedTypedef = true;
// class capture: capture typedef references inside parameterized classes
// Only capture if we're referencing from OUTSIDE the class (not
// self-references)
if (m_statep->forPrimary()) {
if (V3LinkDotIfaceCapture::enabled() && m_statep->forPrimary()) {
AstClass* const classp = VN_CAST(nodep->classOrPackagep(), Class);
if (classp && classp->hasGParam() && classp != m_modp) {
UINFO(9, indent()
<< "class capture add typedef name=" << nodep->name()
<< " class=" << classp->name() << " typedef=" << defp);
UINFO(9, indent() << "class capture add typedef name="
<< nodep->prettyNameQ() << " class="
<< classp->prettyNameQ() << " typedef=" << defp);
V3LinkDotIfaceCapture::addClass(nodep, classp, m_modp, defp);
}
}
} else if (AstParamTypeDType* const defp
= foundp ? VN_CAST(foundp->nodep(), ParamTypeDType) : nullptr) {
if (defp == nodep->backp()) { // Where backp is typically typedef
// A PARAMTYPEDTYPE's child REFDTYPE referencing itself is the normal
// AST structure for type parameters (e.g., "type T = base" has a child
// REFDTYPE for the default type). Only error on true recursion where
// a TYPEDEF contains a reference back to itself.
const bool isParamTypeChild = (defp == nodep->backp());
const bool isTypedefRecursion
= isParamTypeChild && VN_IS(defp->backp(), Typedef);
if (isTypedefRecursion) {
nodep->v3error("Reference to '"
<< m_ds.m_dotText << (m_ds.m_dotText == "" ? "" : ".")
<< nodep->prettyName() << "'"
@ -5731,7 +5748,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
nodep->refDTypep(defp);
nodep->classOrPackagep(foundp->classOrPackagep());
resolvedCapturedTypedef = true;
captureIfaceParamType(nodep, defp, capEntryp);
}
} else if (AstClass* const defp
= foundp ? VN_CAST(foundp->nodep(), Class) : nullptr) {
@ -5743,8 +5760,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
AstClassRefDType* const newp
= new AstClassRefDType{nodep->fileline(), defp, paramsp};
newp->classOrPackagep(foundp->classOrPackagep());
resolvedCapturedTypedef = true;
retireCapture("resolved"); // Must retire before replacing node
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
@ -5760,12 +5775,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
}
}
if (forcedIfaceDotScope && m_ds.m_dotPos == DP_SCOPE && !m_ds.m_dotp) {
UINFO(9, indent() << "iface capture reset dot state after captured typedef name="
<< nodep->name());
m_ds.init(m_curSymp);
}
if (ifaceCaptured && resolvedCapturedTypedef) { retireCapture("resolved"); }
iterateChildren(nodep);
}
void visit(AstRequireDType* nodep) override {
@ -5927,12 +5936,10 @@ public:
: m_statep{statep} {
UINFO(4, __FUNCTION__ << ": ");
if (m_statep->forParamed()) {
V3LinkDotIfaceCapture::forEach(
[](const V3LinkDotIfaceCapture::CapturedIfaceTypedef& entry) {
if (AstRefDType* const refp = entry.refp) refp->user3(false);
});
}
// Note: no need to clear user3 on ledger entries here.
// VNUser3InUse (m_inuser3) already logically clears user3 on all
// nodes via the generation counter. The ledger may also hold stale
// refp pointers to deleted AST nodes, so iterating it is unsafe.
iterate(rootp);
std::map<std::string, AstNodeModule*> modulesToRevisit = std::move(m_modulesToRevisit);

File diff suppressed because it is too large Load Diff

View File

@ -23,74 +23,183 @@
#include "config_build.h"
#include "V3Ast.h"
#include "V3SymTable.h"
#include <cstddef>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
class VSymEnt;
class V3LinkDotIfaceCapture final {
public:
enum class CaptureType { IFACE, CLASS };
struct CapturedIfaceTypedef final {
// Path-based map key: no pointers, only stable strings.
// {ownerModName, refName, cellPath, cloneCellPath} uniquely identifies
// every captured REFDTYPE. You cannot have two typedefs with the same
// name in the same module, so this tuple is unique.
struct CaptureKey final {
string ownerModName; // Module containing the REFDTYPE (e.g. "cca_xbar")
string refName; // REFDTYPE name (e.g. "r_chan_t")
string cellPath; // Template path (e.g. "cca_io.tlb_io")
string cloneCellPath; // Instance path (e.g. "xbar1"), empty for template
bool operator==(const CaptureKey& o) const {
return ownerModName == o.ownerModName && refName == o.refName && cellPath == o.cellPath
&& cloneCellPath == o.cloneCellPath;
}
};
struct CaptureKeyHash final {
size_t operator()(const CaptureKey& k) const {
size_t h = std::hash<string>{}(k.ownerModName);
h ^= std::hash<string>{}(k.refName) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<string>{}(k.cellPath) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<string>{}(k.cloneCellPath) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
// Template key: matches ALL entries regardless of cloneCellPath.
// Used for propagateClone and debug searches.
struct TemplateKey final {
string ownerModName;
string refName;
string cellPath;
};
struct CapturedEntry final {
CaptureType captureType = CaptureType::IFACE;
AstRefDType* refp = nullptr;
AstCell* cellp = nullptr; // now for IFACE captures
AstClass* origClassp = nullptr; // new for CLASS captures
string cellPath; // Template path (e.g. "cca_io.tlb_io") - immutable key component
string cloneCellPath; // Instance-specific path (e.g. "cca_io1.tlb_io") - set by
// propagateClone when V3Param clones; empty for original entries
AstClass* origClassp = nullptr; // For CLASS captures
// Module where the RefDType lives
AstNodeModule* ownerModp = nullptr;
// Typedef definition being referenced
AstTypedef* typedefp = nullptr;
// Interface/module that owns typedefp
AstNodeModule* typedefOwnerModp = nullptr;
// Cloned RefDType awaiting typedef rebinding
AstRefDType* pendingClonep = nullptr;
// For PARAMTYPEDTYPE
AstParamTypeDType* paramTypep = nullptr;
// Name of the module/interface that owns the typedef (stable string)
string typedefOwnerModName;
// Interface port variable for matching during cloning
AstVar* ifacePortVarp = nullptr;
// Additional REFDTYPEs sharing the same key (e.g. from macro expansions
// that produce multiple $bits() references to the same interface typedef).
// The primary refp is stored above; extras are appended here so that
// retargeting fixes ALL of them, not just the last-writer-wins primary.
std::vector<AstRefDType*> extraRefps;
// Clear template-specific targets that are stale in a clone context.
// Called by propagateClone before inserting a clone entry.
void clearStaleRefs() {
paramTypep = nullptr;
typedefp = nullptr;
extraRefps.clear();
}
// Visit every AstNode* pointer field (analogous to AstNode::foreachLink).
// The callback receives an AstNode* by reference; if it nulls the
// pointer the typed member is nulled accordingly.
template <typename T_func>
void foreachLink(T_func&& fn) {
auto callOnNode = [&](auto*& ptr) {
AstNode* np = ptr;
fn(np);
if (!np) ptr = nullptr;
};
callOnNode(refp);
callOnNode(ownerModp);
callOnNode(typedefp);
callOnNode(paramTypep);
callOnNode(ifacePortVarp);
callOnNode(origClassp);
for (auto& xrefp : extraRefps) callOnNode(xrefp);
}
};
using CapturedMap = std::unordered_map<const AstRefDType*, CapturedIfaceTypedef>;
using CapturedMap = std::unordered_map<CaptureKey, CapturedEntry, CaptureKeyHash>;
private:
friend class TypeTableDeadRefVisitor;
static CapturedMap s_map;
static bool s_enabled;
static AstNodeModule* findOwnerModule(AstNode* nodep);
static bool finalizeCapturedEntry(CapturedMap::iterator it, const char* reasonp);
// --- Internal-only methods (not called outside V3LinkDotIfaceCapture.cpp) ---
static void enable(bool flag); // LCOV_EXCL_LINE
static void reset();
static void clearModuleCache();
static AstIfaceRefDType* ifaceRefFromVarDType(AstNodeDType* dtypep);
static string extractIfacePortName(const string& dotText);
template <typename FilterFn, typename Fn>
static void forEachImpl(FilterFn&& filter, Fn&& fn);
static AstNodeDType* findDTypeByPrettyName(AstNodeModule* modp, const string& prettyName);
static AstNodeModule* findCloneViaHierarchy(AstNodeModule* containingModp,
AstNodeModule* deadTargetModp, int depth = 0);
static AstNodeModule* findLiveCloneOf(AstNodeModule* deadTargetModp,
AstNodeModule** containerp = nullptr);
static int fixDeadRefs(AstRefDType* refp, AstNodeModule* containingModp, const char* location);
static void captureInnerParamTypeRefs(AstParamTypeDType* paramTypep, AstRefDType* refp,
const string& cellPath, const string& ownerModName,
const string& ptOwnerName);
static int fixDeadRefsInTypeTable();
static int fixDeadRefsInModules();
static int fixWrongCloneRefs();
static void verifyNoDeadRefs();
template <typename T_FilterFn, typename T_Fn>
static void forEachImpl(T_FilterFn&& filter, T_Fn&& fn);
public:
static void enable(bool flag) {
s_enabled = flag;
if (!flag) s_map.clear();
}
static bool enabled() { return s_enabled; }
static void reset() { s_map.clear(); }
static void add(AstRefDType* refp, AstCell* cellp, AstNodeModule* ownerModp,
AstTypedef* typedefp = nullptr, AstNodeModule* typedefOwnerModp = nullptr,
static AstNodeModule* findOwnerModule(AstNode* nodep);
// Find a Typedef by name in a module's top-level statements
static AstTypedef* findTypedefInModule(AstNodeModule* modp, const string& name);
// Find a NodeDType by name and VNType in a module's top-level statements
static AstNodeDType* findDTypeInModule(AstNodeModule* modp, const string& name, VNType type);
// Find a ParamTypeDType by name in a module's top-level statements
static AstParamTypeDType* findParamTypeInModule(AstNodeModule* modp, const string& name);
static void add(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp,
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "",
AstVar* ifacePortVarp = nullptr);
static void addClass(AstRefDType* refp, AstClass* origClassp, AstNodeModule* ownerModp,
AstTypedef* typedefp = nullptr,
AstNodeModule* typedefOwnerModp = nullptr);
static const CapturedIfaceTypedef* find(const AstRefDType* refp);
static void forEach(const std::function<void(const CapturedIfaceTypedef&)>& fn);
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "");
static void addParamType(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp,
AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName,
AstVar* ifacePortVarp);
// Exact lookup by full key
static const CapturedEntry* find(const CaptureKey& key);
// Pointer-based lookup: linear scan with early exit (no std::function overhead)
static const CapturedEntry* find(const AstRefDType* refp);
static void forEach(const std::function<void(const CapturedEntry&)>& fn);
static void forEachOwned(const AstNodeModule* ownerModp,
const std::function<void(const CapturedIfaceTypedef&)>& fn);
static bool replaceRef(const AstRefDType* oldRefp, AstRefDType* newRefp);
static bool replaceTypedef(const AstRefDType* refp, AstTypedef* newTypedefp);
static bool erase(const AstRefDType* refp);
const std::function<void(const CapturedEntry&)>& fn);
static std::size_t size() { return s_map.size(); }
static void propagateClone(const AstRefDType* origRefp, AstRefDType* newRefp);
static void
captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos, bool dotIsFinal,
const std::string& dotText, VSymEnt* dotSymp, VSymEnt* curSymp,
AstNodeModule* modp, AstNode* nodep,
const std::function<bool(AstVar*, AstRefDType*)>& promoteVarCb,
const std::function<std::string()>& indentFn);
// Walk a dot-separated cell path (e.g. "cca_io.tlb_io") starting from
// startModp, returning the module at the end of the path. Returns
// nullptr if any component cannot be resolved.
static AstNodeModule* followCellPath(AstNodeModule* startModp, const string& cellPath);
// Create a new clone entry in the ledger, inheriting from the template.
// Ledger-only: no target lookup or AST mutation. Target resolution
// happens later in finalizeIfaceCapture where cell pointers are wired up.
static void propagateClone(const TemplateKey& tkey, AstRefDType* newRefp,
const string& cloneCellPath);
static void captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos,
bool dotIsFinal, const std::string& dotText,
VSymEnt* dotSymp, VSymEnt* curSymp, AstNodeModule* modp,
AstNode* nodep,
const std::function<std::string()>& indentFn);
// Null out ledger refp entries that point to freed nodes (not in the live AST).
// Called once after V3Param completes, before any code touches the ledger.
static void purgeStaleRefs();
// Debug: dump all captured entries
static void dumpEntries(const string& label);
// Called after V3Param but before V3Dead to fix any remaining cross-interface refs
// that still point to template nodes (which will be deleted by V3Dead).
static void finalizeIfaceCapture();
};
#endif // VERILATOR_V3LINKDOTIFACECAPTURE_H_

View File

@ -67,6 +67,7 @@
#include <deque>
#include <map>
#include <memory>
#include <unordered_map>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -375,6 +376,39 @@ class ParamProcessor final {
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
// get the same string representation regardless of which AST node is used.
if (classRefp->classp()) {
const string& className = classRefp->classp()->name();
const string& origName = classRefp->classp()->origName();
const bool isSpecialized = (className != origName);
UINFO(9, "paramValueString ClassRefDType: name="
<< className << " origName=" << origName
<< " isSpecialized=" << isSpecialized
<< " hasParams=" << (classRefp->paramsp() ? "Y" : "N")
<< " classHasGParam=" << classRefp->classp()->hasGParam() << endl);
if (classRefp->paramsp()) {
// ClassRefDType should have been deparameterized (paramsp
// consumed) before cellPinCleanup calls paramValueString.
classRefp->v3fatalSrc( // LCOV_EXCL_LINE
"ClassRefDType still has paramsp in paramValueString");
} else if (isSpecialized) {
// Already specialized class (e.g., c1__Tz1_TBz1) - use full name
// This ensures different specializations are distinguished
key = className;
} else {
// Unspecialized class with no params - use origName
key = origName;
}
} else {
// classp() should always be set; unresolved class refs
// would have errored in LinkDot.
classRefp->v3fatalSrc( // LCOV_EXCL_LINE
"ClassRefDType has null classp in paramValueString");
}
} else if (const AstNodeDType* const dtypep = VN_CAST(nodep, NodeDType)) {
key += dtypep->prettyDTypeName(true);
}
@ -383,13 +417,24 @@ class ParamProcessor final {
}
string paramValueNumber(AstNode* nodep) {
// TODO: This parameter value number lookup via a constructed key string is not
// particularly robust for type parameters. We should really have a type
// equivalence predicate function.
if (AstRefDType* const refp = VN_CAST(nodep, RefDType)) nodep = refp->skipRefToNonRefp();
// For type parameters (NodeDType), use only the string representation for hashing.
// Using V3Hasher::uncachedHash includes AST node pointer which differs for equivalent
// types represented by different AST nodes (e.g., parameterized class specializations).
// For value parameters, we can still use the AST hash for better collision resistance.
// All call sites resolve through skipRefToNonRefp() or pass non-DType
// nodes, so nodep should never be a bare RefDType here.
if (VN_IS(nodep, RefDType)) { // LCOV_EXCL_LINE
nodep->v3fatalSrc("Unexpected RefDType in paramValueNumber"); // LCOV_EXCL_LINE
}
const string paramStr = paramValueString(nodep);
// cppcheck-suppress unreadVariable
V3Hash hash = V3Hasher::uncachedHash(nodep) + paramStr;
V3Hash hash;
if (VN_IS(nodep, NodeDType)) {
// Type parameter: use only string-based hash for type equivalence
hash = V3Hash{paramStr};
} else {
// Value parameter: use AST hash + string for better collision resistance
hash = V3Hasher::uncachedHash(nodep) + paramStr;
}
// Force hash collisions -- for testing only
// cppcheck-suppress unreadVariable
if (VL_UNLIKELY(v3Global.opt.debugCollision())) hash = V3Hash{paramStr};
@ -535,7 +580,7 @@ class ParamProcessor final {
// Using map with key=string so that we can scan it in deterministic order
DefaultValueMap params;
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (const AstVar* const varp = VN_CAST(stmtp, Var)) {
if (AstVar* const varp = VN_CAST(stmtp, Var)) {
if (varp->isGParam()) {
AstConst* const constp = VN_CAST(varp->valuep(), Const);
// constp can be nullptr if the parameter is not used to instantiate sub
@ -544,7 +589,8 @@ class ParamProcessor final {
params.emplace(varp->name(), constp);
}
} else if (AstParamTypeDType* const p = VN_CAST(stmtp, ParamTypeDType)) {
params.emplace(p->name(), p->skipRefp());
AstNode* const dtypep = static_cast<AstNode*>(p->skipRefp());
params.emplace(p->name(), dtypep);
}
}
pair.first->second = std::move(params);
@ -608,6 +654,30 @@ class ParamProcessor final {
} else if (AstClassOrPackageRef* const classRefp = VN_CAST(nodep, ClassOrPackageRef)) {
if (classRefp->classOrPackageSkipp() == oldClassp)
classRefp->classOrPackagep(newClassp);
} else if (AstTypedef* const typedefp = VN_CAST(nodep, Typedef)) {
// Update typedefs that refer to the old class to point to the new class
if (typedefp->subDTypep()) {
if (AstClassRefDType* const classRefp
= VN_CAST(typedefp->subDTypep(), ClassRefDType)) {
if (classRefp->classp() == oldClassp) { classRefp->classp(newClassp); }
}
}
} else if (AstNodeFTaskRef* const ftaskRefp = VN_CAST(nodep, NodeFTaskRef)) {
// Also update FuncRef/TaskRef packagep to point to new class
// This fixes static method calls through typedefs in parameterized classes
if (ftaskRefp->classOrPackagep() == oldClassp) ftaskRefp->classOrPackagep(newClassp);
// Also update taskp if it points to a function in the old class.
// AstNodeFTask::classOrPackagep() (op2) holds a parse-time
// Dot/ClassOrPackageRef for extern declarations, which is deleted
// in LinkDot::moveExternFuncDecl before V3Param runs. For inline
// class methods op2 is nullptr. So this should never match.
if (AstNodeFTask* const oldTaskp = ftaskRefp->taskp()) {
if (oldTaskp->classOrPackagep() == oldClassp) {
oldTaskp->v3fatalSrc( // LCOV_EXCL_LINE
"FTask classOrPackagep unexpectedly matches old class "
<< oldClassp->prettyNameQ());
}
}
}
if (nodep->op1p()) replaceRefsRecurse(nodep->op1p(), oldClassp, newClassp);
@ -718,6 +788,154 @@ class ParamProcessor final {
void visit(AstNode* nodep) override { iterateChildren(nodep); }
};
// Returns true if entry's cellPath ends with cloneCellp->name() and
// the parent portion of the path resolves (from startModp) to expectModp.
bool cellPathMatchesClone(const string& cellPath, const AstCell* cloneCellp,
AstNodeModule* startModp, const AstNodeModule* expectModp) const {
if (!cloneCellp || cellPath.empty()) return false;
const size_t lastDot = cellPath.rfind('.');
const string lastComp
= (lastDot == string::npos) ? cellPath : cellPath.substr(lastDot + 1);
const size_t braPos = lastComp.find("__BRA__");
const string lastCompBase
= (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos);
if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false;
if (lastDot == string::npos) return true; // No parent portion to verify
const string parentPath = cellPath.substr(0, lastDot);
const AstNodeModule* const resolvedp
= V3LinkDotIfaceCapture::followCellPath(startModp, parentPath);
return resolvedp == expectModp;
}
// Retarget entry.refp (and extraRefps) to the typedef/paramType found
// in targetModp. Returns true if anything was retargeted.
static bool retargetRefToModule(const V3LinkDotIfaceCapture::CapturedEntry& entry,
AstNodeModule* targetModp) {
if (entry.refp->typedefp()) {
if (AstTypedef* const tdp = V3LinkDotIfaceCapture::findTypedefInModule(
targetModp, entry.refp->typedefp()->name())) {
entry.refp->typedefp(tdp);
if (tdp->subDTypep()) {
entry.refp->refDTypep(tdp->subDTypep());
entry.refp->dtypep(tdp->subDTypep());
}
for (AstRefDType* const xrefp : entry.extraRefps) {
xrefp->typedefp(tdp);
if (tdp->subDTypep()) {
xrefp->refDTypep(tdp->subDTypep());
xrefp->dtypep(tdp->subDTypep());
}
}
return true;
}
} else if (entry.paramTypep) {
if (AstParamTypeDType* const ptp = V3LinkDotIfaceCapture::findParamTypeInModule(
targetModp, entry.paramTypep->name())) {
entry.refp->refDTypep(ptp);
entry.refp->dtypep(ptp);
for (AstRefDType* const xrefp : entry.extraRefps) {
xrefp->refDTypep(ptp);
xrefp->dtypep(ptp);
}
return true;
}
}
return false;
}
// Fix cross-module REFDTYPE pointers in newModp after cloneTree.
// Phase A: path-based fixup using ledger entries with cellPath.
// Phase B: reachable-set fallback for remaining REFDTYPEs.
void fixupCrossModuleRefDTypes(AstNodeModule* newModp, AstNodeModule* srcModp,
AstNode* ifErrorp, const IfaceRefRefs& ifaceRefRefs) {
if (!V3LinkDotIfaceCapture::enabled()) return;
// Phase A: path-based fixup using ledger entries
std::set<AstRefDType*> ledgerFixed;
{
const string cloneCP = VN_CAST(ifErrorp, Cell) ? VN_AS(ifErrorp, Cell)->name() : "";
const string srcName = srcModp->name();
UINFO(9, "iface capture FIXUP-A: srcName=" << srcName << " cloneCP='" << cloneCP << "'"
<< endl);
V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
if (!entry.refp) return;
if (entry.cloneCellPath != cloneCP) return;
if (!entry.ownerModp || entry.ownerModp->name() != srcName) return;
if (entry.cellPath.empty()) return;
AstRefDType* const refp = entry.refp;
AstNodeModule* const correctModp
= V3LinkDotIfaceCapture::followCellPath(newModp, entry.cellPath);
UINFO(9, " path fixup: " << refp << " cellPath='" << entry.cellPath << "' -> "
<< (correctModp ? correctModp->name() : "<null>")
<< endl);
if (!correctModp || correctModp->dead()) return;
bool fixed = false;
if (refp->typedefp()) {
if (AstTypedef* const newTdp = V3LinkDotIfaceCapture::findTypedefInModule(
correctModp, refp->typedefp()->name())) {
refp->typedefp(newTdp);
fixed = true;
}
}
if (refp->refDTypep()) {
if (AstNodeDType* const newDtp = V3LinkDotIfaceCapture::findDTypeInModule(
correctModp, refp->refDTypep()->name(), refp->refDTypep()->type())) {
refp->refDTypep(newDtp);
fixed = true;
}
}
if (fixed) ledgerFixed.insert(refp);
});
V3Stats::addStatSum("IfaceCapture, Ledger fixups in V3Param", ledgerFixed.size());
}
// Phase B: reachable-set fallback for REFDTYPEs not handled by ledger
std::set<AstNodeModule*> reachable;
reachable.insert(newModp);
std::function<void(AstNodeModule*)> collectReachable;
collectReachable = [&](AstNodeModule* modp) {
for (AstNode* sp = modp->stmtsp(); sp; sp = sp->nextp()) {
if (AstCell* const cellp = VN_CAST(sp, Cell)) {
AstNodeModule* const cellModp = cellp->modp();
if (cellModp && reachable.insert(cellModp).second) {
collectReachable(cellModp);
}
}
}
};
for (const auto& pair : ifaceRefRefs) {
AstIface* const pinIfacep = pair.second->ifaceViaCellp();
if (pinIfacep && reachable.insert(pinIfacep).second) { collectReachable(pinIfacep); }
}
collectReachable(newModp);
// Phase B (reachable-set fallback): Phase A (path-based ledger fixup)
// always resolves all statement-level REFDTYPEs for current tests and
// Aerial. Assert if any REFDTYPE slips through so we can investigate.
// The loop body is assert-only (no mutations); LCOV_EXCL because
// Phase A always resolves everything and ledgerFixed catches all refs.
for (AstNode* stmtp = newModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstRefDType* const refp = VN_CAST(stmtp, RefDType);
if (!refp) continue;
if (ledgerFixed.count(refp)) continue; // LCOV_EXCL_LINE
// LCOV_EXCL_START
// Check if typedefp or refDTypep points outside the reachable set
auto checkNotStale = [&](const char* label, AstNode* targetp) {
AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(targetp);
if (!ownerp || ownerp == newModp || VN_IS(ownerp, Package)
|| reachable.count(ownerp))
return; // OK: owner is reachable or self
v3fatalSrc("Phase B reachable-set fallback triggered for "
<< refp->prettyNameQ() << " " << label << " owner="
<< ownerp->prettyNameQ() << " in " << newModp->prettyNameQ());
};
if (refp->typedefp()) checkNotStale("typedefp", refp->typedefp());
if (refp->refDTypep()) checkNotStale("refDTypep", refp->refDTypep());
// LCOV_EXCL_STOP
}
}
// Return true on success, false on error
bool deepCloneModule(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp,
const string& newname, const IfaceRefRefs& ifaceRefRefs) {
@ -731,73 +949,83 @@ class ParamProcessor final {
newModp = srcModp->cloneTree(false);
}
// Mark the source module as a parameterized template now that a specialized
// clone exists. This suppresses width/type errors on the unresolved template
// during widthParamsEdit (which runs before V3LinkDot sets dead()).
srcModp->parameterizedTemplate(true);
// The clone is a specialized instance, not a template. Clear the flag in
// case it was inherited from a prior cloneTree (when srcModp was already
// marked by an earlier specialization).
newModp->parameterizedTemplate(false);
// cloneTree(false) temporarily populates origNode->clonep() for every node under
// srcModp. The capture list still stores those orig AstRefDType* pointers, so walking
// it lets us follow clonep() into newModp and scrub each clone with the saved
// interface context before newModp is re-linked. we have pointers to the same nodes saved
// in the capture map, so we can use them to scrub the new module.
if (V3LinkDotIfaceCapture::enabled()) {
AstCell* const cloneCellp = VN_CAST(ifErrorp, Cell);
UINFO(9, "iface capture clone: " << srcModp->prettyNameQ() << " -> "
<< newModp->prettyNameQ() << endl);
// First pass: register clone entries and direct-retarget
// REFDTYPEs whose owner won't be cloned later.
V3LinkDotIfaceCapture::forEachOwned(
srcModp, [&](const V3LinkDotIfaceCapture::CapturedIfaceTypedef& entry) {
srcModp, [&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
if (!entry.refp) return;
AstTypedef* const origTypedefp = entry.typedefp;
if (!origTypedefp) return;
// Find the correct typedef from the correct interface clone.
// entry.typedefp points to the original interface's typedef,
// but we need the typedef in the interface clone this module connects to.
AstTypedef* targetTypedefp = nullptr;
const string& typedefName = origTypedefp->name();
for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) {
const AstIfaceRefDType* const portIrefp = it->first;
AstNodeModule* const pinIfacep = it->second->ifaceViaCellp();
if (!pinIfacep) continue;
// If we have a port variable, match against it
if (entry.ifacePortVarp) {
// Get the IfaceRefDType from the captured port variable
AstNodeDType* const portDTypep = entry.ifacePortVarp->subDTypep();
AstIfaceRefDType* entryPortIrefp = VN_CAST(portDTypep, IfaceRefDType);
if (!entryPortIrefp && arraySubDTypep(portDTypep)) {
entryPortIrefp
= VN_CAST(arraySubDTypep(portDTypep), IfaceRefDType);
}
if (entryPortIrefp != portIrefp) continue; // Not the right port
UINFO(9, "iface capture entry: " << entry.refp << " cellPath='"
<< entry.cellPath << "'" << endl);
// Disambiguate via cellPath when cloning the interface
// that owns the typedef (matched via typedefOwnerModName).
if (cloneCellp && entry.ownerModp != srcModp
&& entry.typedefOwnerModName == srcModp->name()) {
UASSERT_OBJ(!entry.cellPath.empty(), entry.refp,
"cellPath is empty in entry matched via typedefOwnerModName");
if (!cellPathMatchesClone(entry.cellPath, cloneCellp, entry.ownerModp,
m_modp)) {
UINFO(9, "iface capture skipping (path mismatch)" << endl);
return;
}
// Search for typedef with same name in the connected interface clone
for (AstNode* stmtp = pinIfacep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstTypedef* const tdp = VN_CAST(stmtp, Typedef)) {
if (tdp->name() == typedefName) {
targetTypedefp = tdp;
UINFO(8,
" [iface-capture] found '"
<< typedefName << "' in " << pinIfacep->name()
<< " via port "
<< (entry.ifacePortVarp ? entry.ifacePortVarp->name()
: "<unknown>")
<< endl);
break;
}
}
}
if (targetTypedefp) break;
}
// Fallback to clone of original typedef (existing behavior)
if (!targetTypedefp) targetTypedefp = origTypedefp->clonep();
if (targetTypedefp) {
UINFO(8, " [iface-capture] replaceTypedef "
<< origTypedefp->name() << " -> " << targetTypedefp << endl);
V3LinkDotIfaceCapture::replaceTypedef(entry.refp, targetTypedefp);
}
// Propagate to cloned RefDType in new module
// Register clone entry in ledger (no AST mutation).
if (AstRefDType* const clonedRefp = entry.refp->clonep()) {
V3LinkDotIfaceCapture::propagateClone(entry.refp, clonedRefp);
const string cloneCP = cloneCellp ? cloneCellp->name() : string{};
const V3LinkDotIfaceCapture::TemplateKey tkey{
entry.ownerModp ? entry.ownerModp->name() : "", entry.refp->name(),
entry.cellPath};
V3LinkDotIfaceCapture::propagateClone(tkey, clonedRefp, cloneCP);
} else if (entry.ownerModp != srcModp) {
// REFDTYPE lives in a parent module; clonep() is null.
AstNodeModule* const actualOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(entry.refp);
if (actualOwnerp && actualOwnerp->hasGParam()) return;
// Owner won't be cloned - directly retarget now.
if (retargetRefToModule(entry, newModp)) {
UINFO(9, "iface capture direct retarget: " << entry.refp << " -> "
<< newModp->prettyNameQ()
<< endl);
}
}
});
// Second pass: retarget clone entries (non-empty cloneCellPath)
// whose typedef owner matches the module being cloned.
const string srcName = srcModp->name();
V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
if (!entry.refp || entry.cloneCellPath.empty()) return;
if (entry.typedefOwnerModName != srcName) return;
AstNodeModule* const actualOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(entry.refp);
if (!actualOwnerp || actualOwnerp->hasGParam()) return;
if (cloneCellp && !entry.cellPath.empty()
&& !cellPathMatchesClone(entry.cellPath, cloneCellp, actualOwnerp, m_modp)) {
return;
}
if (retargetRefToModule(entry, newModp)) {
UINFO(9, "iface capture clone-entry retarget: "
<< entry.refp << " -> " << newModp->prettyNameQ() << endl);
}
});
}
newModp->name(newname);
@ -877,6 +1105,11 @@ class ParamProcessor final {
// to find the correct interface for each VarXRef.
if (!ifaceRefRefs.empty()) { VarXRefRelinkVisitor{newModp}; }
// Fix cross-module REFDTYPE pointers in newModp (Phase A path-based
// + Phase B reachable-set fallback).
UASSERT_OBJ(newModp, srcModp, "newModp null before hierarchy fixup");
fixupCrossModuleRefDTypes(newModp, srcModp, ifErrorp, ifaceRefRefs);
// Assign parameters to the constants specified
// DOES clone() so must be finished with module clonep() before here
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
@ -886,7 +1119,7 @@ class ParamProcessor final {
AstConst* const exprp = VN_CAST(newp, Const);
AstConst* const origp = VN_CAST(modvarp->valuep(), Const);
const bool overridden
= !(origp && ParameterizedHierBlocks::areSame(exprp, origp));
= !(origp && exprp && ParameterizedHierBlocks::areSame(exprp, origp));
// Remove any existing parameter
if (modvarp->valuep()) modvarp->valuep()->unlinkFrBack()->deleteTree();
// Set this parameter to value requested by cell
@ -904,6 +1137,7 @@ class ParamProcessor final {
}
}
}
return true;
}
const ModInfo* moduleFindOrClone(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp,
@ -993,7 +1227,9 @@ class ParamProcessor final {
+= "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(pinp->exprp());
any_overridesr = true;
} else {
UINFO(9, "cellPinCleanup: before constify " << pinp << " " << modvarp);
V3Const::constifyParamsEdit(pinp->exprp());
UINFO(9, "cellPinCleanup: after constify " << pinp);
// String constants are parsed as logic arrays and converted to strings in V3Const.
// At this moment, some constants may have been already converted.
// To correctly compare constants, both should be of the same type,
@ -1005,6 +1241,9 @@ class ParamProcessor final {
AstConst* const exprp = VN_CAST(pinp->exprp(), Const);
AstConst* const origp = VN_CAST(modvarp->valuep(), Const);
if (!exprp) {
// With DepGraph architecture, all expressions should be constants
// by the time V3Param runs. If not, it's an error.
UINFO(9, "cellPinCleanup: NOT CONST after constify " << pinp);
UINFOTREE(1, pinp, "", "errnode");
pinp->v3error("Can't convert defparam value to constant: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
@ -1019,6 +1258,7 @@ class ParamProcessor final {
// Setting parameter to its default value. Just ignore it.
// This prevents making additional modules, and makes coverage more
// obvious as it won't show up under a unique module page name.
UINFO(9, "cellPinCleanup: same as default " << pinp);
} else if (exprp->num().isDouble() || exprp->num().isString()
|| exprp->num().isFourState() || exprp->num().width() != 32) {
longnamer
@ -1036,9 +1276,37 @@ class ParamProcessor final {
resolveDotToTypedef(pinp->exprp());
AstNodeDType* rawTypep = VN_CAST(pinp->exprp(), NodeDType);
if (rawTypep) V3Width::widthParamsEdit(rawTypep);
// Guard against widthing a struct/union still owned by a
// parameterized template interface (not yet specialized).
// widthParamsEdit is destructive: it evaluates range expressions,
// sets didWidth=1, and removes Range nodes, which would corrupt
// the template's BASICDTYPEs for all subsequent clones.
// Triggered by deeply nested parameterized interfaces (e.g.
// outer_if containing inner_if with struct typedefs) when the
// inner interface hasn't been specialized yet.
bool skipWidthForTemplateStruct = false;
{
// Use non-asserting skip: before widthParamsEdit, type(expr)
// constructs may contain unlinked REFDTYPEs (e.g. type(x-y))
AstNodeDType* const resolvedp = rawTypep ? rawTypep->skipRefOrNullp() : nullptr;
if (resolvedp && (VN_IS(resolvedp, StructDType) || VN_IS(resolvedp, UnionDType))) {
AstNodeModule* const ownerModp
= V3LinkDotIfaceCapture::findOwnerModule(resolvedp);
// Skip if owned by a parameterized template (not yet specialized)
if (ownerModp && ownerModp->parameterizedTemplate()) {
skipWidthForTemplateStruct = true;
V3Stats::addStatSum("Param, Template struct width skips", 1);
UINFO(9, "SKIP-WIDTH-TEMPLATE: struct="
<< resolvedp->prettyTypeName() << " templateOwner="
<< ownerModp->prettyNameQ() << " pin=" << pinp->prettyNameQ()
<< " of " << nodep->prettyNameQ()
<< " srcMod=" << srcModp->prettyNameQ() << endl);
}
}
if (rawTypep && !skipWidthForTemplateStruct) V3Width::widthParamsEdit(rawTypep);
}
AstNodeDType* exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr;
const AstNodeDType* const origp = modvarp->skipRefToNonRefp();
const AstNodeDType* origp = modvarp->skipRefToNonRefp();
if (!exprp) {
pinp->v3error("Parameter type pin value isn't a type: Param "
<< pinp->prettyNameQ() << " of " << nodep->prettyNameQ());
@ -1047,10 +1315,12 @@ class ParamProcessor final {
<< modvarp->prettyNameQ());
} else {
UINFO(9, "Parameter type assignment expr=" << exprp << " to " << origp);
V3Const::constifyParamsEdit(pinp->exprp()); // Reconcile typedefs
// Constify may have caused pinp->exprp to change
rawTypep = VN_AS(pinp->exprp(), NodeDType);
exprp = rawTypep->skipRefToNonRefp();
if (!skipWidthForTemplateStruct) {
V3Const::constifyParamsEdit(pinp->exprp()); // Reconcile typedefs
// Constify may have caused pinp->exprp to change
rawTypep = VN_AS(pinp->exprp(), NodeDType);
exprp = rawTypep->skipRefToNonRefp();
}
if (!modvarp->fwdType().isNodeCompatible(exprp)) {
pinp->v3error("Parameter type expression type "
<< exprp->prettyDTypeNameQ()
@ -1062,9 +1332,11 @@ class ParamProcessor final {
// This prevents making additional modules, and makes coverage more
// obvious as it won't show up under a unique module page name.
} else {
VL_DO_DANGLING(V3Const::constifyParamsEdit(exprp), exprp);
rawTypep = VN_CAST(pinp->exprp(), NodeDType);
exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr;
if (!skipWidthForTemplateStruct) {
VL_DO_DANGLING(V3Const::constifyParamsEdit(exprp), exprp);
rawTypep = VN_CAST(pinp->exprp(), NodeDType);
exprp = rawTypep ? rawTypep->skipRefToNonRefp() : nullptr;
}
longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp);
any_overridesr = true;
}
@ -1302,7 +1574,6 @@ class ParamProcessor final {
// Returns new or reused module
// Make sure constification worked
// Must be a separate loop, as constant conversion may have changed some pointers.
// UINFOTREE(1, nodep, "", "cel2");
string longname = srcModp->name() + "_";
if (debug() >= 9 && paramsp) paramsp->dumpTreeAndNext(cout, "- cellparams: ");
@ -1332,6 +1603,9 @@ class ParamProcessor final {
}
}
UINFO(9, "nodeDeparamCommon: " << srcModp->prettyNameQ() << " overrides=" << any_overrides
<< endl);
AstNodeModule* newModp = nullptr;
if (m_hierBlocks.hierSubRun() && m_hierBlocks.isHierBlock(srcModp->origName())) {
AstNodeModule* const paramedModp
@ -1343,6 +1617,7 @@ class ParamProcessor final {
newModp = paramedModp;
// any_overrides = true; // Unused later, so not needed
} else if (!any_overrides) {
UINFO(9, "nodeDeparamCommon: no overrides, reusing " << srcModp);
UINFO(8, "Cell parameters all match original values, skipping expansion.");
// If it's the first use of the default instance, create a copy and store it in user3p.
// user3p will also be used to check if the default instance is used.
@ -1369,10 +1644,8 @@ class ParamProcessor final {
}
const bool cloned = (newModp != srcModp);
UINFO(9, "iface capture module clone src=" << srcModp << " new=" << newModp << " name="
<< newModp->name() << " from cell=" << nodep
<< " cellName=" << nodep->name()
<< " cloned=" << cloned);
UINFO(9, "nodeDeparamCommon result: " << newModp->prettyNameQ() << " cloned=" << cloned
<< endl);
// Link source class to its specialized version for later relinking of method references
if (defaultsResolved) srcModp->user4p(newModp);
@ -1412,6 +1685,56 @@ class ParamProcessor final {
return newModp;
}
AstNodeModule* ifaceRefDeparam(AstIfaceRefDType* const nodep, AstNodeModule* srcModp) {
// Check for self-reference pattern: typedef iface#(T) this_type inside interface iface
// When processing inside a specialized interface, the IfaceRefDType should point to
// the owner interface, not create an intermediate specialization.
if (m_modp && VN_IS(m_modp, Iface)) {
AstIface* ownerIfacep = const_cast<AstIface*>(VN_AS(m_modp, Iface));
const string ownerOrigName
= ownerIfacep->origName().empty() ? ownerIfacep->name() : ownerIfacep->origName();
const string srcOrigName
= srcModp->origName().empty() ? srcModp->name() : srcModp->origName();
string ownerBaseName = ownerOrigName;
const size_t ownerPos = ownerBaseName.find("__");
if (ownerPos != string::npos) ownerBaseName = ownerBaseName.substr(0, ownerPos);
string srcBaseName = srcOrigName;
const size_t srcPos = srcBaseName.find("__");
if (srcPos != string::npos) srcBaseName = srcBaseName.substr(0, srcPos);
if (ownerBaseName == srcBaseName) {
bool allOwnParams = true;
for (AstPin* pinp = nodep->paramsp(); pinp && allOwnParams;
pinp = VN_AS(pinp->nextp(), Pin)) {
if (AstRefDType* const refp = VN_CAST(pinp->exprp(), RefDType)) {
if (AstParamTypeDType* const ptdp
= VN_CAST(refp->refDTypep(), ParamTypeDType)) {
AstNodeModule* const ptdOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(ptdp);
if (ptdOwnerp != m_modp) allOwnParams = false;
} else {
pinp->v3error( // LCOV_EXCL_LINE
"Self-referencing interface typedef "
"parameter is not a type parameter of "
"the enclosing interface");
}
} else {
pinp->v3error( // LCOV_EXCL_LINE
"Self-referencing interface typedef "
"parameter is not a type reference");
}
}
if (allOwnParams) {
UINFO(5, "ifaceRefDeparam: self-reference pattern detected in "
<< ownerIfacep->prettyNameQ() << ", using owner interface"
<< endl);
V3Stats::addStatSum("Param, Self-reference iface typedefs", 1);
nodep->ifacep(ownerIfacep);
if (nodep->paramsp()) nodep->paramsp()->unlinkFrBackWithNext()->deleteTree();
return ownerIfacep;
}
}
}
AstNodeModule* const newModp
= nodeDeparamCommon(nodep, srcModp, nodep->paramsp(), nullptr, false);
if (!newModp) return nullptr;
@ -1461,13 +1784,35 @@ public:
// We always run this, even if no parameters, as need to look for interfaces,
// and remove any recursive references
UINFO(4, "De-parameterize: " << nodep);
UINFO(9, "nodeDeparam ENTER node=<"
<< AstNode::nodeAddr(nodep) << ">" << " type=" << nodep->typeName()
<< " srcMod=" << (srcModp ? srcModp->prettyNameQ() : "'<null>'")
<< " srcSomeInstanceName='"
<< (srcModp ? srcModp->someInstanceName() : string("<null>")) << "'"
<< " parentMod=" << (modp ? modp->prettyNameQ() : "'<null>'")
<< " parentSomeInstanceName='"
<< (modp ? modp->someInstanceName() : string("<null>")) << "'"
<< " inputSomeInstanceName='" << someInstanceName << "'" << endl);
// Create new module name with _'s between the constants
UINFOTREE(10, nodep, "", "cell");
// Evaluate all module constants
V3Const::constifyParamsEdit(nodep);
// Set name for warnings for when we param propagate the module
const string instanceName = someInstanceName + "." + nodep->name();
// For AstIfaceRefDType, name() returns the modport name (often empty),
// so use cellName() which is the actual cell instance name.
// If both are empty (interface port, not a cell), skip appending
// to avoid double-dots in the path.
string nodeName = nodep->name();
if (AstIfaceRefDType* const ifaceRefp = VN_CAST(nodep, IfaceRefDType)) {
if (nodeName.empty()) nodeName = ifaceRefp->cellName();
}
const string instanceName
= nodeName.empty() ? someInstanceName : (someInstanceName + "." + nodeName);
srcModp->someInstanceName(instanceName);
UINFO(9, "nodeDeparam SET-SRC-INST srcMod="
<< srcModp->prettyNameQ() << " someInstanceName='"
<< srcModp->someInstanceName() << "'" << " node=<" << AstNode::nodeAddr(nodep)
<< ">" << " nodeType=" << nodep->typeName() << endl);
AstNodeModule* newModp = nullptr;
if (AstCell* const cellp = VN_CAST(nodep, Cell)) {
@ -1487,6 +1832,15 @@ public:
// Set name for later warnings
newModp->someInstanceName(instanceName);
UINFO(9, "nodeDeparam EXIT node=<"
<< AstNode::nodeAddr(nodep) << ">" << " type=" << nodep->typeName()
<< " srcMod=" << (srcModp ? srcModp->prettyNameQ() : "'<null>'")
<< " srcSomeInstanceName='"
<< (srcModp ? srcModp->someInstanceName() : string("<null>")) << "'"
<< " newMod=" << (newModp ? newModp->prettyNameQ() : "'<null>'")
<< " newSomeInstanceName='"
<< (newModp ? newModp->someInstanceName() : string("<null>")) << "'" << endl);
UINFO(8, " Done with orig " << nodep);
// if (debug() >= 10)
// v3Global.rootp()->dumpTreeFile(v3Global.debugFilename("param-out.tree"));
@ -1592,6 +1946,12 @@ class ParamVisitor final : public VNVisitor {
// Process once; note user2 will be cleared on specialization, so we will do the
// specialized module if needed
if (!modp->user2SetOnce()) {
UINFO(9, "processWorkQ module begin mod='"
<< modp->name() << "' orig='" << modp->origName() << "'"
<< " someInstanceName='" << modp->someInstanceName() << "'"
<< " hasGParam=" << (modp->hasGParam() ? "yes" : "no")
<< " user3p=" << (modp->user3p() ? "set" : "null")
<< " dead=" << (modp->dead() ? "yes" : "no") << endl);
// TODO: this really should be an assert, but classes and hier_blocks are
// special...
@ -1643,6 +2003,10 @@ class ParamVisitor final : public VNVisitor {
if (AstNodeModule* const newModp
= m_processor.nodeDeparam(cellp, srcModp, modp, someInstanceName)) {
if (VN_IS(srcModp, Iface)) {
logTemplateLeakRefs(modp, srcModp, "after queued nodeDeparam", cellp);
}
// Add the (now potentially specialized) child module to the work queue
workQueue.emplace(newModp->level(), newModp);
@ -1662,18 +2026,105 @@ class ParamVisitor final : public VNVisitor {
return dotted.substr(0, dotted.find('.'));
}
// Debug-only diagnostic (requires --debugi-V3Param 9).
// Walks parentModp looking for RefDTypes or VarRefs whose typedef,
// refDType, or variable target is still owned by templateModp (the
// unspecialized interface template). Any such "leak" indicates a
// pointer that was not properly redirected to the clone during
// deparameterization. Logs each leak with ancestry for triage.
void logTemplateLeakRefs(AstNodeModule* parentModp, AstNodeModule* templateModp,
const char* stage, AstNode* contextp) {
if (debug() < 9 || !parentModp || !templateModp || !VN_IS(templateModp, Iface)) return;
// LCOV_EXCL_START // Debug-only diagnostic
int leakCount = 0;
const auto ancestryOf = [](const AstNode* nodep) {
string ancestry;
for (const AstNode* curp = nodep; curp; curp = curp->backp()) {
if (!ancestry.empty()) ancestry += "<-";
ancestry += curp->typeName();
if (VN_IS(curp, NodeModule) || VN_IS(curp, Netlist) || VN_IS(curp, TypeTable)) {
break;
}
}
return ancestry;
};
parentModp->foreach([&](AstRefDType* refp) {
if (refp->typedefp()) {
AstNodeModule* const tdOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(refp->typedefp());
if (tdOwnerp == templateModp) {
++leakCount;
UINFO(9, "TEMPLATE-LEAK "
<< stage << " parent=" << parentModp->prettyNameQ()
<< " template=" << templateModp->prettyNameQ() << " contextType="
<< (contextp ? contextp->typeName() : string("<null>"))
<< " contextName="
<< (contextp ? contextp->prettyNameQ() : "'<null>'")
<< " leak=REFDTYPE typedef owner" << " ref=<"
<< AstNode::nodeAddr(refp) << ">"
<< " refName=" << refp->prettyNameQ()
<< " ancestry=" << ancestryOf(refp) << endl);
}
}
if (refp->refDTypep()) {
AstNodeModule* const rdOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(refp->refDTypep());
if (rdOwnerp == templateModp) {
++leakCount;
UINFO(9, "TEMPLATE-LEAK "
<< stage << " parent=" << parentModp->prettyNameQ()
<< " template=" << templateModp->prettyNameQ() << " contextType="
<< (contextp ? contextp->typeName() : string("<null>"))
<< " contextName="
<< (contextp ? contextp->prettyNameQ() : "'<null>'")
<< " leak=REFDTYPE refDType owner" << " ref=<"
<< AstNode::nodeAddr(refp) << ">"
<< " refName=" << refp->prettyNameQ()
<< " ancestry=" << ancestryOf(refp) << endl);
}
}
});
parentModp->foreach([&](AstVarRef* varrefp) {
if (!varrefp->varp()) return;
AstNodeModule* const varOwnerp
= V3LinkDotIfaceCapture::findOwnerModule(varrefp->varp());
if (varOwnerp != templateModp) return;
++leakCount;
UINFO(9, "TEMPLATE-LEAK "
<< stage << " parent=" << parentModp->prettyNameQ()
<< " template=" << templateModp->prettyNameQ()
<< " contextType=" << (contextp ? contextp->typeName() : string("<null>"))
<< " contextName=" << (contextp ? contextp->prettyNameQ() : "'<null>'")
<< " leak=VARREF target owner" << " ref=<" << AstNode::nodeAddr(varrefp)
<< ">" << " var=" << varrefp->prettyNameQ()
<< " ancestry=" << ancestryOf(varrefp) << endl);
});
if (leakCount > 0) {
UINFO(9, "TEMPLATE-LEAK summary stage='"
<< stage << "' parent=" << parentModp->prettyNameQ() << " template="
<< templateModp->prettyNameQ() << " count=" << leakCount << endl);
}
// LCOV_EXCL_STOP
}
void checkParamNotHier(AstNode* valuep) {
if (!valuep) return;
valuep->foreachAndNext([&](const AstNodeExpr* exprp) {
if (const AstVarXRef* const refp = VN_CAST(exprp, VarXRef)) {
// Allow hierarchical ref to interface params through interface/modport ports
bool isIfacePortRef = false;
// or local interface instances
bool isIfaceRef = false;
if (refp->varp() && refp->varp()->isIfaceParam()) {
const string refname = getRefBaseName(refp);
isIfacePortRef = !refname.empty() && m_ifacePortNames.count(refname);
isIfaceRef
= !refname.empty()
&& (m_ifacePortNames.count(refname) || m_ifaceInstNames.count(refname));
}
if (!isIfacePortRef) {
if (!isIfaceRef) {
refp->v3warn(HIERPARAM, "Parameter values cannot use hierarchical values"
" (IEEE 1800-2023 6.20.2)");
}
@ -1737,13 +2188,12 @@ class ParamVisitor final : public VNVisitor {
AstCell* const cellp = VN_CAST(nodep, Cell);
if (!cellParamsReferenceIfacePorts(cellp)) {
AstNodeModule* const srcModp = cellp->modp();
if (AstNodeModule* const newModp = m_processor.nodeDeparam(
cellp, srcModp, m_modp, m_modp->someInstanceName())) {
// For specialized interfaces, recursively process nested interface cells.
// This ensures nested interfaces are already specialized when modules
// using the interface are processed (parameter passthrough fix).
if (newModp != srcModp) specializeNestedIfaceCells(newModp);
}
// DISABLED: specializeNestedIfaceCells causes early nested
// iface specialization where PARAMTYPEDTYPE child REFDTYPEs
// point to template structs instead of clone structs,
// destructively widthing the template with default (zero)
// values. See t_interface_nested_struct_param.v.
m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
}
}
@ -1756,6 +2206,12 @@ class ParamVisitor final : public VNVisitor {
void visit(AstNodeModule* nodep) override {
if (nodep->recursiveClone()) nodep->dead(true); // Fake, made for recursive elimination
if (nodep->dead()) return; // Marked by LinkDot (and above)
UINFO(9, "V3Param: visit module name="
<< nodep->prettyNameQ() << " orig='" << nodep->origName()
<< "' someInstanceName='" << nodep->someInstanceName()
<< "' hasGParam=" << (nodep->hasGParam() ? "yes" : "no")
<< " user3p=" << (nodep->user3p() ? "set" : "null")
<< " dead=" << (nodep->dead() ? "yes" : "no") << endl);
if (AstClass* const classp = VN_CAST(nodep, Class)) {
if (classp->hasGParam()) {
// Don't enter into a definition.
@ -1836,8 +2292,22 @@ class ParamVisitor final : public VNVisitor {
if (!nodep->valuep() && !VN_IS(m_modp, Class)) {
nodep->v3error("Parameter without default value is never given value"
<< " (IEEE 1800-2023 6.20.1): " << nodep->prettyNameQ());
} else {
V3Const::constifyParamsEdit(nodep); // The variable, not just the var->init()
} else if (nodep->valuep()) {
// In visit(AstVar*) for localparams, check if expression contains VARXREF
// to another localparam (not parameter). Parameters are already const,
// but localparams may not be evaluated yet.
bool hasVarXRefToLparam = false;
nodep->valuep()->foreach([&](const AstVarXRef* xrefp) {
if (xrefp->varp() && xrefp->varp()->varType() == VVarType::LPARAM) {
hasVarXRefToLparam = true;
}
});
if (hasVarXRefToLparam) {
// Don't constify - let it be evaluated later
return;
}
V3Const::constifyParamsEdit(nodep);
}
}
}
@ -2127,6 +2597,7 @@ public:
explicit ParamVisitor(ParamState& state, AstNetlist* netlistp)
: m_state{state}
, m_processor{netlistp} {
// Relies on modules already being in top-down-order
iterate(netlistp);
}
@ -2214,22 +2685,28 @@ public:
netlistp->foreach([](AstNodeFTaskRef* ftaskrefp) {
AstNodeFTask* ftaskp = ftaskrefp->taskp();
if (!ftaskp || !ftaskp->classMethod()) return;
const string funcName = ftaskp->name();
for (AstNode* backp = ftaskrefp->backp(); backp; backp = backp->backp()) {
if (VN_IS(backp, Class)) {
if (backp == ftaskrefp->classOrPackagep())
return; // task is in the same class as reference
break;
string funcName = ftaskp->name();
// Find the nearest containing (ancestor) class for a node.
// Uses aboveLoopp() which correctly skips sibling links
// (e.g. covergroup classes) to find the true parent.
const auto ancestorClassOf = [](AstNode* startp) -> AstClass* {
for (AstNode* np = startp->aboveLoopp(); np; np = np->aboveLoopp()) {
if (AstClass* const cp = VN_CAST(np, Class)) return cp;
}
}
AstClass* classp = nullptr;
for (AstNode* backp = ftaskp->backp(); backp; backp = backp->backp()) {
if (VN_IS(backp, Class)) {
classp = VN_AS(backp, Class);
break;
}
}
return nullptr;
};
AstClass* refClassp = ancestorClassOf(ftaskrefp);
if (refClassp == ftaskrefp->classOrPackagep())
return; // task is in the same class as reference
AstClass* classp = ancestorClassOf(ftaskp);
UASSERT_OBJ(classp, ftaskrefp, "Class method has no class above it");
// If the FUNCREF and its task are both in the same (clone) class but
// classOrPackagep still points to the old template, just retarget it
if (refClassp && refClassp == classp && ftaskrefp->classOrPackagep()
&& ftaskrefp->classOrPackagep() != refClassp) {
ftaskrefp->classOrPackagep(refClassp);
return;
}
if (classp->user3p()) return; // will not get removed, no need to relink
AstClass* const parametrizedClassp = VN_CAST(classp->user4p(), Class);
if (!parametrizedClassp) return;
@ -2280,11 +2757,11 @@ public:
}
// Set all links pointing to a user3 (deleting) node as null
netlistp->foreach([](AstNode* const nodep) {
nodep->foreachLink([&](AstNode** const linkpp, const char*) {
nodep->foreachLink([&](AstNode** const linkpp, const char* namep) {
if (*linkpp && (*linkpp)->user3()) {
UINFO(9, "clear link " << nodep);
UINFO(9, "clear link " << namep << " on " << nodep);
*linkpp = nullptr;
UINFO(9, "cleared link " << nodep);
UINFO(9, "cleared link " << namep << " on " << nodep);
}
});
});
@ -2298,6 +2775,11 @@ public:
void V3Param::param(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
{ ParamTop{rootp}; } // Destruct before checking
if (dumpTreeEitherLevel() >= 9) V3LinkDotIfaceCapture::dumpEntries("before V3Param");
{ ParamTop{rootp}; }
V3LinkDotIfaceCapture::purgeStaleRefs();
if (dumpTreeEitherLevel() >= 9) V3LinkDotIfaceCapture::dumpEntries("after V3Param");
V3Global::dumpCheckGlobalTree("param", 0, dumpTreeEitherLevel() >= 3);
}

View File

@ -72,6 +72,7 @@
#include "V3Const.h"
#include "V3Error.h"
#include "V3Global.h"
#include "V3LinkDotIfaceCapture.h"
#include "V3LinkLValue.h"
#include "V3MemberMap.h"
#include "V3Number.h"
@ -270,6 +271,13 @@ class WidthVisitor final : public VNVisitor {
nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)});
}
}
// When fromp() is a DType (e.g. unlinked RefDType), resolve through
// the ref chain; when it's an expression, dtypep() is already resolved.
static AstNodeDType* fromDTypep(AstNode* fromp) {
if (AstNodeDType* const dtypep = VN_CAST(fromp, NodeDType))
return dtypep->skipRefOrNullp();
return fromp ? fromp->dtypep() : nullptr;
}
// VISITORS
// Naming: width_O{outputtype}_L{lhstype}_R{rhstype}_W{widthing}_S{signing}
// Where type:
@ -1006,8 +1014,17 @@ class WidthVisitor final : public VNVisitor {
<< std::hex << width << std::dec);
}
// Note width() not set on range; use elementsConst()
const bool inDeadModule = m_modep && m_modep->dead();
// Suppress ASCRANGE in parameterized template modules whose parameter-dependent
// ranges haven't been resolved yet, or when the type has no owning module
// (e.g. moved to TypeTable during DepGraph resolution).
const bool inParameterizedTemplate
= m_modep && (m_modep->dead() || m_modep->parameterizedTemplate());
const bool inTypeTable = !m_modep;
if (nodep->ascending() && !VN_IS(nodep->backp(), UnpackArrayDType)
&& !VN_IS(nodep->backp(), Cell)) { // For cells we warn in V3Inst
&& !VN_IS(nodep->backp(), Cell) // For cells we warn in V3Inst
&& !m_paramsOnly // Skip during parameter evaluation
&& !inDeadModule && !inParameterizedTemplate && !inTypeTable) {
nodep->v3warn(ASCRANGE, "Ascending bit range vector: left < right of bit range: ["
<< nodep->leftConst() << ":" << nodep->rightConst()
<< "]");
@ -1035,6 +1052,11 @@ class WidthVisitor final : public VNVisitor {
}
UASSERT_OBJ(nodep->dtypep(), nodep, "dtype wasn't set"); // by V3WidthSel
// Suppress SELRANGE in parameterized template modules where
// parameter-dependent widths haven't been resolved yet.
const bool inParameterizedTemplate
= m_modep && (m_modep->dead() || m_modep->parameterizedTemplate());
if (VN_IS(nodep->lsbp(), Const) && nodep->msbConst() < nodep->lsbConst()) {
// Likely impossible given above width check
nodep->v3warn(E_UNSUPPORTED,
@ -1047,7 +1069,7 @@ class WidthVisitor final : public VNVisitor {
nodep->lsbp()->replaceWith(new AstConst{nodep->lsbp()->fileline(), 0});
}
// We're extracting, so just make sure the expression is at least wide enough.
if (nodep->fromp()->width() < width) {
if (nodep->fromp()->width() < width && !inParameterizedTemplate) {
nodep->v3warn(SELRANGE, "Extracting " << width << " bits from only "
<< nodep->fromp()->width() << " bit number");
// Extend it.
@ -1102,7 +1124,7 @@ class WidthVisitor final : public VNVisitor {
AstNodeVarRef* lrefp = AstNodeVarRef::varRefLValueRecurse(nodep);
if (m_doGenerate) {
UINFO(5, "Selection index out of range inside generate");
} else {
} else if (!inParameterizedTemplate) {
nodep->v3warn(SELRANGE, "Selection index out of range: "
<< nodep->msbConst() << ":" << nodep->lsbConst()
<< " outside " << frommsb << ":" << fromlsb);
@ -1196,11 +1218,13 @@ class WidthVisitor final : public VNVisitor {
if (VN_IS(nodep->bitp(), Const)
&& (VN_AS(nodep->bitp(), Const)->toSInt() > (frommsb - fromlsb)
|| VN_AS(nodep->bitp(), Const)->toSInt() < 0)) {
nodep->v3warn(SELRANGE,
"Selection index out of range: "
<< (VN_AS(nodep->bitp(), Const)->toSInt() + fromlsb)
<< " outside " << frommsb << ":" << fromlsb);
UINFO(1, " Related node: " << nodep);
// Suppress in dead/parameterized template modules
if (!(m_modep && (m_modep->dead() || m_modep->parameterizedTemplate()))) {
nodep->v3warn(SELRANGE,
"Selection index out of range: "
<< (VN_AS(nodep->bitp(), Const)->toSInt() + fromlsb)
<< " outside " << frommsb << ":" << fromlsb);
}
}
widthCheckSized(nodep, "Extract Range", nodep->bitp(), selwidthDTypep, EXTEND_EXP,
false /*NOWARN*/);
@ -1842,8 +1866,9 @@ class WidthVisitor final : public VNVisitor {
switch (nodep->attrType()) {
case VAttrType::DIM_DIMENSIONS:
case VAttrType::DIM_UNPK_DIMENSIONS: {
UASSERT_OBJ(nodep->fromp() && nodep->fromp()->dtypep(), nodep, "Unsized expression");
const std::pair<uint32_t, uint32_t> dim = nodep->fromp()->dtypep()->dimensions(true);
AstNodeDType* const dtypep = fromDTypep(nodep->fromp());
UASSERT_OBJ(dtypep, nodep, "Unsized expression");
const std::pair<uint32_t, uint32_t> dim = dtypep->dimensions(true);
const int val
= (nodep->attrType() == VAttrType::DIM_UNPK_DIMENSIONS ? dim.second
: (dim.first + dim.second));
@ -1872,8 +1897,8 @@ class WidthVisitor final : public VNVisitor {
case VAttrType::DIM_LOW:
case VAttrType::DIM_RIGHT:
case VAttrType::DIM_SIZE: {
UASSERT_OBJ(nodep->fromp() && nodep->fromp()->dtypep(), nodep, "Unsized expression");
AstNodeDType* const dtypep = nodep->fromp()->dtypep();
AstNodeDType* const dtypep = fromDTypep(nodep->fromp());
UASSERT_OBJ(dtypep, nodep, "Unsized expression");
if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, DynArrayDType)) {
switch (nodep->attrType()) {
case VAttrType::DIM_SIZE: {
@ -1940,9 +1965,11 @@ class WidthVisitor final : public VNVisitor {
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
} else {
AstNodeDType* const baseDTypep = dtypep->skipRefp();
UASSERT_OBJ(baseDTypep, nodep, "Unsized expression");
const int dim = 1;
AstConst* const newp
= dimensionValue(nodep->fileline(), dtypep, nodep->attrType(), dim);
AstConst* const newp = dimensionValue(nodep->fileline(), baseDTypep,
nodep->attrType(), dim);
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
@ -2033,6 +2060,34 @@ class WidthVisitor final : public VNVisitor {
// Only used in CStmts which don't care....
}
void visit(AstCellArrayRef* nodep) override {
if (nodep->didWidthAndSet()) return;
userIterateAndNext(nodep->selp(), WidthVP{SELF, PRELIM}.p());
userIterateAndNext(nodep->selp(), WidthVP{SELF, FINAL}.p());
nodep->dtypeSetVoid(); // placeholder; this node shouldnt survive beyond linking
nodep->didWidth(true);
}
void visit(AstCellRef* nodep) override {
if (nodep->didWidthAndSet()) return;
if (AstNodeExpr* const cellExprp = VN_CAST(nodep->cellp(), NodeExpr)) {
userIterateAndNext(cellExprp, WidthVP{SELF, PRELIM}.p());
userIterateAndNext(cellExprp, WidthVP{SELF, FINAL}.p());
}
if (AstNodeExpr* const exprp = VN_CAST(nodep->exprp(), NodeExpr)) {
userIterateAndNext(exprp, WidthVP{SELF, PRELIM}.p());
nodep->dtypeFrom(exprp);
userIterateAndNext(exprp, WidthVP{SELF, FINAL}.p());
} else {
nodep->v3fatalSrc("CellRef exprp is not a NodeExpr: " // LCOV_EXCL_LINE
<< nodep->exprp()->prettyTypeName());
}
nodep->didWidth(true);
}
// DTYPES
void visit(AstNodeArrayDType* nodep) override {
if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed
@ -2226,6 +2281,16 @@ class WidthVisitor final : public VNVisitor {
}
// Effectively nodep->dtypeFrom(nodep->dtypeSkipRefp());
// But might be recursive, so instead manually recurse into the referenced type
if (!nodep->subDTypep()) {
// Defer unlinked RefDTypes in parameterized template modules (or types
// with no owning module, e.g. moved to TypeTable) until specialization
// resolves them.
const bool inTemplateModule = !m_modep || m_modep->parameterizedTemplate();
if (inTemplateModule) {
nodep->doingWidth(false);
return;
}
}
UASSERT_OBJ(nodep->subDTypep(), nodep, "Unlinked");
nodep->dtypeFrom(nodep->subDTypep());
nodep->widthFromSub(nodep->subDTypep());
@ -2251,9 +2316,13 @@ class WidthVisitor final : public VNVisitor {
}
void visit(AstParamTypeDType* nodep) override {
if (nodep->didWidthAndSet()) return; // This node is a dtype & not both PRELIMed+FINALed
nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->subDTypep()));
userIterateChildren(nodep, nullptr);
nodep->widthFromSub(nodep->subDTypep());
// Clear childDTypep after dtypep is set to satisfy V3Broken invariant.
// The child dtype has been moved to the type table by iterateEditMoveDTypep.
if (nodep->dtypep() && nodep->childDTypep()) { nodep->childDTypep(nullptr); }
}
void visit(AstRequireDType* nodep) override {
userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p());
@ -2631,6 +2700,10 @@ class WidthVisitor final : public VNVisitor {
const bool implicitParam = nodep->isParam() && bdtypep && bdtypep->implicit();
if (implicitParam) {
if (nodep->valuep()) {
// Remove blanket deferral. We must attempt to visit the value to determine
// type/deps. If it remains unresolved, specific node visitors (like AttrOf) should
// handle deferral by setting a placeholder type to prevent "No dtype" errors
// later.
userIterateAndNext(nodep->valuep(), WidthVP{nodep->dtypep(), PRELIM}.p());
UINFO(9, "implicitParamPRELIMIV " << nodep->valuep());
// Although nodep will get a different width for parameters
@ -2645,7 +2718,10 @@ class WidthVisitor final : public VNVisitor {
VL_DANGLING(bdtypep);
} else {
int width = 0;
const AstBasicDType* const valueBdtypep = nodep->valuep()->dtypep()->basicp();
AstNodeDType* const valueDTypep = nodep->valuep()->dtypep();
UASSERT_OBJ(valueDTypep, nodep->valuep(),
"Null dtype on implicit param value");
const AstBasicDType* const valueBdtypep = valueDTypep->basicp();
bool issigned = false;
if (bdtypep->isNosign()) {
if (valueBdtypep && valueBdtypep->isSigned()) issigned = true;
@ -3278,6 +3354,14 @@ class WidthVisitor final : public VNVisitor {
const bool isHardPackedUnion
= nodep->packed() && VN_IS(nodep, UnionDType) && !VN_CAST(nodep, UnionDType)->isSoft();
// Suppress union size errors in parameterized template modules where member
// widths depend on unresolved parameters. Also suppress when the type has no
// owning module (e.g. moved to TypeTable during DepGraph resolution).
// TODO: Revisit this gate if DepGraph becomes the sole flow and widthing
// can assume all types are already specialized.
const bool inTemplateModule = (m_modep && m_modep->parameterizedTemplate())
|| (VN_IS(nodep, UnionDType) && !m_modep);
// Determine bit assignments and width
if (VN_IS(nodep, UnionDType) || nodep->packed()) {
int lsb = 0;
@ -3293,7 +3377,8 @@ class WidthVisitor final : public VNVisitor {
itemp->lsb(lsb);
if (VN_IS(nodep, UnionDType)) {
const int itemWidth = itemp->width();
if (!first && isHardPackedUnion && itemWidth != width) {
// Skip union size check for template modules with unresolved parameters
if (!first && isHardPackedUnion && itemWidth != width && !inTemplateModule) {
itemp->v3error("Hard packed union members must have equal size "
"(IEEE 1800-2023 7.3.1)");
}
@ -7899,6 +7984,7 @@ class WidthVisitor final : public VNVisitor {
"Under node " << nodep->prettyTypeName()
<< " has no dtype?? Missing Visitor func?");
if (expDTypep->basicp()->untyped() || nodep->dtypep()->basicp()->untyped()) return false;
// During DepGraph execution, expected width may be 0 if the type hasn't been
UASSERT_OBJ(nodep->width() != 0, nodep,
"Under node " << nodep->prettyTypeName()
<< " has no expected width?? Missing Visitor func?");
@ -8871,6 +8957,7 @@ class WidthVisitor final : public VNVisitor {
UASSERT_OBJ(dtnodep->didWidth(), parentp,
"iterateEditMoveDTypep didn't get width resolution of "
<< dtnodep->prettyTypeName());
// Move to under netlist
UINFO(9, "iterateEditMoveDTypep child moving " << dtnodep);
dtnodep->unlinkFrBack();

View File

@ -69,6 +69,7 @@
#include "V3LifePost.h"
#include "V3LiftExpr.h"
#include "V3LinkDot.h"
#include "V3LinkDotIfaceCapture.h"
#include "V3LinkInc.h"
#include "V3LinkJump.h"
#include "V3LinkLValue.h"
@ -178,10 +179,15 @@ static void process() {
// This requires some width calculations and constant propagation
// No more AstGenCase/AstGenFor/AstGenIf after this
V3Param::param(v3Global.rootp());
V3LinkDot::linkDotParamed(v3Global.rootp()); // Cleanup as made new modules
V3LinkLValue::linkLValue(v3Global.rootp()); // Resolve new VarRefs
V3Error::abortIfErrors();
// Fix any remaining cross-interface refs created during V3Width::widthParamsEdit
// that weren't captured earlier. Must run before V3Dead deletes template modules.
V3LinkDotIfaceCapture::finalizeIfaceCapture();
// Remove any modules that were parameterized and are no longer referenced.
V3Dead::deadifyModules(v3Global.rootp());

View File

@ -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: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,32 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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: 2025 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
class Class1 #(
type T
);
static function int get_p();
return 7;
endfunction
endclass
class Class2 #(
type T
) extends Class1 #(T);
static function int get_p2;
return T::get_p();
endfunction
endclass
module t;
initial begin
typedef Class2#(Class1#(int)) Class;
if (Class::get_p2() != Class1#(int)::get_p()) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,24 @@
#!/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 by Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
# Verifies that the self-referential interface typedef pattern
# (typedef iface#(T) self_t inside interface iface) is detected
# and handled correctly during deparameterization.
import vltest_bootstrap
test.scenarios('vlt')
test.compile(v_flags2=["--binary --stats"])
test.file_grep(test.stats, r'Param, Self-reference iface typedefs\s+(\d+)', 1)
test.execute()
test.passes()

View File

@ -0,0 +1,24 @@
// DESCRIPTION: Verilator: Test for self-referential interface typedef
//
// 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
// Self-referential typedef: typedef iface#(T) this_type inside interface iface
interface my_iface #(type T = logic);
typedef my_iface #(T) self_t;
T data;
endinterface
module t ();
my_iface #(logic [7:0]) if0 ();
initial begin
if0.data = 8'hAB;
if ($bits(if0.data) != 8) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,75 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Stress test for interface typedef scaling. Generates an interface with
# many typedefs and two parameterised instances to exercise the
# findTypedefInModule / findDTypeInModule caching path.
#
# 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 time
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = test.obj_dir + "/t_iface_typedef_scale.sv"
N_TYPEDEFS = 5000
MAX_COMPILE_SECS = 10 # Generous budget; catches O(N^2) explosion where 5k typedefs would take minutes without the cache
def gen(filename, n):
with open(filename, 'w', encoding="utf8") as fh:
fh.write("// Generated by t_iface_typedef_scale.py\n")
fh.write("// Stress test: interface with {} typedefs, two instances\n".format(n))
fh.write("\n")
# Interface with N typedefs
fh.write("interface iface_many_types #(parameter int CFG = 8);\n")
for i in range(n):
fh.write(" typedef logic [CFG-1:0] td_{};\n".format(i))
fh.write("endinterface\n\n")
# Module that uses the interface and references typedefs via the port
fh.write("module sub (\n")
fh.write(" iface_many_types ifc,\n")
fh.write(" input logic clk\n")
fh.write(");\n")
fh.write(" typedef ifc.td_0 local_td_0;\n")
fh.write(" typedef ifc.td_{} local_td_n;\n".format(n - 1))
fh.write(" local_td_0 r0;\n")
fh.write(" local_td_n rn;\n")
fh.write(" always @(posedge clk) begin\n")
fh.write(" r0 <= '0;\n")
fh.write(" rn <= '0;\n")
fh.write(" end\n")
fh.write("endmodule\n\n")
# Top module with two instances using different parameters
fh.write("module t (input logic clk);\n")
fh.write(" iface_many_types #(.CFG(16)) ifc_a();\n")
fh.write(" iface_many_types #(.CFG(32)) ifc_b();\n")
fh.write(" sub sub_a (.ifc(ifc_a), .clk(clk));\n")
fh.write(" sub sub_b (.ifc(ifc_b), .clk(clk));\n")
fh.write(" initial begin\n")
fh.write(' $write("*-* All Finished *-*\\n");\n')
fh.write(" $finish;\n")
fh.write(" end\n")
fh.write("endmodule\n")
gen(test.top_filename, N_TYPEDEFS)
test.timeout(MAX_COMPILE_SECS)
t0 = time.time()
test.compile(verilator_flags2=["-x-assign fast --x-initial fast"])
elapsed = time.time() - t0
print("t_iface_typedef_scale: {} typedefs compiled in {:.3f}s (limit {:.1f}s)".format(
N_TYPEDEFS, elapsed, MAX_COMPILE_SECS))
test.execute()
test.passes()

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Test for MemberDType fixup in type table
#
# 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: 2025 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_iface_typedef_struct_member.v"
test.compile(v_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,80 @@
// DESCRIPTION: Verilator: Test for MemberDType fixup and fixDeadRefs in type table (coverage)
//
// Parameterized interface with struct/union typedefs, instantiated in two
// modules. The sub-module uses $bits() on a typedef, which forces widthing
// of the template's type chain during V3Param. This moves template RefDTypes
// into the global type table before the template dies, exercising
// fixDeadRefsInTypeTable and the MemberDType fixup in V3LinkDotIfaceCapture.
//
// 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: 2025 Wilson Snyder
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
// verilog_format: off
`define stop $stop
`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package cfg_pkg;
typedef struct packed {
int unsigned NumUnits;
int unsigned LineSize;
} cfg_t;
endpackage
// Parameterized interface with nested struct/union typedefs.
// The struct-in-union pattern triggers MemberDType fixup (line 1056).
// The $bits() usage in sub_mod forces widthing during V3Param, moving
// template RefDTypes into the type table before the template dies.
interface types_if #(parameter cfg_pkg::cfg_t cfg = 0)();
typedef logic [$clog2(cfg.NumUnits)-1:0] idx_t;
typedef struct packed {
logic [31:$clog2(cfg.LineSize)] tag;
logic [$clog2(cfg.LineSize)-1:0] offset;
} addr_t;
typedef struct packed {
logic [3:0] cmd;
union packed {
addr_t addr; // struct-in-union: MemberDType trigger
logic [31:0] raw;
} payload;
} req_t;
endinterface
module sub_mod #(parameter cfg_pkg::cfg_t cfg = 0)();
types_if #(cfg) types();
typedef types.idx_t idx_t;
typedef types.req_t req_t;
typedef types.addr_t addr_t;
localparam int ReqWidth = $bits(req_t);
idx_t s_idx;
req_t s_req;
addr_t s_addr;
endmodule
module t;
localparam cfg_pkg::cfg_t CFG = '{NumUnits: 5, LineSize: 32};
types_if #(CFG) types();
typedef types.req_t req_t;
req_t top_req;
sub_mod #(.cfg(CFG)) sub();
initial begin
#1;
`checkd($bits(top_req), 36);
`checkd($bits(sub.s_req), 36);
`checkd($bits(sub.s_idx), 3);
`checkd($bits(sub.s_addr), 32);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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('vlt')
test.compile(v_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,85 @@
// DESCRIPTION: Verilator: Test for structural disambiguation and wrong-clone
// fixup in V3LinkDotIfaceCapture.
//
// Modeled after Aerial's simple_cache_if pattern:
// - A parameterized interface (sc_if) contains a nested sub-interface (sc_io)
// - The sub-interface has typedefs (addr_t)
// - A wrapper module (sc_wrap) instantiates sc_if and uses its typedefs
// - Two different parameterizations of sc_wrap exist
// - The re-exported typedefs from sc_io create entries where followCellPath
// may fail, triggering the structural disambiguation path
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct packed {
int unsigned AddrBits;
int unsigned DataBits;
} sc_cfg_t;
// Inner types interface - parameterized with struct typedef
interface sc_types_if #(parameter sc_cfg_t cfg = 0)();
typedef logic [cfg.AddrBits-1:0] addr_t;
typedef logic [cfg.DataBits-1:0] data_t;
typedef struct packed {
addr_t addr;
data_t data;
} pkt_t;
endinterface
// Cache interface - wraps types interface and re-exports typedefs
interface sc_if #(parameter sc_cfg_t cfg = 0)();
sc_types_if #(cfg) sc_io();
typedef sc_io.addr_t addr_t;
typedef sc_io.data_t data_t;
typedef sc_io.pkt_t pkt_t;
addr_t rq_addr_i;
endinterface
// Wrapper module that uses the cache interface
module sc_wrap #(parameter sc_cfg_t cfg = 0)();
sc_if #(cfg) cache();
typedef cache.addr_t addr_t;
typedef cache.pkt_t pkt_t;
addr_t local_addr;
pkt_t local_pkt;
assign cache.rq_addr_i = local_addr;
endmodule
// Top-level: two wrappers with DIFFERENT configs
// This creates two clones of sc_if (and sc_types_if) with different params
module t;
localparam sc_cfg_t cfg_narrow = '{AddrBits: 16, DataBits: 32};
localparam sc_cfg_t cfg_wide = '{AddrBits: 32, DataBits: 64};
sc_wrap #(.cfg(cfg_narrow)) narrow();
sc_wrap #(.cfg(cfg_wide)) wide();
initial begin
#1;
// narrow: addr=16, data=32, pkt=16+32=48
`checkd($bits(narrow.local_addr), 16);
`checkd($bits(narrow.local_pkt), 48);
// wide: addr=32, data=64, pkt=32+64=96
`checkd($bits(wide.local_addr), 32);
`checkd($bits(wide.local_pkt), 96);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -11,8 +11,8 @@ import vltest_bootstrap
test.scenarios('simulator_st')
test.compile(fails=True)
test.compile(verilator_flags2=['--binary'])
#test.execute(fails=True)
test.execute()
test.passes()

View File

@ -1,22 +1,10 @@
%Error: t/t_interface_nested_port_array.v:160:5: Interface 'l3_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
160 | l3_if#(W, L0A_W) l3,
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_nested_port_array.v:123:5: Interface 'l2_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3.m_l2'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
123 | l2_if#(W, L0A_W) l2s[1:0],
| ^~~~~
%Error: t/t_interface_nested_port_array.v:91:5: Interface 'l2_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3.m_l2.m_l2b'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
91 | l2_if#(W, L0A_W) l2,
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Internal Error: t/t_interface_nested_port_array.v:27:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry?
: ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1'
27 | interface l2_if #(

View File

@ -1,22 +1,10 @@
%Error: t/t_interface_nested_port_array.v:160:5: Interface 'l3_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
160 | l3_if#(W, L0A_W) l3,
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_nested_port_array.v:123:5: Interface 'l2_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3.m_l2'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
123 | l2_if#(W, L0A_W) l2s[1:0],
| ^~~~~
%Error: t/t_interface_nested_port_array.v:91:5: Interface 'l2_if' not connected as parent's interface not connected
: ... note: In instance 't.m_l3.m_l2.m_l2b'
: ... Perhaps caused by another error on the parent interface that needs resolving
: ... Or, perhaps intended an interface instantiation but are missing parenthesis (IEEE 1800-2023 25.3)?
91 | l2_if#(W, L0A_W) l2,
| ^~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Internal Error: t/t_interface_nested_port_array.v:27:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry?
: ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1'
27 | interface l2_if #(

View File

@ -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()

View File

@ -0,0 +1,186 @@
// 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
//
// Test: nested parameterized interface with struct typedef used as type parameter
//
// Reproduces a bug where specializeNestedIfaceCells causes early
// specialization of a nested interface, leaving PARAMTYPEDTYPE child
// REFDTYPEs pointing to the template struct instead of the clone struct.
// The struct's width then resolves using the template's default parameter
// values instead of the actual overridden values.
//
// Pattern (mirrors Aerial's simple_cache / simple_cache_if / simple_cache_types_if):
// 1. A wrapper interface instantiates a nested types interface
// 2. The types interface computes localparams from cfg (using $clog2/division)
// 3. Those localparams define typedef ranges for struct members
// 4. A module receives the wrapper interface as a port, also instantiates
// the types interface locally, and uses the struct typedef
// 5. Both the wrapper and the module use the same cfg, so they share
// the same types_if clone via "De-parameterize to prev"
package cfg_pkg;
typedef struct packed {
logic [31:0] AddrBits;
logic [31:0] Capacity;
logic [31:0] LineSize;
logic [31:0] Associativity;
} cfg_t;
endpackage
// Nested types interface: derives struct typedef from computed localparams
interface types_if #(
parameter cfg_pkg::cfg_t cfg = '0
)();
// Computed localparams - these use division and $clog2 of cfg fields.
// With default cfg='0, these produce X/undefined values.
localparam int NUM_LINES = cfg.Capacity / cfg.LineSize;
localparam int LINES_PER_WAY = NUM_LINES / cfg.Associativity;
localparam int BLOCK_BITS = $clog2(cfg.LineSize);
localparam int ROW_BITS = $clog2(LINES_PER_WAY);
localparam int TAG_BITS = cfg.AddrBits - ROW_BITS - BLOCK_BITS;
typedef logic [TAG_BITS-1:0] tag_t;
typedef logic [ROW_BITS-1:0] row_t;
typedef logic [BLOCK_BITS-1:0] block_t;
typedef struct packed {
logic vld;
tag_t tag;
row_t row;
block_t block;
} entry_t;
endinterface
// Wrapper interface: instantiates types_if as a nested cell
// (mirrors simple_cache_if which instantiates simple_cache_types_if)
interface wrapper_if #(
parameter cfg_pkg::cfg_t cfg = '0
)();
types_if #(cfg) types();
typedef types.tag_t tag_t;
logic req_vld;
tag_t req_tag;
endinterface
// Sub-module parameterized by entry width
// (mirrors flop_nr / sram_generic_1r1w parameterized by $bits(sc_tag_t))
module entry_store #(
parameter int ENTRY_WIDTH = 8,
parameter int DEPTH = 4
)(
input logic clk,
input logic wr_en,
input logic [ENTRY_WIDTH-1:0] wr_data,
output logic [ENTRY_WIDTH-1:0] rd_data
);
logic [ENTRY_WIDTH-1:0] mem [DEPTH];
always_ff @(posedge clk) begin
if (wr_en) mem[0] <= wr_data;
end
assign rd_data = mem[0];
endmodule
// Inner module: receives wrapper_if as port, instantiates types_if locally,
// uses struct typedef from types_if
// (mirrors simple_cache which receives simple_cache_if, instantiates
// simple_cache_types_if, and uses types.sc_tag_t)
module inner_mod #(
parameter cfg_pkg::cfg_t cfg = '0
)(
input logic clk,
wrapper_if io
);
// Local instantiation of types_if - same cfg, so gets same clone
// as the one inside wrapper_if via "De-parameterize to prev"
types_if #(cfg) types();
typedef types.entry_t entry_t;
typedef types.tag_t tag_t;
entry_t wr_entry;
entry_t rd_entry;
assign wr_entry.vld = io.req_vld;
assign wr_entry.tag = io.req_tag;
assign wr_entry.row = '0;
assign wr_entry.block = '0;
// Use $bits of the struct typedef as a value parameter to sub-module.
// This is the critical pattern: $bits(entry_t) must resolve using the
// clone's struct (correct width), not the template's (zero/X width).
entry_store #(
.ENTRY_WIDTH($bits(entry_t)),
.DEPTH(8)
) u_store (
.clk(clk),
.wr_en(io.req_vld),
.wr_data(wr_entry),
.rd_data(rd_entry)
);
endmodule
// Outer wrapper module: instantiates wrapper_if and inner_mod
// (mirrors mblit_simple_cache_wrap)
module outer_mod #(
parameter cfg_pkg::cfg_t cfg = '0
)(
input logic clk
);
wrapper_if #(cfg) wif();
inner_mod #(cfg) u_inner (
.clk(clk),
.io(wif)
);
endmodule
module t;
logic clk = 0;
always #5 clk = ~clk;
int cyc = 0;
// Non-default config:
// AddrBits=64, Capacity=1024, LineSize=64, Associativity=2
// NUM_LINES = 1024/64 = 16
// LINES_PER_WAY = 16/2 = 8
// BLOCK_BITS = $clog2(64) = 6
// ROW_BITS = $clog2(8) = 3
// TAG_BITS = 64 - 3 - 6 = 55
// entry_t = 1 + 55 + 3 + 6 = 65 bits
localparam cfg_pkg::cfg_t MY_CFG = '{
AddrBits: 64,
Capacity: 1024,
LineSize: 64,
Associativity: 2
};
outer_mod #(.cfg(MY_CFG)) u_outer (.clk(clk));
always @(posedge clk) begin
cyc <= cyc + 1;
u_outer.wif.req_vld <= (cyc[0] == 1'b1);
u_outer.wif.req_tag <= 55'(cyc);
if (cyc > 5) begin
// Verify the struct round-trips correctly
if (u_outer.u_inner.rd_entry.vld !== 1'b1 && cyc > 10) begin
$display("FAIL cyc=%0d: rd_entry.vld=%b expected 1",
cyc, u_outer.u_inner.rd_entry.vld);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,40 @@
// 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
typedef struct {
int BAR_INT;
bit BAR_BIT;
byte BAR_ARRAY [0:3];
} foo_t;
interface intf
#(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}})
();
endinterface
module sub (intf the_intf_port [4]);
localparam int intf_foo_bar_int = the_intf_port[0].FOO.BAR_INT;
initial begin
#1;
if (intf_foo_bar_int != 4) $stop;
end
endmodule
module t ();
intf the_intf [4] ();
sub
the_sub (
.the_intf_port (the_intf)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -31,8 +31,8 @@ module top();
.p_dwidth(8)
) if0();
localparam p0_rq_t = if0.rq_t;
localparam p0_rs_t = if0.rs_t;
localparam type p0_rq_t = if0.rq_t;
localparam type p0_rs_t = if0.rs_t;
p0_rq_t rq;
p0_rs_t rs;

View File

@ -0,0 +1,5 @@
%Error: t/t_lparam_assign_iface_typedef_bad.v:28:3: Expecting a data type: 'p0_rq_t'
28 | p0_rq_t rq;
| ^~~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -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: 2025 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()

View File

@ -0,0 +1,29 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2025 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
//
// localparam without 'type' keyword should error, not silently ignore.
// Correct syntax is: localparam type p0_rq_t = if0.rq_t;
interface x_if #(
parameter int p_awidth = 4,
parameter int p_dwidth = 7
)();
typedef struct packed {
logic [p_awidth-1:0] addr;
logic [p_dwidth-1:0] data;
} rq_t;
endinterface
module t();
x_if #(
.p_awidth(16),
.p_dwidth(8)
) if0();
localparam p0_rq_t = if0.rq_t; // Bad: missing 'type' keyword
p0_rq_t rq; // Should fail: p0_rq_t is not a data type
endmodule

View File

@ -37,7 +37,7 @@ module top();
.p_dwidth(8)
) if0();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
initial begin
#1;

View File

@ -39,7 +39,7 @@ module top ();
.p_dwidth(8)
) if0 ();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
p0_rq2_t p0_rq2;

View File

@ -40,9 +40,9 @@ module top ();
.p_dwidth(8)
) if0 ();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam p0_rq_t = if0.rq_t;
localparam p0_rs_t = if0.rs_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq_t = if0.rq_t;
localparam type p0_rs_t = if0.rs_t;
p0_rq2_t p0_rq2;
p0_rq_t p0_rq;

View File

@ -46,10 +46,10 @@ module top ();
.p_dwidth(8)
) if0 ();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam p0_rq_t = if0.rq_t;
localparam p0_rs_t = if0.rs_t;
localparam p0_req_t = if0.z_if0.req_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq_t = if0.rq_t;
localparam type p0_rs_t = if0.rs_t;
localparam type p0_req_t = if0.z_if0.req_t;
p0_rq2_t p0_rq2;
p0_rq_t p0_rq;

View File

@ -20,7 +20,7 @@ endinterface
module a_mod(
bus_if bus_io
);
localparam bus_rq_t = bus_io.rq_t;
localparam type bus_rq_t = bus_io.rq_t;
endmodule
module top();

View File

@ -28,8 +28,8 @@ endinterface
module a_mod (
bus_if bus_io
);
localparam bus_rq_t = bus_io.rq_t;
localparam bus_rs_t = bus_io.rs_t;
localparam type bus_rq_t = bus_io.rq_t;
localparam type bus_rs_t = bus_io.rs_t;
localparam p_awidth = bus_io.p_awidth;
localparam p_dwidth = bus_io.p_dwidth;

View File

@ -36,9 +36,9 @@ module top ();
.p_dwidth(8)
) if0 ();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam p0_rq_t = if0.rq_t;
localparam p0_rs_t = if0.rs_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq_t = if0.rq_t;
localparam type p0_rs_t = if0.rs_t;
p0_rq2_t p0_rq2;
p0_rq_t p0_rq;

View File

@ -37,8 +37,8 @@ module a_mod #(
) (
bus_if bus_io
);
localparam bus_rq_t = bus_io.rq_t;
localparam bus_rs_t = bus_io.rs_t;
localparam type bus_rq_t = bus_io.rq_t;
localparam type bus_rs_t = bus_io.rs_t;
bus_rq_t rq;
bus_rs_t rs;

View File

@ -54,10 +54,10 @@ module top ();
x_if #(.CFG(CFG)) if0 ();
localparam p0_rq2_t = if0.y_if0.rq2_t;
localparam p0_rq_t = if0.rq_t;
localparam p0_rs_t = if0.rs_t;
localparam p0_req_t = if0.z_if0.req_t;
localparam type p0_rq2_t = if0.y_if0.rq2_t;
localparam type p0_rq_t = if0.rq_t;
localparam type p0_rs_t = if0.rs_t;
localparam type p0_req_t = if0.z_if0.req_t;
p0_rq2_t p0_rq2;
p0_rq_t p0_rq;

View File

@ -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()

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct {
int BAR_INT;
bit BAR_BIT;
byte BAR_ARRAY [0:3];
} foo_t;
interface intf
#(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}})
();
endinterface
module sub (intf single_intf_port);
localparam byte single_foo_bar_byte = single_intf_port.FOO.BAR_ARRAY[3];
initial begin
#1;
`checkd(single_foo_bar_byte, 8'd8);
end
endmodule
module t ();
intf single_intf ();
sub
the_sub (
.single_intf_port(single_intf)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,47 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct {
int BAR_INT;
bit BAR_BIT;
byte BAR_ARRAY [0:3];
} foo_t;
interface intf
#(parameter foo_t FOO = '{4, 1'b1, '{8'd1, 8'd2, 8'd4, 8'd8}})
();
endinterface
module sub (intf the_intf_port [4]);
localparam int intf_foo_bar_int = the_intf_port[0].FOO.BAR_INT;
initial begin
#1;
`checkd(intf_foo_bar_int, 4);
end
endmodule
module t ();
intf the_intf [4] ();
sub
the_sub (
.the_intf_port (the_intf)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 by Wilson Snyder. This program is free # 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()

View File

@ -0,0 +1,72 @@
// DESCRIPTION: Verilator: Multiple interface instances with different params
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p * 2;
typedef logic [LP0-1:0] a_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP_MUL = cfg.ABits * cfg.BBits;
localparam int LP_ADD = cfg.ABits + cfg.BBits;
a_if #(LP_MUL) types_mul();
a_if #(LP_ADD) types_add();
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.types_mul.a_t a_mul_t;
typedef io.types_add.a_t a_add_t;
initial begin
#1;
// cfg.ABits=2, cfg.BBits=3
// LP_MUL=6 -> a_p=6 -> LP0=12 -> a_mul_t is 12 bits
// LP_ADD=5 -> a_p=5 -> LP0=10 -> a_add_t is 10 bits
`checkd($bits(a_mul_t), 12);
`checkd($bits(a_add_t), 10);
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,82 @@
// DESCRIPTION: Verilator: Cross-interface typedef references
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p * 2;
typedef logic [LP0-1:0] a_t;
endinterface
interface b_if #(
parameter b_p = 0
)();
localparam int LP0 = b_p + 3;
typedef logic [LP0-1:0] b_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_inst();
b_if #(LP0) b_inst();
typedef a_inst.a_t a_t;
typedef b_inst.b_t b_t;
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.a_t a_t;
typedef io.b_t b_t;
initial begin
#1;
// cfg.ABits=2, cfg.BBits=3 -> LP0=6
// a_if: a_p=6 -> LP0=12 -> a_t is 12 bits
// b_if: b_p=6 -> LP0=9 -> b_t is 9 bits
`checkd(12, $bits(a_t));
`checkd(9, $bits(b_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,90 @@
// DESCRIPTION: Verilator: 4-level deep nested interface typedef with dependent localparams
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
// Level 4: innermost interface
interface d_if #(
parameter d_p = 0
)();
localparam int LP0 = d_p + 1;
typedef logic [LP0-1:0] d_t;
endinterface
// Level 3
interface c_if #(
parameter c_p = 0
)();
localparam int LP0 = c_p * 2;
d_if #(LP0) d_inst();
typedef d_inst.d_t c_t;
endinterface
// Level 2
interface b_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP0 = cfg.ABits * cfg.BBits;
c_if #(LP0) c_inst();
typedef c_inst.c_t b_t;
endinterface
// Level 1: outermost interface
interface a_if #(
parameter scp::cfg_t cfg = 0
)();
b_if #(cfg) b_inst();
typedef b_inst.b_t a_t;
endinterface
module m #(parameter scp::cfg_t cfg=0) (
a_if io
);
typedef io.a_t a_t;
initial begin
#1;
// cfg.ABits=2, cfg.BBits=3
// b_if: LP0 = 2*3 = 6
// c_if: c_p=6, LP0 = 6*2 = 12
// d_if: d_p=12, LP0 = 12+1 = 13
// d_t is 13 bits
`checkd($bits(a_t), 13);
end
endmodule
module t();
localparam scp::cfg_t cfg = '{
ABits : 2,
BBits : 3
};
a_if #(cfg) a_io ();
m #(cfg) m_inst(
.io(a_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,107 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package sc;
typedef struct packed {
int unsigned CmdTagBits;
int unsigned Associativity;
int unsigned Capacity;
int unsigned LineSize;
int unsigned StateBits;
int unsigned AddrBits;
int unsigned MissQSize;
// fetch (hit) width. this must be >= to refill width. FgWidth / RefillWidth is the number of array slices for data.
int unsigned FgWidth;
// number of expected beats for refill is LineSize/RefillWidth
int unsigned RefillWidth;
} cfg_t;
endpackage
interface simple_cache_types_if #(
parameter sc::cfg_t cfg = 0
)();
localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize;
localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity;
localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize);
localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY);
localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS;
localparam int SC_DROWS_PER_LINE = cfg.LineSize / cfg.FgWidth;
localparam int SC_NUM_DROWS = SC_NUM_LINES * SC_DROWS_PER_LINE;
endinterface
interface simple_cache_if #(
parameter sc::cfg_t cfg = 0
)();
simple_cache_types_if #(cfg) types();
endinterface
module simple_cache #(parameter sc::cfg_t cfg=0) (
simple_cache_if io
);
localparam num_rld_beats = cfg.LineSize / cfg.RefillWidth;
localparam num_arrays = cfg.FgWidth / cfg.RefillWidth;
localparam dat_array_width = cfg.RefillWidth*8;
localparam int SC_DROWS_PER_LINE = io.types.SC_DROWS_PER_LINE;
localparam int SC_NUM_LINES = io.types.SC_NUM_LINES;
localparam int SC_LINES_PER_WAY = io.types.SC_LINES_PER_WAY;
localparam int SC_NUM_DROWS = io.types.SC_NUM_DROWS;
initial begin
#1;
`checkd(SC_DROWS_PER_LINE, 4);
`checkd(SC_NUM_LINES, 16);
`checkd(SC_LINES_PER_WAY, 8);
`checkd(SC_NUM_DROWS, 64);
`checkd(num_rld_beats, 8);
`checkd(num_arrays, 2);
`checkd(dat_array_width, 64);
end
endmodule
module t();
localparam sc::cfg_t sc_cfg = '{
CmdTagBits : $clog2(6),
Associativity : 2,
Capacity : 1024,
LineSize : 64,
StateBits : 2,
AddrBits : 64,
MissQSize : 2,
FgWidth : 16,
RefillWidth : 8
};
simple_cache_if #(sc_cfg) sc_io ();
simple_cache #(sc_cfg) simple_cache(
.io(sc_io)
);
localparam int SC_DROWS_PER_LINE = sc_io.types.SC_DROWS_PER_LINE;
initial begin
#2;
`checkd(SC_DROWS_PER_LINE, 4);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,85 @@
// DESCRIPTION:
// Combined regression model mixing PIN-assigned type param
// (t_interface_derived_type) and nested captured typedef flows
// (t_lparam_dep_iface*). Keeps both types in a single file.
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} scp_cfg_t;
interface a_if #(parameter a_p = 0)();
localparam int LP0 = a_p * 2;
typedef logic [LP0-1:0] a_t;
endinterface
interface sct_if #(parameter scp_cfg_t cfg = 0)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_if0();
typedef a_if0.a_t a_t; // Captured typedef from nested interface
endinterface
interface intf #(
parameter type data_t = bit,
parameter int arr[2][4]
) ();
data_t data;
logic [$bits(data)-1:0] other_data;
endinterface
module sub #(
parameter int width,
parameter int arr[2][4]
) ();
typedef struct packed {
logic [3:3] [0:0] [width-1:0] field;
} user_type_t;
// This has a PIN that assigns data_t
intf #(
.data_t(user_type_t),
.arr(arr)
) the_intf ();
logic [width-1:0] signal;
always_comb begin
the_intf.data.field = signal;
the_intf.other_data = signal;
end
endmodule
module t ();
localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3};
sct_if #(sc_cfg) types ();
sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 ();
sub #(.width(16), .arr('{'{16, 2, 3, 4}, '{1, 2, 3, 4}})) sub16 ();
typedef types.a_if0.a_t a_t;
typedef types.a_t a2_t;
initial begin
#1;
`checkd(12, $bits(a_t));
`checkd(12, $bits(a2_t));
`checkd(8, $bits(sub8.the_intf.data));
`checkd(16, $bits(sub16.the_intf.data));
#1;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,101 @@
// 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
//
// DESCRIPTION:
// Combined test mixing PIN-assigned type param interface
// (t_interface_derived_type) with nested captured typedef/localparam
// (t_lparam_dep_iface6).
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} scp_cfg_t;
interface a_if #(parameter a_p = 0)();
localparam int LP0 = a_p;
typedef logic [LP0-1:0] a_t;
endinterface
interface sct_if #(parameter scp_cfg_t cfg = 0)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_if0();
typedef a_if0.a_t a_t;
endinterface
interface sc_if #(parameter scp_cfg_t cfg = 0)();
sct_if #(cfg) types();
typedef types.a_t a_t;
endinterface
interface intf #(
parameter type data_t = bit,
parameter int arr[2][4]
) ();
data_t data;
logic [$bits(data)-1:0] other_data;
endinterface
module sub #(
parameter int width,
parameter int arr[2][4]
) ();
typedef struct packed {
logic [3:3] [0:0] [width-1:0] field;
} user_type_t;
intf #(
.data_t(user_type_t),
.arr(arr)
) the_intf ();
logic [width-1:0] signal;
always_comb begin
the_intf.data.field = signal;
the_intf.other_data = signal;
end
endmodule
module sc #(parameter scp_cfg_t cfg=0) (
sc_if io
);
typedef io.a_t a_t;
initial begin
#1;
`checkd($bits(a_t), 6);
end
endmodule
module t ();
localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc_inst (.io(sc_io));
sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 ();
sub #(.width(16), .arr('{'{16, 2, 3, 4}, '{1, 2, 3, 4}})) sub16 ();
typedef sc_io.types.a_if0.a_t inner_t;
typedef sc_io.types.a_t mid_t;
initial begin
#1;
`checkd($bits(inner_t), 6);
`checkd($bits(mid_t), 6);
`checkd($bits(sub8.the_intf.data), 8);
`checkd($bits(sub16.the_intf.data), 16);
#1;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,98 @@
// 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
//
// DESCRIPTION:
// Combined test mixing PIN-assigned type param interface (t_interface_derived_type)
// with nested captured typedef/localparam (t_lparam_dep_iface6).
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} scp_cfg_t;
interface a_if #(parameter a_p = 0)();
localparam int LP0 = a_p;
typedef logic [LP0-1:0] a_t;
endinterface
interface sct_if #(parameter scp_cfg_t cfg = 0)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_if0();
typedef a_if0.a_t a_t;
endinterface
interface sc_if #(parameter scp_cfg_t cfg = 0)();
sct_if #(cfg) types();
typedef types.a_t a_t;
endinterface
interface intf #(
parameter type data_t = bit,
parameter int arr[2][4]
) ();
data_t data;
logic [$bits(data)-1:0] other_data;
endinterface
module sub #(
parameter int width,
parameter int arr[2][4]
) ();
typedef struct packed {
logic [3:3] [0:0] [width-1:0] field;
} user_type_t;
intf #(
.data_t(user_type_t),
.arr(arr)
) the_intf ();
logic [width-1:0] signal;
always_comb begin
the_intf.data.field = signal;
the_intf.other_data = signal;
end
endmodule
module sc #(parameter scp_cfg_t cfg=0) (
sc_if io
);
typedef io.a_t a_t;
initial begin
#1;
`checkd($bits(a_t), 6);
end
endmodule
module t (input clk);
localparam scp_cfg_t sc_cfg = '{ABits: 2, BBits: 3};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc_inst (.io(sc_io));
sub #(.width(8), .arr('{'{8, 2, 3, 4}, '{1, 2, 3, 4}})) sub8 ();
typedef sc_io.types.a_if0.a_t inner_t;
typedef sc_io.types.a_t mid_t;
initial begin
#1;
`checkd($bits(inner_t), 6);
`checkd($bits(mid_t), 6);
`checkd($bits(sub8.the_intf.data), 8);
#1;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,90 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package p1;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
package p2;
typedef struct packed {
int unsigned CBits;
int unsigned DBits;
} cfg_t;
endpackage
interface types_if #(parameter p1::cfg_t cfg=0)();
localparam int ABits = cfg.ABits;
localparam int BBits = cfg.BBits;
typedef struct packed {
logic [cfg.ABits-1:0] a;
logic [cfg.BBits-1:0] b;
} a_t;
endinterface
interface io_if #(parameter p1::cfg_t cfg=0)();
localparam int ABits = cfg.ABits;
localparam int BBits = cfg.BBits;
types_if #(cfg) types ();
typedef types.a_t a_t;
endinterface
module modA(
io_if io
);
localparam int ABits = io.types.ABits;
localparam int BBits = io.types.BBits;
typedef io.types.a_t a_t;
initial begin
#1;
`checkd(ABits, 8);
`checkd(BBits, 24);
`checkd($bits(a_t), 32);
end
endmodule
module t ();
localparam p2::cfg_t mcfg = '{
CBits : 8,
DBits : 16
};
localparam p1::cfg_t cfg = '{
ABits : mcfg.CBits,
BBits : mcfg.CBits + mcfg.DBits
};
io_if #(cfg) modA_io ();
typedef modA_io.types.a_t a_t;
modA modA_inst (
.io(modA_io)
);
initial begin
#2;
`checkd($bits(a_t), 32);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,157 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package sc;
typedef struct packed {
int unsigned CmdTagBits;
int unsigned Associativity;
int unsigned Capacity;
int unsigned LineSize;
int unsigned StateBits;
int unsigned AddrBits;
int unsigned MissQSize;
// fetch (hit) width. this must be >= to refill width. FgWidth / RefillWidth is the number of array slices for data.
int unsigned FgWidth;
// number of expected beats for refill is LineSize/RefillWidth
int unsigned RefillWidth;
} cfg_t;
endpackage
interface simple_cache_types_if #(
parameter sc::cfg_t cfg = 0
)();
localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize;
localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity;
localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize);
localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY);
localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS;
localparam int SC_DROWS_PER_LINE = cfg.LineSize / cfg.FgWidth;
localparam int SC_NUM_DROWS = SC_NUM_LINES * SC_DROWS_PER_LINE;
typedef logic [cfg.AddrBits-1:0] addr_t;
typedef logic [cfg.Associativity-1:0] assoc_oh_t;
typedef logic [cfg.Associativity-2:0] plru_t;
typedef logic [cfg.StateBits-1:0] state_t;
typedef logic [cfg.CmdTagBits-1:0] cmd_tag_t;
typedef logic [$clog2(cfg.MissQSize)-1:0] missq_tag_t;
typedef logic [SC_TAG_BITS-1:0] tag_t;
typedef logic [SC_ROW_BITS-1:0] row_t;
typedef logic [SC_BLOCK_BITS-1:0] block_t;
typedef logic [$clog2(SC_NUM_DROWS)-1:0] drow_addr_t;
typedef struct packed {
tag_t tag;
row_t row;
block_t block;
} sc_tag_addr_t;
typedef struct packed {
logic vld;
tag_t tag;
state_t state;
} sc_tag_t;
typedef struct packed {
state_t [cfg.Associativity-1:0] state_v;
assoc_oh_t hit_v;
assoc_oh_t vld_v;
plru_t plru;
} sc_tag_status_t;
endinterface
interface simple_cache_if #(
parameter sc::cfg_t cfg = 0
)();
simple_cache_types_if #(cfg) types();
typedef types.cmd_tag_t cmd_tag_t;
typedef types.addr_t addr_t;
typedef types.missq_tag_t missq_tag_t;
endinterface
module simple_cache #(parameter sc::cfg_t cfg=0) (
simple_cache_if io
);
typedef io.types.addr_t addr_t;
typedef io.types.cmd_tag_t cmd_tag_t;
typedef io.types.drow_addr_t drow_addr_t;
typedef io.types.plru_t plru_t;
typedef io.types.row_t row_t;
typedef io.types.state_t state_t;
typedef io.types.sc_tag_addr_t sc_tag_addr_t;
typedef io.types.sc_tag_t sc_tag_t;
typedef io.types.sc_tag_status_t sc_tag_status_t;
localparam num_rld_beats = cfg.LineSize / cfg.RefillWidth;
localparam num_arrays = cfg.FgWidth / cfg.RefillWidth;
localparam dat_array_width = cfg.RefillWidth*8;
localparam int SC_DROWS_PER_LINE = io.types.SC_DROWS_PER_LINE;
localparam int SC_NUM_LINES = io.types.SC_NUM_LINES;
localparam int SC_LINES_PER_WAY = io.types.SC_LINES_PER_WAY;
localparam int SC_NUM_DROWS = io.types.SC_NUM_DROWS;
initial begin
#1;
`checkd(SC_DROWS_PER_LINE, 4);
`checkd(SC_NUM_LINES, 16);
`checkd(SC_LINES_PER_WAY, 8);
`checkd(SC_NUM_DROWS, 64);
`checkd(num_rld_beats, 8);
`checkd(num_arrays, 2);
`checkd(dat_array_width, 64);
end
endmodule
module t();
localparam sc::cfg_t sc_cfg = '{
CmdTagBits : $clog2(6),
Associativity : 2,
Capacity : 1024,
LineSize : 64,
StateBits : 2,
AddrBits : 64,
MissQSize : 2,
FgWidth : 16,
RefillWidth : 8
};
simple_cache_if #(sc_cfg) sc_io ();
simple_cache #(sc_cfg) simple_cache(
.io(sc_io)
);
//localparam int SC_DROWS_PER_LINE = sc_io.types.SC_DROWS_PER_LINE;
//localparam int SC_NUM_LINES = sc_io.types.SC_NUM_LINES;
//localparam int SC_LINES_PER_WAY = sc_io.types.SC_LINES_PER_WAY;
//localparam int SC_NUM_DROWS = sc_io.types.SC_NUM_DROWS;
initial begin
#2;
//`checkd(SC_DROWS_PER_LINE, 4);
//`checkd(SC_NUM_LINES, 16);
//`checkd(SC_LINES_PER_WAY, 8);
//`checkd(SC_NUM_DROWS, 64);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,78 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned Capacity;
int unsigned LineSize;
} cfg_t;
endpackage
interface sct_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize;
typedef logic [(cfg.Capacity / cfg.LineSize)-1:0] sc_num_lines_t;
typedef logic [SC_NUM_LINES-1:0] sc_num_lines_2_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
sct_if #(cfg) types();
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
localparam int SC_NUM_LINES = io.types.SC_NUM_LINES;
typedef io.types.sc_num_lines_t sc_num_lines_t;
typedef io.types.sc_num_lines_2_t sc_num_lines_2_t;
initial begin
#1;
$display("SC_NUM_LINES = %d", SC_NUM_LINES);
$display("bits SC_NUM_LINES = %d", $bits(sc_num_lines_t));
$display("bits SC_NUM_LINES_2 = %d", $bits(sc_num_lines_2_t));
`checkd(SC_NUM_LINES, 16);
`checkd($bits(sc_num_lines_t), 16);
`checkd($bits(sc_num_lines_2_t), 16);
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
Capacity : 1024,
LineSize : 64
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) simple_cache(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,80 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned Associativity;
int unsigned Capacity;
int unsigned LineSize;
int unsigned AddrBits;
} cfg_t;
endpackage
interface sct_if #(
parameter scp::cfg_t cfg = 0
)();
// this is intentional as I want all the dependencies to be resolved
localparam int SC_NUM_LINES = cfg.Capacity / cfg.LineSize;
localparam int SC_LINES_PER_WAY = SC_NUM_LINES / cfg.Associativity;
localparam int SC_BLOCK_BITS = $clog2(cfg.LineSize);
localparam int SC_ROW_BITS = $clog2(SC_LINES_PER_WAY);
localparam int SC_TAG_BITS = cfg.AddrBits - SC_ROW_BITS - SC_BLOCK_BITS;
typedef logic [SC_TAG_BITS-1:0] tag_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
sct_if #(cfg) types();
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
sct_if #(cfg) types();
typedef io.types.tag_t tag_t;
typedef types.tag_t tag2_t;
initial begin
#1;
`checkd(55, $bits(tag_t));
`checkd(55, $bits(tag2_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
Associativity : 2,
Capacity : 1024,
LineSize : 64,
AddrBits : 64
};
sc_if #(sc_cfg) sc_io ();
typedef sc_io.types.tag_t tag_t;
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
`checkd(55, $bits(tag_t));
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,76 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p;
typedef logic [LP0-1:0] a_t;
endinterface
interface sct_if #(
parameter scp::cfg_t cfg = 0
)();
// this is intentional as I want all the dependencies to be resolved
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_if0();
typedef a_if0.a_t a_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
sct_if #(cfg) types();
typedef types.a_t a_t;
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.a_t a_t;
initial begin
#1;
`checkd(6, $bits(a_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,67 @@
// DESCRIPTION: Verilator: Get agregate type parameter from array of interfaces
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p * 2;
typedef logic [LP0-1:0] a_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) types();
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.types.a_t a_t;
initial begin
#1;
`checkd(12, $bits(a_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,77 @@
// DESCRIPTION: Verilator: 3-level nested interface typedef with dependent localparams
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
// Level 3: innermost interface
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p * 2;
typedef logic [LP0-1:0] a_t;
endinterface
// Level 2: middle interface
interface sct_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) a_if0();
typedef a_if0.a_t a_t;
endinterface
// Level 1: outermost interface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
sct_if #(cfg) types();
typedef types.a_t a_t;
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.a_t a_t;
initial begin
#1;
// cfg.ABits=2, cfg.BBits=3 -> LP0=6 -> a_p=6 -> LP0=12 -> a_t is 12 bits
`checkd(12, $bits(a_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,70 @@
// DESCRIPTION: Verilator: Multiple dependent localparams in chain
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package scp;
typedef struct packed {
int unsigned ABits;
int unsigned BBits;
} cfg_t;
endpackage
// Interface with chained localparam dependencies
interface a_if #(
parameter a_p = 0
)();
localparam int LP0 = a_p * 2; // LP0 = a_p * 2
localparam int LP1 = LP0 + 1; // LP1 = LP0 + 1
localparam int LP2 = LP1 * LP0; // LP2 = LP1 * LP0
typedef logic [LP2-1:0] a_t;
endinterface
interface sc_if #(
parameter scp::cfg_t cfg = 0
)();
localparam int LP0 = cfg.ABits * cfg.BBits;
a_if #(LP0) types();
endinterface
module sc #(parameter scp::cfg_t cfg=0) (
sc_if io
);
typedef io.types.a_t a_t;
initial begin
#1;
// cfg.ABits=2, cfg.BBits=3 -> LP0=6
// a_if: a_p=6 -> LP0=12, LP1=13, LP2=156 -> a_t is 156 bits
`checkd(156, $bits(a_t));
end
endmodule
module t();
localparam scp::cfg_t sc_cfg = '{
ABits : 2,
BBits : 3
};
sc_if #(sc_cfg) sc_io ();
sc #(sc_cfg) sc(
.io(sc_io)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,24 @@
#!/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
# Verifies that skipWidthForTemplateStruct fires in V3Param::cellPinCleanup
# when struct typedefs from a nested parameterized interface are passed as
# type parameters through two levels of interface nesting.
import vltest_bootstrap
test.scenarios('vlt')
test.compile(v_flags2=["--binary --stats"])
test.file_grep(test.stats, r'Param, Template struct width skips\s+(\d+)', 2)
test.execute()
test.passes()

View File

@ -0,0 +1,91 @@
// DESCRIPTION: Verilator: Test type parameter from interface struct
//
// 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
// Exercises skipWidthForTemplateStruct in V3Param::cellPinCleanup.
//
// Pattern: nested parameterized interfaces with struct type parameters:
// 1. Parameterized inner interface (inner_if) defines struct typedefs
// 2. Outer interface (outer_if) contains a nested inner_if instance
// 3. A module takes the outer interface as a port and creates typedefs
// through two levels of nesting: port.inner.req_t
// 4. Those struct typedefs are passed as type parameters to an inner module
// 5. A separate module creates inner_if clones with different configs,
// ensuring inner_if gets parameterizedTemplate()=true before the
// type parameter pins are processed
package cfg_pkg;
typedef struct packed {
int unsigned IdBits;
int unsigned DataBits;
} cfg_t;
endpackage
// Parameterized inner interface with struct typedefs
interface inner_if #(parameter cfg_pkg::cfg_t cfg = '0);
typedef struct packed {
logic [cfg.IdBits-1:0] id;
logic [cfg.DataBits-1:0] data;
} req_t;
typedef struct packed {
logic [cfg.IdBits-1:0] id;
logic [1:0] resp;
} resp_t;
req_t req;
resp_t resp;
endinterface
// Outer interface containing a nested inner_if
interface outer_if #(parameter cfg_pkg::cfg_t cfg = '0);
inner_if #(cfg) inner();
endinterface
// Module with type parameters (consumer of struct typedefs)
module typed_mod #(
parameter type req_t = logic,
parameter type resp_t = logic
)(
input logic clk
);
req_t r;
resp_t s;
assign r = '0;
assign s = '0;
endmodule
// Wrapper: takes outer_if ports, typedefs through two-level nesting,
// passes as type parameters to typed_mod
module wrap_mod #(parameter int NUM = 1)(
input logic clk,
outer_if ports [NUM]
);
typedef ports[0].inner.req_t local_req_t;
typedef ports[0].inner.resp_t local_resp_t;
typed_mod #(.req_t(local_req_t), .resp_t(local_resp_t)) u_sub(.clk(clk));
endmodule
module t();
logic clk = 0;
localparam cfg_pkg::cfg_t CFG_A = '{IdBits: 4, DataBits: 32};
localparam cfg_pkg::cfg_t CFG_B = '{IdBits: 8, DataBits: 64};
// Force inner_if to be cloned with different configs first
inner_if #(CFG_A) early_a();
inner_if #(CFG_B) early_b();
assign early_a.req = '0;
assign early_a.resp = '0;
assign early_b.req = '0;
assign early_b.resp = '0;
outer_if #(CFG_A) io [2] ();
wrap_mod #(.NUM(2)) u_wrap(.clk(clk), .ports(io));
initial begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,44 @@
// DESCRIPTION: Verilator: Regression for prelim ASCRANGE on cfg-based interface typedefs
//
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package axis;
typedef struct packed {
int unsigned DataWidth;
} cfg_t;
endpackage
interface axis_if #(parameter axis::cfg_t cfg = '0)();
typedef logic [cfg.DataWidth-1:0] tdata_t;
endinterface
module axis_chan #(
parameter axis::cfg_t chan_cfg = '0
) ();
axis_if #(chan_cfg) axis_channel_io();
typedef axis_channel_io.tdata_t data_t;
localparam int kWidth = $bits(data_t);
initial begin
#1;
`checkd(kWidth,32);
end
endmodule
module t;
localparam axis::cfg_t axis_chan_cfg = '{DataWidth: 32};
axis_chan #(.chan_cfg(axis_chan_cfg)) u_chan();
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,116 @@
// 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
//
// DESCRIPTION: Minimal test for sibling interface typedef resolution
// This is the SIMPLEST case that demonstrates the t_lparam_dep_iface10 failure pattern:
// - Two sibling cells of the same interface type with DIFFERENT parameters
// - A module that accesses typedefs from BOTH siblings
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package rial;
// Configuration structure
typedef struct packed {
// CCA Parameters
int unsigned NumDd;
// CC Parameters
int unsigned DDNumStuff;
int unsigned DDNumStuffThreads;
} cfg_t;
endpackage
package cb;
typedef struct packed {
int unsigned XdatSize; // raw packet data size
} cfg_t;
endpackage
interface ccia_types_if #(parameter rial::cfg_t cfg=0)();
// 'base' types
typedef logic [$clog2(cfg.DDNumStuff)-1:0] wave_index_t;
// types for tb
typedef struct packed {
logic [3:0] e_cmd;
logic en;
logic csr;
wave_index_t wave_index;
logic [11:0] reg_addr;
logic [64-(4+1+1+$clog2(cfg.DDNumStuff)+12)-1:0] pad0;
} tl_reg_cmd_t;
typedef struct packed {
logic [63:0] raw;
} tl_addr_cmd_t;
typedef union packed {
tl_reg_cmd_t rcmd;
tl_addr_cmd_t acmd;
} tl_data_fld_t;
typedef union packed {
tl_data_fld_t [cfg.DDNumStuffThreads-1:0] d_a;
} cmd_data_t;
typedef struct packed {
cmd_data_t d;
} cmd_beat_t;
endinterface
module rial_top #(
parameter rial::cfg_t aer_cfg=0
)();
// for the types
ccia_types_if #(aer_cfg) ccia_types();
// genvars and locally defined types
typedef ccia_types.cmd_beat_t cmd_beat_t;
// CB and RBUS
localparam cb::cfg_t cb_cfg = '{
XdatSize:$bits(cmd_beat_t)
};
initial begin
#1;
`checkd($bits(ccia_types.tl_data_fld_t), 64);
`checkd($bits(ccia_types.cmd_data_t), 512);
`checkd($bits(cmd_beat_t), 512);
`checkd(cb_cfg.XdatSize, 512);
end
endmodule
// SOC Top w/IO and SOC configuration
module rial_wrap();
parameter rial::cfg_t aer_cfg = '{
NumDd : 3,
// CC Parameters
DDNumStuff : 4,
DDNumStuffThreads : 8
};
// DUT
rial_top #(
.aer_cfg(aer_cfg)
) rial_top();
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,69 @@
// 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
//
// DESCRIPTION: Minimal test for sibling interface typedef resolution
// This is the SIMPLEST case that demonstrates the t_lparam_dep_iface10 failure pattern:
// - Two sibling cells of the same interface type with DIFFERENT parameters
// - A module that accesses typedefs from BOTH siblings
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package TestPkg;
// Create a struct that results in 525 bits like in aerial_wrap
typedef struct packed {
logic [31:0] field1;
logic [31:0] field2;
logic [31:0] field3;
logic [31:0] field4;
logic [31:0] field5;
logic [31:0] field6;
logic [31:0] field7;
logic [31:0] field8;
logic [31:0] field9;
logic [31:0] field10;
logic [31:0] field11;
logic [31:0] field12;
logic [31:0] field13;
logic [31:0] field14;
logic [31:0] field15;
logic [31:0] field16;
logic [12:0] field17; // 525 bits total (16*32 + 13)
} cmd_beat_t;
typedef struct packed {
logic [31:0] Rids;
logic [31:0] Pids;
logic [31:0] Fnum;
logic [31:0] XdatSize; // 32-bit field
} cfg_t;
// This pattern assignment should trigger the error
// The issue is that $bits(cmd_beat_t) evaluation during DepGraph causes corruption
// where the pattern literal gets a 128-bit constant instead of proper 32-bit assignment
// Note: cmd_beat_t is referenced directly, not through a localparam type alias
localparam cfg_t cb_cfg = '{
Rids : 32'h1,
Pids : 32'h2,
Fnum : 32'h3,
XdatSize : $bits(cmd_beat_t) // Should be 525, but gets corrupted
};
endpackage
module TestMod;
import TestPkg::*;
initial begin
$display("XdatSize = %d", cb_cfg.XdatSize);
`checkd(cb_cfg.XdatSize, 525);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,81 @@
// 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
// DESCRIPTION: Test for $bits() of interface typedef used as parameter value
// This reproduces the issue from axis_upsizer.sv:186
// The issue: $bits(op_pkt_t) where op_pkt_t is a typedef from an interface port
// can't be converted to constant because the PARAMTYPEDTYPE's dtype isn't resolved
// Interface with a packed struct typedef
interface axis_if #(
parameter int DataWidth = 8
);
typedef struct packed {
logic [DataWidth-1:0] data;
logic valid;
} pkt_t;
logic [DataWidth-1:0] tdata;
logic tvalid;
logic tready;
modport initiator (output tdata, tvalid, input tready);
modport target (input tdata, tvalid, output tready);
endinterface
// Simple buffer module that takes a width parameter
module skid_buffer #(
parameter int p_width = 8
) (
input logic clk,
input logic [p_width-1:0] data_i,
output logic [p_width-1:0] data_o
);
always_ff @(posedge clk) data_o <= data_i;
endmodule
// Module that uses $bits() of an interface typedef as a parameter
module axis_upsizer #(
parameter int p_has_skid = 1
) (
input logic clk,
axis_if.initiator op_io
);
// Typedef from interface port
typedef op_io.pkt_t op_pkt_t;
op_pkt_t op_pkt_int;
generate
if (p_has_skid>0) begin : gen_skid
op_pkt_t skid_src_pkt;
// This is the problematic line - $bits(op_pkt_t) used as parameter
// The PARAMTYPEDTYPE for op_pkt_t has REQUIREDTYPE that needs resolution
skid_buffer #(.p_width($bits(op_pkt_t))) skid (
.clk(clk),
.data_i(op_pkt_int),
.data_o(skid_src_pkt)
);
end
endgenerate
endmodule
module top;
logic clk;
axis_if #(.DataWidth(32)) op_if();
axis_upsizer #(.p_has_skid(1)) u_upsizer (
.clk(clk),
.op_io(op_if.initiator)
);
initial begin
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,81 @@
// 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
// DESCRIPTION: Verilator: Test for cloned RefDType classOrPackagep fix
//
// This test verifies that when parameterized classes are cloned, the RefDType
// nodes that reference typedefs within the class get their classOrPackagep
// and typedefp updated to point to the cloned class, not the template class.
//
// This file is part of the Verilator regression test suite.
//
// A registry class that returns its own type
class uvm_object_registry #(type T = int, string Tname = "<unknown>");
typedef uvm_object_registry#(T, Tname) this_type;
static function this_type get();
static this_type m_inst;
if (m_inst == null) m_inst = new();
return m_inst;
endfunction
endclass
// A pool class that has a nested type_id typedef pointing to the registry
// The key pattern: type_id is a typedef to uvm_object_registry parameterized with THIS class
class uvm_object_string_pool #(type T = int);
typedef uvm_object_string_pool#(T) this_type;
typedef uvm_object_registry#(uvm_object_string_pool#(T)) type_id;
// This function's return type references type_id - after cloning,
// the RefDType for the return type must point to THIS class's type_id,
// not the template class's type_id
static function type_id get_type();
return type_id::get();
endfunction
static function T get_global();
this_type gpool = new();
return gpool.get();
endfunction
virtual function T get();
T result;
return result;
endfunction
endclass
// Simple wrapper classes to create different specializations
class uvm_queue #(type T = int);
endclass
class uvm_event #(type T = int);
endclass
// Create two different specializations of uvm_object_string_pool
// Each should get its own type_id pointing to a different registry specialization
typedef uvm_object_string_pool#(uvm_event#(int)) uvm_event_pool;
typedef uvm_object_string_pool#(uvm_queue#(string)) uvm_queue_pool;
module t;
initial begin
// Get the type_id::get() for both pool types
// Before the fix, both would incorrectly return the same registry type
// After the fix, each returns its own correctly-specialized registry
uvm_object_registry#(uvm_object_string_pool#(uvm_event#(int))) event_reg;
uvm_object_registry#(uvm_object_string_pool#(uvm_queue#(string))) queue_reg;
event_reg = uvm_event_pool::get_type();
queue_reg = uvm_queue_pool::get_type();
if (event_reg != null && queue_reg != null) begin
$write("*-* All Coverage Coverage *-*\n");
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,120 @@
// 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
//
// DESCRIPTION: Verilator: TRULY BLENDED test for interface typedef resolution
// This test BLENDS both patterns into a single interacting structure:
// - Sibling cells (like t_lparam_dep_iface10)
// - Nested interface chains (like aerial_wrap)
// - COMBINED: Sibling cells that EACH contain nested interface chains
//
// The key test: A module accesses typedefs from TWO sibling nested interface
// chains, and each must resolve to the correct parameterized type.
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
typedef struct packed {
int unsigned AddrBits;
int unsigned DataBits;
int unsigned IdBits;
} axi_cfg_t;
// INNERMOST: Parameterized interface with typedefs
interface axi4_if #(parameter axi_cfg_t cfg = 0)();
localparam int unsigned AddrBits = cfg.AddrBits * 2;
localparam int unsigned DataBits = cfg.DataBits * 2;
localparam int unsigned IdBits = cfg.IdBits * 2;
typedef logic [AddrBits-1:0] addr_t;
typedef logic [DataBits-1:0] data_t;
typedef logic [IdBits-1:0] id_t;
typedef struct packed {
id_t id;
addr_t addr;
} ar_chan_t;
typedef struct packed {
id_t id;
data_t data;
} r_chan_t;
ar_chan_t ar;
r_chan_t r;
endinterface
// MIDDLE: Interface that wraps axi4_if and re-exports its typedefs
interface tlb_io_if #(parameter axi_cfg_t axi_cfg = 0)();
axi4_if #(.cfg(axi_cfg)) axi_tlb_io();
// Re-export typedefs from nested interface
typedef axi_tlb_io.r_chan_t r_chan_t;
typedef axi_tlb_io.ar_chan_t ar_chan_t;
endinterface
// OUTER: Interface with TWO SIBLING tlb_io_if instances with DIFFERENT params
// This is the BLENDED pattern: sibling cells + nested chains
interface cca_io_if #(
parameter axi_cfg_t axi_cfg_a = 0,
parameter axi_cfg_t axi_cfg_b = 0
)();
// SIBLING CELLS - same interface type, DIFFERENT params
tlb_io_if #(.axi_cfg(axi_cfg_a)) tlb_io_a();
tlb_io_if #(.axi_cfg(axi_cfg_b)) tlb_io_b();
// Re-export from each sibling (these should be DIFFERENT types)
typedef tlb_io_a.r_chan_t r_chan_a_t;
typedef tlb_io_b.r_chan_t r_chan_b_t;
endinterface
// MODULE: Accesses typedefs from BOTH sibling nested chains via interface port
// This is the CRITICAL test - must distinguish between tlb_io_a and tlb_io_b
module cca_xbar (
cca_io_if cca_io
);
// Access typedefs through SIBLING nested interface chains
// These MUST resolve to DIFFERENT types based on the different params
typedef cca_io.tlb_io_a.r_chan_t m_r_chan_a_t; // From axi_cfg_a
typedef cca_io.tlb_io_b.r_chan_t m_r_chan_b_t; // From axi_cfg_b
typedef cca_io.tlb_io_a.ar_chan_t m_ar_chan_a_t;
typedef cca_io.tlb_io_b.ar_chan_t m_ar_chan_b_t;
m_r_chan_a_t r_data_a;
m_r_chan_b_t r_data_b;
initial begin
#1;
// axi_cfg_a: AddrBits=32, DataBits=64, IdBits=4
// r_chan_t = id(4) + data(64) = 68 bits * 2 = 136 bits
// ar_chan_t = id(4) + addr(32) = 36 bits * 2 = 72 bits
`checkd($bits(m_r_chan_a_t), 136);
`checkd($bits(m_ar_chan_a_t), 72);
// axi_cfg_b: AddrBits=40, DataBits=128, IdBits=8
// r_chan_t = id(8) + data(128) = 136 bits * 2 = 272 bits
// ar_chan_t = id(8) + addr(40) = 48 bits * 2 = 96 bits
`checkd($bits(m_r_chan_b_t), 272);
`checkd($bits(m_ar_chan_b_t), 96);
end
endmodule
// TOP MODULE
module t();
localparam axi_cfg_t cfg_a = '{AddrBits: 32, DataBits: 64, IdBits: 4};
localparam axi_cfg_t cfg_b = '{AddrBits: 40, DataBits: 128, IdBits: 8};
cca_io_if #(.axi_cfg_a(cfg_a), .axi_cfg_b(cfg_b)) cca_io();
cca_xbar xbar(.cca_io(cca_io));
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,30 @@
#!/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
# Stats-variant of t_paramgraph_comined_iface: verifies IfaceCapture
# statistics for combined sibling + nested interface typedef patterns.
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_paramgraph_comined_iface.v"
test.compile(v_flags2=["--binary --stats"])
test.file_grep(test.stats, r'IfaceCapture, Entries total\s+(\d+)', 18)
test.file_grep(test.stats, r'IfaceCapture, Entries template\s+(\d+)', 8)
test.file_grep(test.stats, r'IfaceCapture, Entries cloned\s+(\d+)', 10)
test.file_grep(test.stats, r'IfaceCapture, Ledger fixups in V3Param\s+(\d+)', 10)
test.file_grep(test.stats, r'IfaceCapture, Wrong-clone refs fixed\s+(\d+)', 10)
test.file_grep(test.stats, r'IfaceCapture, Dead refs fixed in modules\s+(\d+)', 6)
test.execute()
test.passes()

View File

@ -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()

View File

@ -0,0 +1,56 @@
// 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
//
// DESCRIPTION:
// Minimal testcase for depgraph resolution with arrayed interface ports
// and typedefs pulled from interface instances.
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package axi_pkg;
typedef struct packed {
int IdBits;
int DataBits;
int UserBits;
} cfg_t;
endpackage
interface axi4_if #(parameter axi_pkg::cfg_t cfg = '0)();
typedef logic [cfg.IdBits-1:0] id_t;
typedef logic [cfg.DataBits-1:0] data_t;
typedef logic [cfg.UserBits-1:0] user_t;
typedef struct packed {
id_t id;
data_t data;
user_t user;
} req_t;
endinterface
module sink #(parameter int N = 1)(axi4_if tgt_ports [N-1:0]);
localparam type req_t = tgt_ports[0].req_t;
req_t rq;
endmodule
module top;
localparam axi_pkg::cfg_t cfg = '{IdBits:4, DataBits:32, UserBits:2};
axi4_if #(.cfg(cfg)) tgt_ports [1:0]();
sink #(.N(2)) u_sink(.tgt_ports(tgt_ports));
initial begin
#1;
`checkd($bits(tgt_ports[0].id_t), 4);
`checkd($bits(tgt_ports[0].data_t), 32);
`checkd($bits(tgt_ports[0].user_t), 2);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,49 @@
// 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
// DESCRIPTION:
// Minimal testcase for depgraph interface typedef resolution
// Derived from aicc_types_if/axis_if ASCRANGE warnings
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package aerial;
typedef struct packed {
int NumCc;
int CCNumWaves;
int CCNumIds;
} cfg_t;
endpackage
interface aicc_types_if #(parameter aerial::cfg_t cfg = '0)();
typedef logic [$clog2(cfg.NumCc)-1:0] cc_index_t;
typedef logic [$clog2(cfg.CCNumIds)-1:0] trans_id_t;
endinterface
module child(aicc_types_if types);
localparam type cc_index_t = types.cc_index_t;
localparam type trans_id_t = types.trans_id_t;
cc_index_t cc_idx;
trans_id_t tr_id;
endmodule
module top;
localparam aerial::cfg_t aer_cfg = '{NumCc:4, CCNumWaves:2, CCNumIds:8};
aicc_types_if #(.cfg(aer_cfg)) types();
child u_child(.types(types));
initial begin
#2;
`checkd($bits(types.cc_index_t), 2);
`checkd($bits(types.trans_id_t), 3);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,61 @@
// 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
//
// DESCRIPTION:
// Minimal testcase to see if unused modules with interface ports
// still trigger ASCRANGE when interface params are defaulted.
//
package axi4;
typedef struct packed {
int IdBits;
int AddrBits;
int DataBits;
int UserBits;
} cfg_t;
endpackage
interface axi4_if #(parameter axi4::cfg_t cfg = '0)();
typedef logic [cfg.AddrBits-1:0] addr_t;
typedef logic [cfg.DataBits-1:0] data_t;
typedef logic [cfg.DataBits/8-1:0] strb_t;
typedef logic [cfg.UserBits-1:0] user_t;
typedef logic [cfg.IdBits-1:0] id_t;
typedef struct packed {
id_t id;
addr_t addr;
user_t user;
} aw_chan_t;
endinterface
module dead_mod(
axi4_if axi_io
);
typedef axi_io.addr_t addr_t;
typedef axi_io.data_t data_t;
typedef axi_io.strb_t strb_t;
addr_t addr_d;
data_t data_d;
strb_t strb_d;
endmodule
module dead_top;
localparam axi4::cfg_t cfg = '{IdBits:4, AddrBits:32, DataBits:64, UserBits:2};
axi4_if #(.cfg(cfg)) axi_io();
dead_mod u_dead(.axi_io(axi_io));
endmodule
module top;
dead_top dead_top();
initial begin
#1;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,55 @@
// 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
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package a_pkg;
typedef struct packed {
int unsigned a;
} cfg_t;
endpackage
interface depgraph_if #(a_pkg::cfg_t cfg=0)();
typedef logic [cfg.a-1:0] byte_t;
typedef struct packed {
byte_t a;
} pair_t;
endinterface
module a_mod(
depgraph_if ifc
);
typedef ifc.pair_t pair_t;
localparam p_a = $bits(pair_t);
initial begin
#1;
`checkd($bits(pair_t),8);
`checkd(p_a, 8);
end
endmodule
module t();
localparam a_pkg::cfg_t cfg = '{
a: 8
};
depgraph_if #(cfg) ifc();
a_mod #() a_mod_0(
.ifc(ifc)
);
initial begin
#2;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

View File

@ -0,0 +1,57 @@
// 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
//
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
// verilog_format: on
package a_pkg;
typedef struct packed {
int unsigned a;
} cfg_t;
endpackage
package b_pkg;
typedef struct packed {
int unsigned a;
} cfg_t;
endpackage
interface depgraph_if #(a_pkg::cfg_t cfg=0)();
typedef logic [cfg.a-1:0] byte_t;
typedef logic [cfg.a*2-1:0] half_t;
typedef struct packed {
byte_t a;
half_t b;
} pair_t;
typedef union packed {
pair_t p;
logic [23:0] flat;
} pair_u_t;
endinterface
module t();
localparam a_pkg::cfg_t cfg = '{
a: 8
};
depgraph_if #(cfg) ifc();
typedef ifc.pair_u_t pair_u_t;
localparam b_pkg::cfg_t cfg_b = '{
a:$bits(pair_u_t)
};
initial begin
#2;
`checkd(cfg_b.a, 24);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -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()

Some files were not shown because too many files have changed in this diff Show More