parent
515841cf15
commit
57c3b8e51b
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
178
src/V3Param.cpp
178
src/V3Param.cpp
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of either the GNU Lesser General Public License Version 3
|
||||
# or the Perl Artistic License Version 2.0.
|
||||
# SPDX-FileCopyrightText: 2026 Wilson Snyder
|
||||
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
||||
|
||||
import vltest_bootstrap
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile(verilator_flags2=['--binary'])
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
||||
|
|
@ -0,0 +1,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
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue