Support nested interface as port connection (#5066) (#6986)

This commit is contained in:
Leela Pakanati 2026-02-04 15:26:20 -06:00 committed by GitHub
parent 515841cf15
commit 57c3b8e51b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 1443 additions and 69 deletions

View File

@ -158,8 +158,8 @@ class InlineMarkVisitor final : public VNVisitor {
}
}
void visit(AstVarXRef* nodep) override {
// Remove link. V3LinkDot will reestablish it after inlining.
nodep->varp(nullptr);
// Keep varp - V3Const::constifyEdit is called during pinReconnectSimple
// which needs varp to be set. V3LinkDot will re-resolve after inlining.
}
void visit(AstNodeFTaskRef* nodep) override {
// Remove link. V3LinkDot will reestablish it after inlining.
@ -322,7 +322,7 @@ class InlineRelinkVisitor final : public VNVisitor {
}
void visit(AstVarRef* nodep) override {
// If the target port is being inlined, replace reference with the
// connected expression (always a Const of a VarRef).
// connected expression (a Const, VarRef, or VarXRef).
AstNode* const pinExpr = nodep->varp()->user2p();
if (!pinExpr) return;
@ -354,10 +354,25 @@ class InlineRelinkVisitor final : public VNVisitor {
return;
}
// Otherwise it must be a variable reference, retarget this ref
const AstVarRef* const vrefp = VN_AS(pinExpr, VarRef);
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
// Handle VarRef: simple retarget
if (const AstVarRef* const vrefp = VN_CAST(pinExpr, VarRef)) {
nodep->varp(vrefp->varp());
nodep->classOrPackagep(vrefp->classOrPackagep());
return;
}
// Handle VarXRef: replace VarRef with VarXRef (e.g., nested interface port)
const AstVarXRef* const xrefp = VN_AS(pinExpr, VarXRef);
AstVarXRef* const newp = new AstVarXRef{nodep->fileline(), xrefp->name(), xrefp->dotted(),
nodep->access()};
newp->varp(xrefp->varp());
// Copy inlinedDots from pin expression - the normal visitor iteration will
// prepend the cell name when this VarXRef is visited later
newp->inlinedDots(xrefp->inlinedDots());
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
// Note: Don't call iterate(newp) here - the node will be visited during
// normal tree iteration which will apply the inlining transformations
}
void visit(AstVarXRef* nodep) override {
// Track what scope it was originally under so V3LinkDot can resolve it
@ -481,13 +496,28 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
}
// Otherwise it must be a variable reference due to having called pinReconnectSimple
const AstVarRef* const pinRefp = VN_AS(pinExprp, VarRef);
const AstNodeVarRef* const pinRefp = VN_AS(pinExprp, NodeVarRef);
// Helper to create an AstVarRef reference to the pin variable
const auto pinRef = [&](VAccess access) {
AstVarRef* const p = new AstVarRef{pinRefp->fileline(), pinRefp->varp(), access};
p->classOrPackagep(pinRefp->classOrPackagep());
return p;
const auto pinRefAsVarRef = [&](VAccess access) -> AstVarRef* {
const AstVarRef* const vrp = VN_AS(pinRefp, VarRef);
AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access};
newp->classOrPackagep(vrp->classOrPackagep());
return newp;
};
const auto pinRefAsExpr = [&](VAccess access) -> AstNodeExpr* {
if (const AstVarRef* const vrp = VN_CAST(pinRefp, VarRef)) {
AstVarRef* const newp = new AstVarRef{vrp->fileline(), vrp->varp(), access};
newp->classOrPackagep(vrp->classOrPackagep());
return newp;
} else {
const AstVarXRef* const xrp = VN_AS(pinRefp, VarXRef);
AstVarXRef* const newp = new AstVarXRef{xrp->fileline(), xrp->name(), xrp->dotted(),
access};
newp->varp(xrp->varp());
newp->inlinedDots(xrp->inlinedDots());
return newp;
}
};
// If it is being inlined, create the alias for it
@ -495,10 +525,10 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
UINFO(6, "Inlining port variable: " << nodep);
if (nodep->isIfaceRef()) {
modp->addStmtsp(
new AstAliasScope{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)});
new AstAliasScope{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)});
} else {
AstVarRef* const aliasArgsp = portRef(VAccess::WRITE);
aliasArgsp->addNext(pinRef(VAccess::READ));
aliasArgsp->addNext(pinRefAsVarRef(VAccess::READ));
modp->addStmtsp(new AstAlias{flp, aliasArgsp});
}
// They will become the same variable, so propagate file-line and variable attributes
@ -512,10 +542,12 @@ void connectPort(AstNodeModule* modp, AstVar* nodep, AstNodeExpr* pinExprp) {
// Otherwise create the continuous assignment between the port var and the pin expression
UINFO(6, "Not inlining port variable: " << nodep);
if (nodep->direction() == VDirection::INPUT) {
AstAssignW* const ap = new AstAssignW{flp, portRef(VAccess::WRITE), pinRef(VAccess::READ)};
AstAssignW* const ap
= new AstAssignW{flp, portRef(VAccess::WRITE), pinRefAsExpr(VAccess::READ)};
modp->addStmtsp(new AstAlways{ap});
} else if (nodep->direction() == VDirection::OUTPUT) {
AstAssignW* const ap = new AstAssignW{flp, pinRef(VAccess::WRITE), portRef(VAccess::READ)};
AstAssignW* const ap
= new AstAssignW{flp, pinRefAsExpr(VAccess::WRITE), portRef(VAccess::READ)};
modp->addStmtsp(new AstAlways{ap});
} else {
pinExprp->v3fatalSrc("V3Tristate left INOUT port");

View File

@ -777,9 +777,9 @@ public:
} else { // Searching for middle submodule, must be a cell name
VSymEnt* findSymp = findWithAltFlat(lookupSymp, ident, altIdent);
if (!findSymp) findSymp = findForkParentAlias(lookupSymp, ident);
if (findSymp)
if (findSymp) {
lookupSymp = unwrapForkParent(findSymp, ident);
else {
} else {
return nullptr; // Not found
}
}
@ -795,6 +795,53 @@ public:
}
}
}
// Follow scope alias for nested interface port access
if (!leftname.empty()) {
const auto aliasIt = m_scopeAliasMap[SAMN_IFTOP].find(lookupSymp);
if (aliasIt != m_scopeAliasMap[SAMN_IFTOP].end()) {
lookupSymp = aliasIt->second;
// Alias may point to __Viftop VarScope; find corresponding Cell
if (const AstVarScope* const vscp
= VN_CAST(lookupSymp->nodep(), VarScope)) {
const string varName = vscp->varp()->name();
static constexpr const char* const VIFTOP_SUFFIX = "__Viftop";
if (VString::endsWith(varName, VIFTOP_SUFFIX)) {
const string cellName
= varName.substr(0, varName.size() - strlen(VIFTOP_SUFFIX));
VSymEnt* const parentSymp = lookupSymp->parentp();
if (parentSymp) {
VSymEnt* const cellSymp = parentSymp->findIdFlat(cellName);
if (cellSymp && VN_IS(cellSymp->nodep(), Cell)) {
lookupSymp = cellSymp; // Use Cell for member lookup
}
}
}
}
} else {
// No alias; try following IfaceRefDType to interface cell
const AstVar* varp = VN_CAST(lookupSymp->nodep(), Var);
if (!varp) {
if (const AstVarScope* const vscp
= VN_CAST(lookupSymp->nodep(), VarScope)) {
varp = vscp->varp();
}
}
if (varp && varp->isIfaceRef()) {
if (const AstIfaceRefDType* const ifaceRefp
= ifaceRefFromArray(varp->dtypep())) {
if (ifaceRefp->cellp() && existsNodeSym(ifaceRefp->cellp())) {
lookupSymp = getNodeSym(ifaceRefp->cellp());
} else if (ifaceRefp->ifaceViaCellp()
&& existsNodeSym(ifaceRefp->ifaceViaCellp())) {
lookupSymp = getNodeSym(ifaceRefp->ifaceViaCellp());
} else if (ifaceRefp->ifacep()
&& existsNodeSym(ifaceRefp->ifacep())) {
lookupSymp = getNodeSym(ifaceRefp->ifacep());
}
}
}
}
}
}
firstId = false;
}
@ -2302,6 +2349,8 @@ class LinkDotScopeVisitor final : public VNVisitor {
LinkDotState* const m_statep; // State to pass between visitors, including symbol table
const AstScope* m_scopep = nullptr; // The current scope
VSymEnt* m_modSymp = nullptr; // Symbol entry for current module
// Deferred AliasScope processing - must be done outer-to-inner for correct alias resolution
std::vector<std::pair<AstAliasScope*, VSymEnt*>> m_deferredAliasScopes;
// METHODS
public:
@ -2345,37 +2394,40 @@ private:
if (!nodep->varp()->isFuncLocal() && !nodep->varp()->isClassMember()) {
VSymEnt* const varSymp
= m_statep->insertSym(m_modSymp, nodep->varp()->name(), nodep, nullptr);
if (nodep->varp()->isIfaceRef() && nodep->varp()->isIfaceParent()) {
UINFO(9, "Iface parent ref var " << nodep->varp()->name() << " " << nodep);
// Find the interface cell the var references
if (nodep->varp()->isIfaceRef()) {
AstIfaceRefDType* const dtypep
= LinkDotState::ifaceRefFromArray(nodep->varp()->dtypep());
UASSERT_OBJ(dtypep, nodep, "Non AstIfaceRefDType on isIfaceRef() var");
UINFO(9, "Iface parent dtype " << dtypep);
const string ifcellname = dtypep->cellName();
string baddot;
VSymEnt* okSymp;
VSymEnt* cellSymp = m_statep->findDotted(nodep->fileline(), m_modSymp, ifcellname,
baddot, okSymp, false);
UASSERT_OBJ(
cellSymp, nodep,
"No symbol for interface instance: " << nodep->prettyNameQ(ifcellname));
UINFO(5, " Found interface instance: se" << cvtToHex(cellSymp) << " "
<< cellSymp->nodep());
if (dtypep->modportName() != "") {
VSymEnt* const mpSymp = m_statep->findDotted(
nodep->fileline(), m_modSymp, ifcellname, baddot, okSymp, false);
UASSERT_OBJ(mpSymp, nodep,
"No symbol for interface modport: "
<< nodep->prettyNameQ(dtypep->modportName()));
cellSymp = mpSymp;
UINFO(5, " Found modport cell: se" << cvtToHex(cellSymp) << " "
<< mpSymp->nodep());
if (nodep->varp()->isIfaceParent()) {
UINFO(9, "Iface parent ref var " << nodep->varp()->name() << " " << nodep);
// Find the interface cell the var references
UINFO(9, "Iface parent dtype " << dtypep);
const string ifcellname = dtypep->cellName();
string baddot;
VSymEnt* okSymp;
VSymEnt* cellSymp
= m_statep->findDotted(nodep->fileline(), m_modSymp, ifcellname, baddot,
okSymp, false);
UASSERT_OBJ(
cellSymp, nodep,
"No symbol for interface instance: " << nodep->prettyNameQ(ifcellname));
UINFO(5, " Found interface instance: se" << cvtToHex(cellSymp) << " "
<< cellSymp->nodep());
if (dtypep->modportName() != "") {
VSymEnt* const mpSymp = m_statep->findDotted(
nodep->fileline(), m_modSymp, ifcellname, baddot, okSymp, false);
UASSERT_OBJ(mpSymp, nodep,
"No symbol for interface modport: "
<< nodep->prettyNameQ(dtypep->modportName()));
cellSymp = mpSymp;
UINFO(5, " Found modport cell: se" << cvtToHex(cellSymp) << " "
<< mpSymp->nodep());
}
// Interface reference; need to put whole thing into
// symtable, but can't clone it now as we may have a later
// alias for it.
m_statep->insertScopeAlias(LinkDotState::SAMN_IFTOP, varSymp, cellSymp);
}
// Interface reference; need to put whole thing into
// symtable, but can't clone it now as we may have a later
// alias for it.
m_statep->insertScopeAlias(LinkDotState::SAMN_IFTOP, varSymp, cellSymp);
}
}
}
@ -2414,6 +2466,12 @@ private:
pushDeletep(nodep->unlinkFrBack());
}
void visit(AstAliasScope* nodep) override { // ScopeVisitor::
// Defer AliasScope processing - must process outer scopes before inner ones
// so that nested interface port alias resolution works correctly
UINFO(5, "ALIASSCOPE (deferred) " << nodep);
m_deferredAliasScopes.emplace_back(nodep, m_modSymp);
}
void processAliasScope(AstAliasScope* nodep, VSymEnt* modSymp) {
UINFO(5, "ALIASSCOPE " << nodep);
UINFOTREE(9, nodep, "", "avs");
VSymEnt* rhsSymp;
@ -2425,22 +2483,22 @@ private:
string inl
= ((xrefp && xrefp->inlinedDots().size()) ? (xrefp->inlinedDots() + "__DOT__")
: "");
const string dottedPath = (xrefp && !xrefp->dotted().empty())
? (xrefp->dotted() + ".")
: "";
VSymEnt* symp = nullptr;
string scopename;
while (!symp) {
scopename
= refp ? refp->name() : (inl.size() ? (inl + xrefp->name()) : xrefp->name());
scopename = refp ? refp->name()
: (inl.size() ? (inl + dottedPath + xrefp->name())
: (dottedPath + xrefp->name()));
string baddot;
VSymEnt* okSymp;
symp = m_statep->findDotted(nodep->rhsp()->fileline(), m_modSymp, scopename,
baddot, okSymp, false);
symp = m_statep->findDotted(nodep->rhsp()->fileline(), modSymp, scopename,
baddot, okSymp, true);
if (inl == "") break;
inl = LinkDotState::removeLastInlineScope(inl);
}
if (!symp) {
UINFO(9, "No symbol for interface alias rhs ("
<< std::string{refp ? "VARREF " : "VARXREF "} << scopename << ")");
}
UASSERT_OBJ(symp, nodep, "No symbol for interface alias rhs");
UINFO(5, " Found a linked scope RHS: " << scopename << " se" << cvtToHex(symp)
<< " " << symp->nodep());
@ -2457,7 +2515,7 @@ private:
= refp ? refp->varp()->name() : xrefp->dotted() + "." + xrefp->name();
string baddot;
VSymEnt* okSymp;
VSymEnt* const symp = m_statep->findDotted(nodep->lhsp()->fileline(), m_modSymp,
VSymEnt* const symp = m_statep->findDotted(nodep->lhsp()->fileline(), modSymp,
scopename, baddot, okSymp, false);
UASSERT_OBJ(symp, nodep, "No symbol for interface alias lhs");
UINFO(5, " Found a linked scope LHS: " << scopename << " se" << cvtToHex(symp)
@ -2470,6 +2528,29 @@ private:
// We have stored the link, we don't need these any more
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void processDeferredAliasScopes() {
// Sort by hierarchy depth (shallower first) so outer aliases are resolved before inner
// Pre-compute depth map to avoid O(N log N * D) complexity in sort comparisons
std::unordered_map<VSymEnt*, int> depthMap;
for (const auto& pair : m_deferredAliasScopes) {
VSymEnt* const symp = pair.second;
if (depthMap.find(symp) == depthMap.end()) {
int depth = 0;
for (VSymEnt* p = symp; p; p = p->parentp()) ++depth;
depthMap[symp] = depth;
}
}
std::stable_sort(m_deferredAliasScopes.begin(), m_deferredAliasScopes.end(),
[&depthMap](const std::pair<AstAliasScope*, VSymEnt*>& a,
const std::pair<AstAliasScope*, VSymEnt*>& b) {
return depthMap.at(a.second) < depthMap.at(b.second);
});
// Process in sorted order
for (auto& pair : m_deferredAliasScopes) {
processAliasScope(pair.first, pair.second);
}
m_deferredAliasScopes.clear();
}
void visit(AstNodeGen* nodep) override { // ScopeVisitor:: // LCOV_EXCL_LINE
nodep->v3fatalSrc("Generate constructs should have been reduced out");
}
@ -2485,6 +2566,8 @@ public:
: m_statep{statep} {
UINFO(4, __FUNCTION__ << ": ");
iterate(rootp);
// Process deferred AliasScopes in outer-to-inner order
processDeferredAliasScopes();
}
~LinkDotScopeVisitor() override = default;
};
@ -4340,10 +4423,10 @@ class LinkDotResolveVisitor final : public VNVisitor {
<< okSymp->cellErrorScopes(nodep));
return;
}
// V3Inst may have expanded arrays of interfaces to
// AstVarXRef's even though they are in the same module detect
// this and convert to normal VarRefs
if (!m_statep->forPrearray() && !m_statep->forScopeCreation()) {
// V3Inst may have expanded arrays of interfaces to AstVarXRef's even though
// they are in the same module; convert to normal VarRefs (but not if dotted)
if (!m_statep->forPrearray() && !m_statep->forScopeCreation()
&& nodep->dotted().empty()) {
if (const AstIfaceRefDType* const ifaceDtp
= VN_CAST(nodep->dtypep(), IfaceRefDType)) {
if (!ifaceDtp->isVirtual()) {

View File

@ -616,6 +616,91 @@ class ParamProcessor final {
if (nodep->op4p()) replaceRefsRecurse(nodep->op4p(), oldClassp, newClassp);
if (nodep->nextp()) replaceRefsRecurse(nodep->nextp(), oldClassp, newClassp);
}
// Helper visitor to update VarXRefs to use variables from specialized interfaces.
// When a module with interface ports is cloned and the port's interface is remapped
// to a specialized version, VarXRefs that access members of the old interface need
// to be updated to reference the corresponding members in the new interface.
class VarXRefRelinkVisitor final : public VNVisitor {
AstNodeModule* m_modp; // The cloned module we're updating
std::unordered_map<AstVar*, AstNodeModule*> m_varModuleMap; // Cache var->module lookups
public:
explicit VarXRefRelinkVisitor(AstNodeModule* newModp)
: m_modp{newModp} {
iterate(newModp);
}
private:
// Find which module a variable belongs to, using cache to avoid repeated backp() walks
AstNodeModule* findVarModule(AstVar* varp) {
const auto it = m_varModuleMap.find(varp);
if (it != m_varModuleMap.end()) return it->second;
AstNodeModule* varModp = nullptr;
for (AstNode* np = varp; np; np = np->backp()) {
if (AstNodeModule* const modp = VN_CAST(np, NodeModule)) {
varModp = modp;
break;
}
}
m_varModuleMap[varp] = varModp;
return varModp;
}
void visit(AstVarXRef* nodep) override {
AstVar* const varp = nodep->varp();
if (!varp) { iterateChildren(nodep); return; }
// Get the dotted prefix (port name) from the VarXRef
// dotted() format: "portname" or "portname.subpath" or empty
const string& dotted = nodep->dotted();
if (dotted.empty()) { iterateChildren(nodep); return; }
const size_t dotPos = dotted.find('.');
const string portName
= (dotPos == string::npos) ? dotted : dotted.substr(0, dotPos);
if (portName.empty()) { iterateChildren(nodep); return; }
// Find the interface port variable in the cloned module
AstVar* portVarp = nullptr;
for (AstNode* stmtp = m_modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const varChkp = VN_CAST(stmtp, Var)) {
if (varChkp->name() == portName && varChkp->isIfaceRef()) {
portVarp = varChkp;
break;
}
}
}
if (!portVarp) { iterateChildren(nodep); return; }
// Get the interface module from the port's dtype
AstIfaceRefDType* const irefp = VN_CAST(portVarp->subDTypep(), IfaceRefDType);
if (!irefp) { iterateChildren(nodep); return; }
AstNodeModule* const newIfacep = irefp->ifaceViaCellp();
if (!newIfacep) { iterateChildren(nodep); return; }
// Find which module the variable currently belongs to (cached)
AstNodeModule* const varModp = findVarModule(varp);
// If variable is in a different module than the port's interface, remap it
if (varModp && varModp != newIfacep) {
for (AstNode* stmtp = newIfacep->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
if (AstVar* const newVarp = VN_CAST(stmtp, Var)) {
if (newVarp->name() == varp->name()) {
UINFO(9, "VarXRef relink " << varp->name() << " in "
<< varModp->name() << " -> " << newIfacep->name() << endl);
nodep->varp(newVarp);
break;
}
}
}
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override { iterateChildren(nodep); }
};
// Return true on success, false on error
bool deepCloneModule(AstNodeModule* srcModp, AstNode* ifErrorp, AstPin* paramsp,
const string& newname, const IfaceRefRefs& ifaceRefRefs) {
@ -737,7 +822,25 @@ class ParamProcessor final {
// thus we need to stash this info.
collectPins(clonemapp, newModp, srcModp->user3p());
// Relink parameter vars to the new module
relinkPins(clonemapp, paramsp);
// For interface ports (e.g., l3_if #(W, L0A_W) l3), the parameter pins may
// reference variables from the enclosing module rather than from the interface
// being cloned. In such cases, use relinkPinsByName to match by variable name.
// Check if any parameter pins reference variables outside the cloned interface.
// This is O(n) but acceptable since parameter pin lists are typically small (<10 pins).
bool needRelinkByName = false;
if (paramsp) {
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->modVarp() && clonemapp->find(pinp->modVarp()) == clonemapp->end()) {
needRelinkByName = true;
break;
}
}
}
if (needRelinkByName) {
relinkPinsByName(paramsp, newModp);
} else {
relinkPins(clonemapp, paramsp);
}
// Fix any interface references
for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) {
@ -751,6 +854,12 @@ class ParamProcessor final {
cloneIrefp->ifacep(pinIrefp->ifaceViaCellp());
UINFO(8, " IfaceNew " << cloneIrefp);
}
// Fix VarXRefs that reference variables in old interfaces.
// Now that interface port dtypes have been updated above, we can use them
// to find the correct interface for each VarXRef.
if (!ifaceRefRefs.empty()) { VarXRefRelinkVisitor{newModp}; }
// 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)) {
@ -962,7 +1071,8 @@ class ParamProcessor final {
AstIfaceRefDType* pinIrefp = nullptr;
const AstNode* const exprp = pinp->exprp();
const AstVar* const varp
= (exprp && VN_IS(exprp, VarRef)) ? VN_AS(exprp, VarRef)->varp() : nullptr;
= (exprp && VN_IS(exprp, NodeVarRef)) ? VN_AS(exprp, NodeVarRef)->varp()
: nullptr;
if (varp && varp->subDTypep() && VN_IS(varp->subDTypep(), IfaceRefDType)) {
pinIrefp = VN_AS(varp->subDTypep(), IfaceRefDType);
} else if (varp && varp->subDTypep() && arraySubDTypep(varp->subDTypep())
@ -978,6 +1088,13 @@ class ParamProcessor final {
pinIrefp
= VN_AS(arraySubDTypep(VN_AS(exprp->op1p(), VarRef)->varp()->subDTypep()),
IfaceRefDType);
} else if (VN_IS(exprp, CellArrayRef)) {
// Interface array element selection (e.g., l1(l2.l1[0]) for nested iface array)
// The CellArrayRef is not yet fully linked to an interface type.
// Skip interface cleanup for this pin - V3LinkDot will resolve this later.
// Just continue to the next pin without error.
UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp << endl);
continue;
}
UINFO(9, " portIfaceRef " << portIrefp);
@ -1184,9 +1301,11 @@ class ParamProcessor final {
cellInterfaceCleanup(pinsp, srcModp, longname /*ref*/, any_overrides /*ref*/,
ifaceRefRefs /*ref*/);
// Default params are resolved as overrides
// Classes/modules with type parameters need specialization even when types match defaults.
// This is required for UVM parameterized classes. However, interfaces should NOT
// be specialized when type params match defaults (needed for nested interface ports).
bool defaultsResolved = false;
if (!any_overrides) {
if (!any_overrides && !VN_IS(srcModp, Iface)) {
for (AstPin* pinp = paramsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (pinp->modPTypep()) {
any_overrides = true;
@ -1238,6 +1357,7 @@ class ParamProcessor final {
<< " cellName=" << nodep->name()
<< " cloned=" << cloned);
// Link source class to its specialized version for later relinking of method references
if (defaultsResolved) srcModp->user4p(newModp);
for (auto* stmtp = newModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
@ -1569,6 +1689,26 @@ class ParamVisitor final : public VNVisitor {
return false;
}
// Recursively specialize nested interface cells within a specialized interface.
// This handles parameter passthrough for nested interface hierarchies.
void specializeNestedIfaceCells(AstNodeModule* ifaceModp) {
for (AstNode* stmtp = ifaceModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstCell* const nestedCellp = VN_CAST(stmtp, Cell);
if (!nestedCellp) continue;
if (!VN_IS(nestedCellp->modp(), Iface)) continue;
if (!nestedCellp->paramsp()) continue;
if (cellParamsReferenceIfacePorts(nestedCellp)) continue;
AstNodeModule* const nestedSrcModp = nestedCellp->modp();
if (AstNodeModule* const nestedNewModp
= m_processor.nodeDeparam(nestedCellp, nestedSrcModp, ifaceModp,
ifaceModp->someInstanceName())) {
// Recursively process nested interfaces within this nested interface
if (nestedNewModp != nestedSrcModp) specializeNestedIfaceCells(nestedNewModp);
}
}
}
// A generic visitor for cells and class refs
void visitCellOrClassRef(AstNode* nodep, bool isIface) {
// Must do ifaces first, so push to list and do in proper order
@ -1581,7 +1721,13 @@ class ParamVisitor final : public VNVisitor {
AstCell* const cellp = VN_CAST(nodep, Cell);
if (!cellParamsReferenceIfacePorts(cellp)) {
AstNodeModule* const srcModp = cellp->modp();
m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
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);
}
}
}
@ -1808,13 +1954,25 @@ class ParamVisitor final : public VNVisitor {
V3Const::constifyParamsEdit(nodep->selp());
if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) {
const string index = AstNode::encodeNumber(constp->toSInt());
const string replacestr = nodep->name() + "__BRA__??__KET__";
// For nested interface array ports, the node name may have a __Viftop suffix
// that doesn't exist in the original unlinked text. Try without the suffix.
const string viftopSuffix = "__Viftop";
const string baseName = VString::endsWith(nodep->name(), viftopSuffix)
? nodep->name().substr(0, nodep->name().size()
- viftopSuffix.size())
: nodep->name();
const string replacestr = baseName + "__BRA__??__KET__";
const size_t pos = m_unlinkedTxt.find(replacestr);
UASSERT_OBJ(pos != string::npos, nodep,
"Could not find array index in unlinked text: '"
<< m_unlinkedTxt << "' for node: " << nodep);
// For interface port array element selections (e.g., l1(l2.l1[0])),
// the AstCellArrayRef may be visited outside of an AstUnlinkedRef context.
// In such cases, m_unlinkedTxt won't contain the expected pattern.
// Simply skip the replacement - the cell array ref will be resolved later.
if (pos == string::npos) {
UINFO(9, "Skipping unlinked text replacement for " << nodep << endl);
return;
}
m_unlinkedTxt.replace(pos, replacestr.length(),
nodep->name() + "__BRA__" + index + "__KET__");
baseName + "__BRA__" + index + "__KET__");
} else {
nodep->v3error("Could not expand constant selection inside dotted reference: "
<< nodep->selp()->prettyNameQ());

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()
test.execute()
test.passes()

View File

@ -0,0 +1,42 @@
// DESCRIPTION: Verilator: VarXRef inlinedDots propagation regression
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
module src #(parameter [3:0] VAL = 4'h0) (output logic [3:0] val);
/*verilator no_inline_module*/
assign val = VAL;
endmodule
module inner (input logic [3:0] in, output logic [3:0] out);
/*verilator inline_module*/
assign out = in;
endmodule
module outer #(parameter [3:0] VAL = 4'h0) (output logic [3:0] out);
/*verilator inline_module*/
logic [3:0] s_val;
src #(.VAL(VAL)) s (.val(s_val));
// Use hierarchical ref s.val (not s_val) to test inlinedDots propagation
inner u (.in(s.val), .out(out));
endmodule
module t;
logic [3:0] out0;
logic [3:0] out1;
logic [3:0] unused;
// Top-level instance with the same name as the inlined one.
src #(.VAL(4'hF)) s (.val(unused));
outer #(.VAL(4'h1)) o0 (.out(out0));
outer #(.VAL(4'h2)) o1 (.out(out1));
initial begin
if (out0 !== 4'h1) $stop;
if (out1 !== 4'h2) $stop;
$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,297 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Issue #5066 - Combined test for nested interface ports with parameters
//
// Tests all parameter patterns in a 5-deep hierarchy:
// - Derived: W doubles at each level (L3=L4*2, L2A=L3*2, L1=L2*2)
// - Hard-coded: L2B.W=8 regardless of parent
// - Passthrough: L0A_W flows unchanged from top to L0A
// - Default: L0B uses default W=8
//
// With TOP_W=4, L0A_W=16:
// L4(W=4) -> L3(W=8) -> L2A(W=16) -> L1(W=32) -> L0A(W=16), L0B(W=8)
// -> L2B(W=8) -> L1(W=16) -> L0A(W=16), L0B(W=8)
interface l0_if #(parameter int W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
endinterface
interface l1_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l0_if #(L0A_W) l0a(); // passthrough
l0_if l0b(); // default
endinterface
interface l2_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l1_if #(W*2, L0A_W) l1(); // derived
endinterface
interface l3_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l2_if #(W*2, L0A_W) l2a(); // derived
l2_if #(8, L0A_W) l2b(); // hard-coded
endinterface
interface l4_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l3_if #(W*2, L0A_W) l3(); // derived
endinterface
// Handlers use unparameterized interface ports with parameterized output widths
module l0_handler #(parameter int W = 8)(
input logic clk,
l0_if l0,
output logic [W-1:0] dout
);
always_ff @(posedge clk) l0.dut_out <= l0.tb_in ^ W'('1);
assign dout = l0.dut_out;
endmodule
module l1_reader #(parameter int W = 8)(
l1_if l1,
output logic [W-1:0] dout
);
assign dout = l1.dut_out;
endmodule
module l1_driver #(parameter int W = 8)(
input logic clk,
l1_if l1
);
always_ff @(posedge clk) l1.dut_out <= l1.tb_in ^ W'('1);
endmodule
module l1_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l1_if l1,
output logic [W-1:0] l1_dout,
output logic [L0A_W-1:0] l0a_dout,
output logic [7:0] l0b_dout
);
// Use reader/driver submodules instead of direct access
l1_reader #(W) m_rdr (.l1(l1), .dout(l1_dout));
l1_driver #(W) m_drv (.clk(clk), .l1(l1));
// Still instantiate l0_handlers for nested ports
l0_handler #(L0A_W) m_l0a (.clk(clk), .l0(l1.l0a), .dout(l0a_dout));
l0_handler #(8) m_l0b (.clk(clk), .l0(l1.l0b), .dout(l0b_dout));
endmodule
module l2_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l2_if l2,
output logic [W-1:0] l2_dout,
output logic [W*2-1:0] l1_dout,
output logic [L0A_W-1:0] l0a_dout,
output logic [7:0] l0b_dout
);
always_ff @(posedge clk) l2.dut_out <= l2.tb_in ^ W'('1);
assign l2_dout = l2.dut_out;
l1_handler #(W*2, L0A_W) m_l1 (
.clk(clk), .l1(l2.l1),
.l1_dout(l1_dout), .l0a_dout(l0a_dout), .l0b_dout(l0b_dout)
);
endmodule
module l3_reader #(parameter int W = 8)(
l3_if l3,
output logic [W-1:0] dout
);
assign dout = l3.dut_out;
endmodule
module l3_driver #(parameter int W = 8)(
input logic clk,
l3_if l3
);
always_ff @(posedge clk) l3.dut_out <= l3.tb_in ^ W'('1);
endmodule
module l3_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l3_if l3,
output logic [W-1:0] l3_dout,
output logic [W*2-1:0] l2a_dout,
output logic [W*4-1:0] l1_2a_dout,
output logic [L0A_W-1:0] l0a_2a_dout,
output logic [7:0] l0b_2a_dout,
output logic [7:0] l2b_dout,
output logic [15:0] l1_2b_dout,
output logic [L0A_W-1:0] l0a_2b_dout,
output logic [7:0] l0b_2b_dout
);
// Use reader/driver submodules instead of direct access
l3_reader #(W) m_rdr (.l3(l3), .dout(l3_dout));
l3_driver #(W) m_drv (.clk(clk), .l3(l3));
// Still instantiate l2_handlers for nested ports
l2_handler #(W*2, L0A_W) m_l2a (
.clk(clk), .l2(l3.l2a),
.l2_dout(l2a_dout), .l1_dout(l1_2a_dout),
.l0a_dout(l0a_2a_dout), .l0b_dout(l0b_2a_dout)
);
l2_handler #(8, L0A_W) m_l2b (
.clk(clk), .l2(l3.l2b),
.l2_dout(l2b_dout), .l1_dout(l1_2b_dout),
.l0a_dout(l0a_2b_dout), .l0b_dout(l0b_2b_dout)
);
endmodule
module l4_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l4_if l4,
output logic [W-1:0] l4_dout,
output logic [W*2-1:0] l3_dout,
output logic [W*4-1:0] l2a_dout,
output logic [W*8-1:0] l1_2a_dout,
output logic [L0A_W-1:0] l0a_2a_dout,
output logic [7:0] l0b_2a_dout,
output logic [7:0] l2b_dout,
output logic [15:0] l1_2b_dout,
output logic [L0A_W-1:0] l0a_2b_dout,
output logic [7:0] l0b_2b_dout
);
always_ff @(posedge clk) l4.dut_out <= l4.tb_in ^ W'('1);
assign l4_dout = l4.dut_out;
l3_handler #(W*2, L0A_W) m_l3 (
.clk(clk), .l3(l4.l3),
.l3_dout(l3_dout),
.l2a_dout(l2a_dout), .l1_2a_dout(l1_2a_dout),
.l0a_2a_dout(l0a_2a_dout), .l0b_2a_dout(l0b_2a_dout),
.l2b_dout(l2b_dout), .l1_2b_dout(l1_2b_dout),
.l0a_2b_dout(l0a_2b_dout), .l0b_2b_dout(l0b_2b_dout)
);
endmodule
module t;
logic clk = 0;
int cyc = 0;
localparam int TOP_W = 4;
localparam int L0A_W = 16;
l4_if #(TOP_W, L0A_W) inst();
logic [TOP_W-1:0] l4_dout;
logic [TOP_W*2-1:0] l3_dout;
logic [TOP_W*4-1:0] l2a_dout;
logic [TOP_W*8-1:0] l1_2a_dout;
logic [L0A_W-1:0] l0a_2a_dout;
logic [7:0] l0b_2a_dout;
logic [7:0] l2b_dout;
logic [15:0] l1_2b_dout;
logic [L0A_W-1:0] l0a_2b_dout;
logic [7:0] l0b_2b_dout;
l4_handler #(TOP_W, L0A_W) m_l4 (
.clk(clk), .l4(inst),
.l4_dout(l4_dout),
.l3_dout(l3_dout),
.l2a_dout(l2a_dout), .l1_2a_dout(l1_2a_dout),
.l0a_2a_dout(l0a_2a_dout), .l0b_2a_dout(l0b_2a_dout),
.l2b_dout(l2b_dout), .l1_2b_dout(l1_2b_dout),
.l0a_2b_dout(l0a_2b_dout), .l0b_2b_dout(l0b_2b_dout)
);
always #5 clk = ~clk;
always_ff @(posedge clk) begin
inst.tb_in <= cyc[TOP_W-1:0];
inst.l3.tb_in <= cyc[TOP_W*2-1:0] + (TOP_W*2)'(1);
inst.l3.l2a.tb_in <= cyc[TOP_W*4-1:0] + (TOP_W*4)'(2);
inst.l3.l2a.l1.tb_in <= cyc[TOP_W*8-1:0] + (TOP_W*8)'(3);
inst.l3.l2a.l1.l0a.tb_in <= cyc[L0A_W-1:0] + L0A_W'(4);
inst.l3.l2a.l1.l0b.tb_in <= cyc[7:0] + 8'd5;
inst.l3.l2b.tb_in <= cyc[7:0] + 8'd6;
inst.l3.l2b.l1.tb_in <= cyc[15:0] + 16'd7;
inst.l3.l2b.l1.l0a.tb_in <= cyc[L0A_W-1:0] + L0A_W'(8);
inst.l3.l2b.l1.l0b.tb_in <= cyc[7:0] + 8'd9;
end
logic [TOP_W-1:0] exp_l4;
logic [TOP_W*2-1:0] exp_l3;
logic [TOP_W*4-1:0] exp_l2a;
logic [TOP_W*8-1:0] exp_l1_2a;
logic [L0A_W-1:0] exp_l0a_2a;
logic [7:0] exp_l0b_2a;
logic [7:0] exp_l2b;
logic [15:0] exp_l1_2b;
logic [L0A_W-1:0] exp_l0a_2b;
logic [7:0] exp_l0b_2b;
always_ff @(posedge clk) begin
exp_l4 <= inst.tb_in ^ TOP_W'('1);
exp_l3 <= inst.l3.tb_in ^ (TOP_W*2)'('1);
exp_l2a <= inst.l3.l2a.tb_in ^ (TOP_W*4)'('1);
exp_l1_2a <= inst.l3.l2a.l1.tb_in ^ (TOP_W*8)'('1);
exp_l0a_2a <= inst.l3.l2a.l1.l0a.tb_in ^ L0A_W'('1);
exp_l0b_2a <= inst.l3.l2a.l1.l0b.tb_in ^ 8'hFF;
exp_l2b <= inst.l3.l2b.tb_in ^ 8'hFF;
exp_l1_2b <= inst.l3.l2b.l1.tb_in ^ 16'hFFFF;
exp_l0a_2b <= inst.l3.l2b.l1.l0a.tb_in ^ L0A_W'('1);
exp_l0b_2b <= inst.l3.l2b.l1.l0b.tb_in ^ 8'hFF;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc > 3) begin
if (l4_dout !== exp_l4) begin
$display("FAIL cyc=%0d: l4_dout=%h expected %h", cyc, l4_dout, exp_l4);
$stop;
end
if (l3_dout !== exp_l3) begin
$display("FAIL cyc=%0d: l3_dout=%h expected %h", cyc, l3_dout, exp_l3);
$stop;
end
if (l2a_dout !== exp_l2a) begin
$display("FAIL cyc=%0d: l2a_dout=%h expected %h", cyc, l2a_dout, exp_l2a);
$stop;
end
if (l1_2a_dout !== exp_l1_2a) begin
$display("FAIL cyc=%0d: l1_2a_dout=%h expected %h", cyc, l1_2a_dout, exp_l1_2a);
$stop;
end
if (l0a_2a_dout !== exp_l0a_2a) begin
$display("FAIL cyc=%0d: l0a_2a_dout=%h expected %h", cyc, l0a_2a_dout, exp_l0a_2a);
$stop;
end
if (l0b_2a_dout !== exp_l0b_2a) begin
$display("FAIL cyc=%0d: l0b_2a_dout=%h expected %h", cyc, l0b_2a_dout, exp_l0b_2a);
$stop;
end
if (l2b_dout !== exp_l2b) begin
$display("FAIL cyc=%0d: l2b_dout=%h expected %h", cyc, l2b_dout, exp_l2b);
$stop;
end
if (l1_2b_dout !== exp_l1_2b) begin
$display("FAIL cyc=%0d: l1_2b_dout=%h expected %h", cyc, l1_2b_dout, exp_l1_2b);
$stop;
end
if (l0a_2b_dout !== exp_l0a_2b) begin
$display("FAIL cyc=%0d: l0a_2b_dout=%h expected %h", cyc, l0a_2b_dout, exp_l0a_2b);
$stop;
end
if (l0b_2b_dout !== exp_l0b_2b) begin
$display("FAIL cyc=%0d: l0b_2b_dout=%h expected %h", cyc, l0b_2b_dout, exp_l0b_2b);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,24 @@
%Error: t/t_interface_nested_port_array.v:111:3: 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)?
111 | 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:83:3: 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)?
83 | l2_if #(W, L0A_W) l2s[1:0],
| ^~~~~
%Error: t/t_interface_nested_port_array.v:60:3: 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)?
60 | l2_if #(W, L0A_W) l2,
| ^~~~~
%Error: Internal Error: t/t_interface_nested_port_array.v:22:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry?
: ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1'
22 | interface l2_if #(parameter int W = 8, parameter int L0A_W = 8);
| ^~~~~
... This fatal error may be caused by the earlier error(s); resolve those first.

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
# Issue #5066: Nested interface ports through interface arrays
# (e.g., l2.l1[0] where l1 is an interface array inside interface l2).
# V3Param internal errors have been fixed, but V3LinkDot interface
# connection resolution for array element selections is not yet implemented.
# This test documents the current behavior and should be updated when
# full support is added.
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,279 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Issue #5066 - Nested interface ports through interface arrays
// Similar structure to t_interface_nested_port.v, but with interface arrays.
interface l0_if #(parameter int W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
endinterface
interface l1_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l0_if #(L0A_W) l0a[1:0](); // arrayed passthrough
l0_if l0b(); // default
endinterface
interface l2_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l1_if #(W*2, L0A_W) l1[1:0](); // derived
endinterface
interface l3_if #(parameter int W = 8, parameter int L0A_W = 8);
logic [W-1:0] tb_in;
logic [W-1:0] dut_out;
l2_if #(W*2, L0A_W) l2[1:0](); // arrayed
endinterface
module l0_handler #(parameter int W = 8)(
input logic clk,
l0_if #(W) l0,
output logic [W-1:0] dout
);
always_ff @(posedge clk) l0.dut_out <= l0.tb_in ^ W'('1);
assign dout = l0.dut_out;
endmodule
module l1_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l1_if #(W, L0A_W) l1,
output logic [W-1:0] l1_dout,
output logic [L0A_W-1:0] l0a0_dout,
output logic [L0A_W-1:0] l0a1_dout,
output logic [7:0] l0b_dout
);
always_ff @(posedge clk) l1.dut_out <= l1.tb_in ^ W'('1);
assign l1_dout = l1.dut_out;
l0_handler #(L0A_W) m_l0a0 (.clk(clk), .l0(l1.l0a[0]), .dout(l0a0_dout));
l0_handler #(L0A_W) m_l0a1 (.clk(clk), .l0(l1.l0a[1]), .dout(l0a1_dout));
l0_handler #(8) m_l0b (.clk(clk), .l0(l1.l0b), .dout(l0b_dout));
endmodule
module l2_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l2_if #(W, L0A_W) l2,
output logic [W-1:0] l2_dout,
output logic [W*2-1:0] l1_0_dout,
output logic [L0A_W-1:0] l0a0_0_dout,
output logic [L0A_W-1:0] l0a1_1_dout,
output logic [7:0] l0b_1_dout
);
always_ff @(posedge clk) l2.dut_out <= l2.tb_in ^ W'('1);
assign l2_dout = l2.dut_out;
l1_handler #(W*2, L0A_W) m_l1_0 (
.clk(clk), .l1(l2.l1[0]),
.l1_dout(l1_0_dout), .l0a0_dout(l0a0_0_dout),
.l0a1_dout(), .l0b_dout()
);
l1_handler #(W*2, L0A_W) m_l1_1 (
.clk(clk), .l1(l2.l1[1]),
.l1_dout(), .l0a0_dout(),
.l0a1_dout(l0a1_1_dout), .l0b_dout(l0b_1_dout)
);
endmodule
module l2_array_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l2_if #(W, L0A_W) l2s[1:0],
output logic [W-1:0] l2a_dout,
output logic [W*2-1:0] l2a_l1_0_dout,
output logic [L0A_W-1:0] l2a_l0a0_0_dout,
output logic [L0A_W-1:0] l2a_l0a1_1_dout,
output logic [7:0] l2a_l0b_1_dout,
output logic [W-1:0] l2b_dout,
output logic [W*2-1:0] l2b_l1_0_dout,
output logic [L0A_W-1:0] l2b_l0a0_0_dout,
output logic [L0A_W-1:0] l2b_l0a1_1_dout,
output logic [7:0] l2b_l0b_1_dout
);
l2_handler #(W, L0A_W) m_l2a (
.clk(clk), .l2(l2s[0]),
.l2_dout(l2a_dout),
.l1_0_dout(l2a_l1_0_dout), .l0a0_0_dout(l2a_l0a0_0_dout),
.l0a1_1_dout(l2a_l0a1_1_dout), .l0b_1_dout(l2a_l0b_1_dout)
);
l2_handler #(W, L0A_W) m_l2b (
.clk(clk), .l2(l2s[1]),
.l2_dout(l2b_dout),
.l1_0_dout(l2b_l1_0_dout), .l0a0_0_dout(l2b_l0a0_0_dout),
.l0a1_1_dout(l2b_l0a1_1_dout), .l0b_1_dout(l2b_l0b_1_dout)
);
endmodule
module l3_handler #(parameter int W = 8, parameter int L0A_W = 8)(
input logic clk,
l3_if #(W, L0A_W) l3,
output logic [W-1:0] l3_dout,
output logic [W*2-1:0] l2a_dout,
output logic [W*4-1:0] l2a_l1_0_dout,
output logic [L0A_W-1:0] l2a_l0a0_0_dout,
output logic [L0A_W-1:0] l2a_l0a1_1_dout,
output logic [7:0] l2a_l0b_1_dout,
output logic [W*2-1:0] l2b_dout,
output logic [W*4-1:0] l2b_l1_0_dout,
output logic [L0A_W-1:0] l2b_l0a0_0_dout,
output logic [L0A_W-1:0] l2b_l0a1_1_dout,
output logic [7:0] l2b_l0b_1_dout
);
always_ff @(posedge clk) l3.dut_out <= l3.tb_in ^ W'('1);
assign l3_dout = l3.dut_out;
l2_array_handler #(W*2, L0A_W) m_l2 (
.clk(clk), .l2s(l3.l2),
.l2a_dout(l2a_dout),
.l2a_l1_0_dout(l2a_l1_0_dout), .l2a_l0a0_0_dout(l2a_l0a0_0_dout),
.l2a_l0a1_1_dout(l2a_l0a1_1_dout), .l2a_l0b_1_dout(l2a_l0b_1_dout),
.l2b_dout(l2b_dout),
.l2b_l1_0_dout(l2b_l1_0_dout), .l2b_l0a0_0_dout(l2b_l0a0_0_dout),
.l2b_l0a1_1_dout(l2b_l0a1_1_dout), .l2b_l0b_1_dout(l2b_l0b_1_dout)
);
endmodule
module t;
logic clk = 0;
int cyc = 0;
localparam int TOP_W = 4;
localparam int L0A_W = 12;
l3_if #(TOP_W, L0A_W) inst();
logic [TOP_W-1:0] l3_dout;
logic [TOP_W*2-1:0] l2a_dout;
logic [TOP_W*4-1:0] l2a_l1_0_dout;
logic [L0A_W-1:0] l2a_l0a0_0_dout;
logic [L0A_W-1:0] l2a_l0a1_1_dout;
logic [7:0] l2a_l0b_1_dout;
logic [TOP_W*2-1:0] l2b_dout;
logic [TOP_W*4-1:0] l2b_l1_0_dout;
logic [L0A_W-1:0] l2b_l0a0_0_dout;
logic [L0A_W-1:0] l2b_l0a1_1_dout;
logic [7:0] l2b_l0b_1_dout;
l3_handler #(TOP_W, L0A_W) m_l3 (
.clk(clk), .l3(inst),
.l3_dout(l3_dout),
.l2a_dout(l2a_dout),
.l2a_l1_0_dout(l2a_l1_0_dout), .l2a_l0a0_0_dout(l2a_l0a0_0_dout),
.l2a_l0a1_1_dout(l2a_l0a1_1_dout), .l2a_l0b_1_dout(l2a_l0b_1_dout),
.l2b_dout(l2b_dout),
.l2b_l1_0_dout(l2b_l1_0_dout), .l2b_l0a0_0_dout(l2b_l0a0_0_dout),
.l2b_l0a1_1_dout(l2b_l0a1_1_dout), .l2b_l0b_1_dout(l2b_l0b_1_dout)
);
always #5 clk = ~clk;
always_ff @(posedge clk) begin
inst.tb_in <= cyc[TOP_W-1:0];
inst.l2[0].tb_in <= cyc[TOP_W*2-1:0] + (TOP_W*2)'(1);
inst.l2[0].l1[0].tb_in <= cyc[TOP_W*4-1:0] + (TOP_W*4)'(2);
inst.l2[0].l1[0].l0a[0].tb_in <= cyc[L0A_W-1:0] + L0A_W'(3);
inst.l2[0].l1[1].l0a[1].tb_in <= cyc[L0A_W-1:0] + L0A_W'(4);
inst.l2[0].l1[1].l0b.tb_in <= cyc[7:0] + 8'd5;
inst.l2[1].tb_in <= cyc[TOP_W*2-1:0] + (TOP_W*2)'(6);
inst.l2[1].l1[0].tb_in <= cyc[TOP_W*4-1:0] + (TOP_W*4)'(7);
inst.l2[1].l1[0].l0a[0].tb_in <= cyc[L0A_W-1:0] + L0A_W'(8);
inst.l2[1].l1[1].l0a[1].tb_in <= cyc[L0A_W-1:0] + L0A_W'(9);
inst.l2[1].l1[1].l0b.tb_in <= cyc[7:0] + 8'd10;
end
logic [TOP_W-1:0] exp_l3_dout;
logic [TOP_W*2-1:0] exp_l2a_dout;
logic [TOP_W*4-1:0] exp_l2a_l1_0_dout;
logic [L0A_W-1:0] exp_l2a_l0a0_0_dout;
logic [L0A_W-1:0] exp_l2a_l0a1_1_dout;
logic [7:0] exp_l2a_l0b_1_dout;
logic [TOP_W*2-1:0] exp_l2b_dout;
logic [TOP_W*4-1:0] exp_l2b_l1_0_dout;
logic [L0A_W-1:0] exp_l2b_l0a0_0_dout;
logic [L0A_W-1:0] exp_l2b_l0a1_1_dout;
logic [7:0] exp_l2b_l0b_1_dout;
always_ff @(posedge clk) begin
exp_l3_dout <= inst.tb_in ^ TOP_W'('1);
exp_l2a_dout <= inst.l2[0].tb_in ^ (TOP_W*2)'('1);
exp_l2a_l1_0_dout <= inst.l2[0].l1[0].tb_in ^ (TOP_W*4)'('1);
exp_l2a_l0a0_0_dout <= inst.l2[0].l1[0].l0a[0].tb_in ^ L0A_W'('1);
exp_l2a_l0a1_1_dout <= inst.l2[0].l1[1].l0a[1].tb_in ^ L0A_W'('1);
exp_l2a_l0b_1_dout <= inst.l2[0].l1[1].l0b.tb_in ^ 8'hFF;
exp_l2b_dout <= inst.l2[1].tb_in ^ (TOP_W*2)'('1);
exp_l2b_l1_0_dout <= inst.l2[1].l1[0].tb_in ^ (TOP_W*4)'('1);
exp_l2b_l0a0_0_dout <= inst.l2[1].l1[0].l0a[0].tb_in ^ L0A_W'('1);
exp_l2b_l0a1_1_dout <= inst.l2[1].l1[1].l0a[1].tb_in ^ L0A_W'('1);
exp_l2b_l0b_1_dout <= inst.l2[1].l1[1].l0b.tb_in ^ 8'hFF;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc > 3) begin
if (l3_dout !== exp_l3_dout) begin
$display("FAIL cyc=%0d: l3_dout=%h expected %h", cyc, l3_dout, exp_l3_dout);
$stop;
end
if (l2a_dout !== exp_l2a_dout) begin
$display("FAIL cyc=%0d: l2a_dout=%h expected %h", cyc, l2a_dout, exp_l2a_dout);
$stop;
end
if (l2a_l1_0_dout !== exp_l2a_l1_0_dout) begin
$display("FAIL cyc=%0d: l2a_l1_0_dout=%h expected %h",
cyc, l2a_l1_0_dout, exp_l2a_l1_0_dout);
$stop;
end
if (l2a_l0a0_0_dout !== exp_l2a_l0a0_0_dout) begin
$display("FAIL cyc=%0d: l2a_l0a0_0_dout=%h expected %h",
cyc, l2a_l0a0_0_dout, exp_l2a_l0a0_0_dout);
$stop;
end
if (l2a_l0a1_1_dout !== exp_l2a_l0a1_1_dout) begin
$display("FAIL cyc=%0d: l2a_l0a1_1_dout=%h expected %h",
cyc, l2a_l0a1_1_dout, exp_l2a_l0a1_1_dout);
$stop;
end
if (l2a_l0b_1_dout !== exp_l2a_l0b_1_dout) begin
$display("FAIL cyc=%0d: l2a_l0b_1_dout=%h expected %h",
cyc, l2a_l0b_1_dout, exp_l2a_l0b_1_dout);
$stop;
end
if (l2b_dout !== exp_l2b_dout) begin
$display("FAIL cyc=%0d: l2b_dout=%h expected %h", cyc, l2b_dout, exp_l2b_dout);
$stop;
end
if (l2b_l1_0_dout !== exp_l2b_l1_0_dout) begin
$display("FAIL cyc=%0d: l2b_l1_0_dout=%h expected %h",
cyc, l2b_l1_0_dout, exp_l2b_l1_0_dout);
$stop;
end
if (l2b_l0a0_0_dout !== exp_l2b_l0a0_0_dout) begin
$display("FAIL cyc=%0d: l2b_l0a0_0_dout=%h expected %h",
cyc, l2b_l0a0_0_dout, exp_l2b_l0a0_0_dout);
$stop;
end
if (l2b_l0a1_1_dout !== exp_l2b_l0a1_1_dout) begin
$display("FAIL cyc=%0d: l2b_l0a1_1_dout=%h expected %h",
cyc, l2b_l0a1_1_dout, exp_l2b_l0a1_1_dout);
$stop;
end
if (l2b_l0b_1_dout !== exp_l2b_l0b_1_dout) begin
$display("FAIL cyc=%0d: l2b_l0b_1_dout=%h expected %h",
cyc, l2b_l0b_1_dout, exp_l2b_l0b_1_dout);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,24 @@
%Error: t/t_interface_nested_port_array.v:111:3: 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)?
111 | 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:83:3: 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)?
83 | l2_if #(W, L0A_W) l2s[1:0],
| ^~~~~
%Error: t/t_interface_nested_port_array.v:60:3: 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)?
60 | l2_if #(W, L0A_W) l2,
| ^~~~~
%Error: Internal Error: t/t_interface_nested_port_array.v:22:11: ../V3LinkDot.cpp:#: Module/etc never assigned a symbol entry?
: ... note: In instance 't.m_l3.m_l2.m_l2b.m_l1_1'
22 | interface l2_if #(parameter int W = 8, parameter int L0A_W = 8);
| ^~~~~
... This fatal error may be caused by the earlier error(s); resolve those first.

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('linter')
test.top_filename = "t/t_interface_nested_port_array.v"
# Issue #5066: Nested interface ports through interface arrays
# (e.g., l2.l1[0] where l1 is an interface array inside interface l2).
# V3Param internal errors have been fixed, but V3LinkDot interface
# connection resolution for array element selections is not yet implemented.
# This test documents the current behavior and should be updated when
# full support is added.
test.lint(fails=True, expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_interface_nested_port.v"
test.compile(verilator_flags2=['--binary', '-fno-inline'])
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,297 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Issue #5066 - Test nested interface ports with type parameters
//
// Tests all parameter patterns in a 5-deep hierarchy using types:
// - Derived: Type width doubles at each level (L3=bits(T)*2, etc.)
// - Hard-coded: L2B.T=logic[7:0] regardless of parent
// - Passthrough: L0A_T flows unchanged from top to L0A
// - Default: L0B uses default T=logic[7:0]
//
// With TOP_T=logic[3:0], L0A_T=logic[15:0]:
// L4(T=4b) -> L3(T=8b) -> L2A(T=16b) -> L1(T=32b) -> L0A(T=16b), L0B(T=8b)
// -> L2B(T=8b) -> L1(T=16b) -> L0A(T=16b), L0B(T=8b)
interface l0_if #(parameter type T = logic [7:0]);
T tb_in;
T dut_out;
endinterface
interface l1_if #(parameter type T = logic, parameter type L0A_T = logic);
T tb_in;
T dut_out;
l0_if #(.T(L0A_T)) l0a(); // passthrough
l0_if l0b(); // default
endinterface
interface l2_if #(parameter type T = logic, parameter type L0A_T = logic);
T tb_in;
T dut_out;
l1_if #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) l1(); // derived
endinterface
interface l3_if #(parameter type T = logic, parameter type L0A_T = logic);
T tb_in;
T dut_out;
l2_if #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) l2a(); // derived
l2_if #(.T(logic [7:0]), .L0A_T(L0A_T)) l2b(); // hard-coded
endinterface
interface l4_if #(parameter type T = logic, parameter type L0A_T = logic);
T tb_in;
T dut_out;
l3_if #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) l3(); // derived
endinterface
// Handlers use type parameters with derived output types
module l0_handler #(parameter type T = logic [7:0])(
input logic clk,
l0_if l0,
output T dout
);
always_ff @(posedge clk) l0.dut_out <= l0.tb_in ^ T'('1);
assign dout = l0.dut_out;
endmodule
module l1_reader #(parameter type T = logic)(
l1_if l1,
output T dout
);
assign dout = l1.dut_out;
endmodule
module l1_driver #(parameter type T = logic)(
input logic clk,
l1_if l1
);
always_ff @(posedge clk) l1.dut_out <= l1.tb_in ^ T'('1);
endmodule
module l1_handler #(parameter type T = logic, parameter type L0A_T = logic)(
input logic clk,
l1_if l1,
output T l1_dout,
output L0A_T l0a_dout,
output logic [7:0] l0b_dout
);
// Use reader/driver submodules instead of direct access
l1_reader #(.T(T)) m_rdr (.l1(l1), .dout(l1_dout));
l1_driver #(.T(T)) m_drv (.clk(clk), .l1(l1));
// Still instantiate l0_handlers for nested ports
l0_handler #(.T(L0A_T)) m_l0a (.clk(clk), .l0(l1.l0a), .dout(l0a_dout));
l0_handler #(.T(logic [7:0])) m_l0b (.clk(clk), .l0(l1.l0b), .dout(l0b_dout));
endmodule
module l2_handler #(parameter type T = logic, parameter type L0A_T = logic)(
input logic clk,
l2_if l2,
output T l2_dout,
output logic [$bits(T)*2-1:0] l1_dout,
output L0A_T l0a_dout,
output logic [7:0] l0b_dout
);
always_ff @(posedge clk) l2.dut_out <= l2.tb_in ^ T'('1);
assign l2_dout = l2.dut_out;
l1_handler #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) m_l1 (
.clk(clk), .l1(l2.l1),
.l1_dout(l1_dout), .l0a_dout(l0a_dout), .l0b_dout(l0b_dout)
);
endmodule
module l3_reader #(parameter type T = logic)(
l3_if l3,
output T dout
);
assign dout = l3.dut_out;
endmodule
module l3_driver #(parameter type T = logic)(
input logic clk,
l3_if l3
);
always_ff @(posedge clk) l3.dut_out <= l3.tb_in ^ T'('1);
endmodule
module l3_handler #(parameter type T = logic, parameter type L0A_T = logic)(
input logic clk,
l3_if l3,
output T l3_dout,
output logic [$bits(T)*2-1:0] l2a_dout,
output logic [$bits(T)*4-1:0] l1_2a_dout,
output L0A_T l0a_2a_dout,
output logic [7:0] l0b_2a_dout,
output logic [7:0] l2b_dout,
output logic [15:0] l1_2b_dout,
output L0A_T l0a_2b_dout,
output logic [7:0] l0b_2b_dout
);
// Use reader/driver submodules instead of direct access
l3_reader #(.T(T)) m_rdr (.l3(l3), .dout(l3_dout));
l3_driver #(.T(T)) m_drv (.clk(clk), .l3(l3));
// Still instantiate l2_handlers for nested ports
l2_handler #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) m_l2a (
.clk(clk), .l2(l3.l2a),
.l2_dout(l2a_dout), .l1_dout(l1_2a_dout),
.l0a_dout(l0a_2a_dout), .l0b_dout(l0b_2a_dout)
);
l2_handler #(.T(logic [7:0]), .L0A_T(L0A_T)) m_l2b (
.clk(clk), .l2(l3.l2b),
.l2_dout(l2b_dout), .l1_dout(l1_2b_dout),
.l0a_dout(l0a_2b_dout), .l0b_dout(l0b_2b_dout)
);
endmodule
module l4_handler #(parameter type T = logic, parameter type L0A_T = logic)(
input logic clk,
l4_if l4,
output T l4_dout,
output logic [$bits(T)*2-1:0] l3_dout,
output logic [$bits(T)*4-1:0] l2a_dout,
output logic [$bits(T)*8-1:0] l1_2a_dout,
output L0A_T l0a_2a_dout,
output logic [7:0] l0b_2a_dout,
output logic [7:0] l2b_dout,
output logic [15:0] l1_2b_dout,
output L0A_T l0a_2b_dout,
output logic [7:0] l0b_2b_dout
);
always_ff @(posedge clk) l4.dut_out <= l4.tb_in ^ T'('1);
assign l4_dout = l4.dut_out;
l3_handler #(.T(logic [$bits(T)*2-1:0]), .L0A_T(L0A_T)) m_l3 (
.clk(clk), .l3(l4.l3),
.l3_dout(l3_dout),
.l2a_dout(l2a_dout), .l1_2a_dout(l1_2a_dout),
.l0a_2a_dout(l0a_2a_dout), .l0b_2a_dout(l0b_2a_dout),
.l2b_dout(l2b_dout), .l1_2b_dout(l1_2b_dout),
.l0a_2b_dout(l0a_2b_dout), .l0b_2b_dout(l0b_2b_dout)
);
endmodule
module t;
logic clk = 0;
int cyc = 0;
localparam type TOP_T = logic [3:0];
localparam type L0A_T = logic [15:0];
l4_if #(.T(TOP_T), .L0A_T(L0A_T)) inst();
logic [3:0] l4_dout;
logic [7:0] l3_dout;
logic [15:0] l2a_dout;
logic [31:0] l1_2a_dout;
logic [15:0] l0a_2a_dout;
logic [7:0] l0b_2a_dout;
logic [7:0] l2b_dout;
logic [15:0] l1_2b_dout;
logic [15:0] l0a_2b_dout;
logic [7:0] l0b_2b_dout;
l4_handler #(.T(TOP_T), .L0A_T(L0A_T)) m_l4 (
.clk(clk), .l4(inst),
.l4_dout(l4_dout),
.l3_dout(l3_dout),
.l2a_dout(l2a_dout), .l1_2a_dout(l1_2a_dout),
.l0a_2a_dout(l0a_2a_dout), .l0b_2a_dout(l0b_2a_dout),
.l2b_dout(l2b_dout), .l1_2b_dout(l1_2b_dout),
.l0a_2b_dout(l0a_2b_dout), .l0b_2b_dout(l0b_2b_dout)
);
always #5 clk = ~clk;
always_ff @(posedge clk) begin
inst.tb_in <= cyc[3:0];
inst.l3.tb_in <= cyc[7:0] + 8'd1;
inst.l3.l2a.tb_in <= cyc[15:0] + 16'd2;
inst.l3.l2a.l1.tb_in <= cyc[31:0] + 32'd3;
inst.l3.l2a.l1.l0a.tb_in <= cyc[15:0] + 16'd4;
inst.l3.l2a.l1.l0b.tb_in <= cyc[7:0] + 8'd5;
inst.l3.l2b.tb_in <= cyc[7:0] + 8'd6;
inst.l3.l2b.l1.tb_in <= cyc[15:0] + 16'd7;
inst.l3.l2b.l1.l0a.tb_in <= cyc[15:0] + 16'd8;
inst.l3.l2b.l1.l0b.tb_in <= cyc[7:0] + 8'd9;
end
logic [3:0] exp_l4;
logic [7:0] exp_l3;
logic [15:0] exp_l2a;
logic [31:0] exp_l1_2a;
logic [15:0] exp_l0a_2a;
logic [7:0] exp_l0b_2a;
logic [7:0] exp_l2b;
logic [15:0] exp_l1_2b;
logic [15:0] exp_l0a_2b;
logic [7:0] exp_l0b_2b;
always_ff @(posedge clk) begin
exp_l4 <= inst.tb_in ^ 4'hF;
exp_l3 <= inst.l3.tb_in ^ 8'hFF;
exp_l2a <= inst.l3.l2a.tb_in ^ 16'hFFFF;
exp_l1_2a <= inst.l3.l2a.l1.tb_in ^ 32'hFFFFFFFF;
exp_l0a_2a <= inst.l3.l2a.l1.l0a.tb_in ^ 16'hFFFF;
exp_l0b_2a <= inst.l3.l2a.l1.l0b.tb_in ^ 8'hFF;
exp_l2b <= inst.l3.l2b.tb_in ^ 8'hFF;
exp_l1_2b <= inst.l3.l2b.l1.tb_in ^ 16'hFFFF;
exp_l0a_2b <= inst.l3.l2b.l1.l0a.tb_in ^ 16'hFFFF;
exp_l0b_2b <= inst.l3.l2b.l1.l0b.tb_in ^ 8'hFF;
end
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc > 3) begin
if (l4_dout !== exp_l4) begin
$display("FAIL cyc=%0d: l4_dout=%h expected %h", cyc, l4_dout, exp_l4);
$stop;
end
if (l3_dout !== exp_l3) begin
$display("FAIL cyc=%0d: l3_dout=%h expected %h", cyc, l3_dout, exp_l3);
$stop;
end
if (l2a_dout !== exp_l2a) begin
$display("FAIL cyc=%0d: l2a_dout=%h expected %h", cyc, l2a_dout, exp_l2a);
$stop;
end
if (l1_2a_dout !== exp_l1_2a) begin
$display("FAIL cyc=%0d: l1_2a_dout=%h expected %h", cyc, l1_2a_dout, exp_l1_2a);
$stop;
end
if (l0a_2a_dout !== exp_l0a_2a) begin
$display("FAIL cyc=%0d: l0a_2a_dout=%h expected %h", cyc, l0a_2a_dout, exp_l0a_2a);
$stop;
end
if (l0b_2a_dout !== exp_l0b_2a) begin
$display("FAIL cyc=%0d: l0b_2a_dout=%h expected %h", cyc, l0b_2a_dout, exp_l0b_2a);
$stop;
end
if (l2b_dout !== exp_l2b) begin
$display("FAIL cyc=%0d: l2b_dout=%h expected %h", cyc, l2b_dout, exp_l2b);
$stop;
end
if (l1_2b_dout !== exp_l1_2b) begin
$display("FAIL cyc=%0d: l1_2b_dout=%h expected %h", cyc, l1_2b_dout, exp_l1_2b);
$stop;
end
if (l0a_2b_dout !== exp_l0a_2b) begin
$display("FAIL cyc=%0d: l0a_2b_dout=%h expected %h", cyc, l0a_2b_dout, exp_l0a_2b);
$stop;
end
if (l0b_2b_dout !== exp_l0b_2b) begin
$display("FAIL cyc=%0d: l0b_2b_dout=%h expected %h", cyc, l0b_2b_dout, exp_l0b_2b);
$stop;
end
end
if (cyc == 20) begin
$write("*-* All Finished *-*\n");
$finish;
end
end
endmodule

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.top_filename = "t/t_interface_nested_port_type.v"
# Type parameters in nested interfaces (no-inline mode)
test.compile(verilator_flags2=['--binary', '-fno-inline'])
test.execute()
test.passes()