diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1972577f0..f3511768e 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -656,7 +656,8 @@ class AstCell final : public AstNode { // A instantiation cell or interface call (don't know which until link) // @astgen op1 := pinsp : List[AstPin] // List of port assignments // @astgen op2 := paramsp : List[AstPin] // List of parameter assignments - // @astgen op3 := rangep : Optional[AstRange] // Range for arrayed instances + // @astgen op3 := rangep : List[AstRange] // Range(s) for arrayed instances; multi-dim chains + // via nextp() // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing // // @astgen ptr := m_modp : Optional[AstNodeModule] // [AfterLink] Pointer to module instanced @@ -680,7 +681,7 @@ public: , m_trace{true} { addPinsp(pinsp); addParamsp(paramsp); - this->rangep(rangep); + addRangep(rangep); } ASTGEN_MEMBERS_AstCell; // No cloneRelink, we presume cloneee's want the same module linkages diff --git a/src/V3Inst.cpp b/src/V3Inst.cpp index ad6a04860..d7f2e4b58 100644 --- a/src/V3Inst.cpp +++ b/src/V3Inst.cpp @@ -178,9 +178,8 @@ class InstDeVisitor final : public VNVisitor { // Find all cells with arrays, and convert to non-arrayed private: // STATE - // Range for arrayed instantiations, nullptr for normal instantiations - const AstRange* m_cellRangep = nullptr; - int m_instSelNum = 0; // Current instantiation count 0..N-1 + const AstRange* m_cellRangep = nullptr; // Outer range; nullptr for non-arrayed cells + int m_instSelNum = 0; // Row-major flat index for 1D-compat pin expansion InstDeModVarVisitor m_deModVars; // State of variables for current cell module // VISITORS @@ -233,52 +232,72 @@ private: m_deModVars.main(nodep->modp()); // if (nodep->rangep()) { - m_cellRangep = nodep->rangep(); + // Collect the full range chain (outer first). + std::vector rangesp; + for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) { + rangesp.push_back(rp); + } + m_cellRangep = rangesp.front(); + const int ndim = static_cast(rangesp.size()); + std::vector sizes(ndim); + int totalElems = 1; + for (int d = 0; d < ndim; ++d) { + sizes[d] = rangesp[d]->elementsConst(); + totalElems *= sizes[d]; + } AstVar* const ifaceVarp = VN_CAST(nodep->nextp(), Var); - // cppcheck-suppress constVariablePointer - AstNodeDType* const ifaceVarDtp - = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; - const bool isIface - = ifaceVarp && VN_IS(ifaceVarDtp, UnpackArrayDType) - && VN_IS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), - IfaceRefDType) - && !VN_AS(VN_AS(ifaceVarDtp, UnpackArrayDType)->subDTypep()->skipRefp(), - IfaceRefDType) - ->isVirtual(); + // Peel all UnpackArrayDType layers to reach the bottom IfaceRefDType. + AstIfaceRefDType* origIfaceRefp = nullptr; + AstUnpackArrayDType* innermostArrp = nullptr; + for (AstNodeDType* dp = ifaceVarp ? ifaceVarp->dtypep()->skipRefp() : nullptr; dp;) { + if (AstUnpackArrayDType* const arrp = VN_CAST(dp, UnpackArrayDType)) { + innermostArrp = arrp; + dp = arrp->subDTypep()->skipRefp(); + } else { + origIfaceRefp = VN_CAST(dp, IfaceRefDType); + break; + } + } + const bool isIface = origIfaceRefp && !origIfaceRefp->isVirtual(); - // Make all of the required clones - for (int i = 0; i < m_cellRangep->elementsConst(); i++) { - m_instSelNum - = m_cellRangep->ascending() ? (m_cellRangep->elementsConst() - 1 - i) : i; - const int instNum = m_cellRangep->loConst() + i; + std::vector idx(ndim, 0); + for (int n = 0; n < totalElems; ++n) { + // Unflatten n into a row-major cartesian index; outer dim most significant. + int rem = n; + for (int d = ndim - 1; d >= 0; --d) { + idx[d] = rem % sizes[d]; + rem /= sizes[d]; + } + // Flat select number for 1D-compat pin expansion; ascending dims invert. + // Also build the "__BRA__i__KET__..." suffix (encodeNumber for negative idx). + int flatSel = 0; + string suffix; + for (int d = 0; d < ndim; ++d) { + const int sel = rangesp[d]->ascending() ? (sizes[d] - 1 - idx[d]) : idx[d]; + flatSel = flatSel * sizes[d] + sel; + suffix += "__BRA__" + AstNode::encodeNumber(rangesp[d]->loConst() + idx[d]) + + "__KET__"; + } + m_instSelNum = flatSel; AstCell* const newp = nodep->cloneTree(false); nodep->addNextHere(newp); - // Remove ranging and fix name - newp->rangep()->unlinkFrBack()->deleteTree(); - // Somewhat illogically, we need to rename the original name of the cell too. - // as that is the name users expect for dotting - // The spec says we add [x], but that won't work in C... - newp->name(newp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__"); - newp->origName(newp->origName() + "__BRA__" + cvtToStr(instNum) + "__KET__"); + while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree(); + newp->name(newp->name() + suffix); + newp->origName(newp->origName() + suffix); UINFO(8, " CELL loop " << newp); - // If this AstCell is actually an interface instantiation, also clone the IfaceRef - // within the same parent module as the cell + // Interface instantiation: also clone the IfaceRef in the parent module. if (isIface) { - AstUnpackArrayDType* const arrdtype = VN_AS(ifaceVarDtp, UnpackArrayDType); - AstIfaceRefDType* const origIfaceRefp - = VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType); origIfaceRefp->cellp(nullptr); AstVar* const varNewp = ifaceVarp->cloneTree(false); AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false); - arrdtype->addNextHere(ifaceRefp); + innermostArrp->addNextHere(ifaceRefp); ifaceRefp->cellp(newp); ifaceRefp->cellName(newp->name()); - varNewp->name(varNewp->name() + "__BRA__" + cvtToStr(instNum) + "__KET__"); - varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(instNum) - + "__KET__"); + varNewp->name(varNewp->name() + suffix); + varNewp->origName(varNewp->origName() + suffix); varNewp->dtypep(ifaceRefp); newp->addNextHere(varNewp); if (debug() == 9) { @@ -389,6 +408,109 @@ private: } } else { AstVar* const pinVarp = nodep->modVarp(); + // Multi-dim whole-array iface pin fanout: cartesian-product the port's + // nested UnpackArrayDType layers and emit one pin + per-element var per cell. + // For 1-dim falls through to the original code below. + std::vector portArrs; + for (AstNodeDType* d = pinVarp->dtypep()->skipRefp(); d;) { + if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) { + portArrs.push_back(arrp); + d = arrp->subDTypep()->skipRefp(); + } else { + break; + } + } + if (portArrs.size() >= 2) { + AstIfaceRefDType* const portIrp + = VN_CAST(portArrs.back()->subDTypep()->skipRefp(), IfaceRefDType); + if (!portIrp || portIrp->isVirtual()) return; + const int ndim = static_cast(portArrs.size()); + std::vector sizes(ndim); + int totalElems = 1; + for (int d = 0; d < ndim; ++d) { + sizes[d] = portArrs[d]->elementsConst(); + totalElems *= sizes[d]; + } + const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef); + if (!varrefp) { + nodep->exprp()->v3error("Unexpected connection to arrayed port"); + return; + } + std::vector exprArrs; + for (AstNodeDType* d = varrefp->dtypep()->skipRefp(); d;) { + if (const AstUnpackArrayDType* const arrp = VN_CAST(d, UnpackArrayDType)) { + exprArrs.push_back(arrp); + d = arrp->subDTypep()->skipRefp(); + } else { + break; + } + } + if (exprArrs.size() != static_cast(ndim)) { + nodep->exprp()->v3error( + "Multi-dim iface pin expression rank does not match port"); + return; + } + AstNode* prevp = nullptr; + AstNode* prevPinp = nullptr; + std::vector idx(ndim, 0); + for (int n = 0; n < totalElems; ++n) { + int rem = n; + for (int d = ndim - 1; d >= 0; --d) { + idx[d] = rem % sizes[d]; + rem /= sizes[d]; + } + string portSuffix; + string exprSuffix; + for (int d = 0; d < ndim; ++d) { + portSuffix += "__BRA__" + AstNode::encodeNumber(portArrs[d]->lo() + idx[d]) + + "__KET__"; + exprSuffix += "__BRA__" + AstNode::encodeNumber(exprArrs[d]->lo() + idx[d]) + + "__KET__"; + } + const string varNewName = pinVarp->name() + portSuffix; + AstVar* varNewp = nullptr; + if (!pinVarp->backp()) { + varNewp = m_deModVars.find(varNewName); + } else { + portIrp->cellp(nullptr); + varNewp = pinVarp->cloneTree(false); + varNewp->name(varNewName); + varNewp->origName(varNewp->origName() + portSuffix); + varNewp->dtypep(portIrp); + m_deModVars.insert(varNewp); + if (!prevp) { + prevp = varNewp; + } else { + prevp->addNextHere(varNewp); + } + } + if (!varNewp) { + if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE + nodep->v3fatalSrc("Module dearray failed for " + << AstNode::prettyNameQ(varNewName)); + } + AstPin* const newp = nodep->cloneTree(false); + newp->modVarp(varNewp); + newp->name(newp->name() + portSuffix); + AstVarXRef* const newVarXRefp = new AstVarXRef{ + nodep->fileline(), varrefp->name() + exprSuffix, "", VAccess::WRITE}; + newVarXRefp->varp(newp->modVarp()); + newp->exprp()->unlinkFrBack()->deleteTree(); + newp->exprp(newVarXRefp); + if (!prevPinp) { + prevPinp = newp; + } else { + prevPinp->addNextHere(newp); + } + } + if (prevp) { + pinVarp->replaceWith(prevp); + pushDeletep(pinVarp); + } + nodep->replaceWith(prevPinp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } const AstUnpackArrayDType* const pinArrp = VN_CAST(pinVarp->dtypep()->skipRefp(), UnpackArrayDType); if (!pinArrp || !VN_IS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)) return; @@ -472,28 +594,50 @@ private: } } void visit(AstArraySel* nodep) override { - if (const AstUnpackArrayDType* const arrp - = VN_CAST(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) { - if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; - V3Const::constifyParamsEdit(nodep->bitp()); - const AstConst* const constp = VN_CAST(nodep->bitp(), Const); + // If a parent is also an ArraySel into the same iface array, let it handle the chain. + if (VN_IS(nodep->backp(), ArraySel) && nodep->backp()->op1p() == nodep) return; + // Collect nested ArraySels top-down (nodep is outermost in AST, innermost dim index). + std::vector sels; + AstNode* curp = nodep; + while (AstArraySel* const asp = VN_CAST(curp, ArraySel)) { + sels.push_back(asp); + curp = asp->fromp(); + } + const AstVarRef* const varrefp = VN_CAST(curp, VarRef); + if (!varrefp) return; + // Confirm base is a (possibly nested) UnpackArray wrapping an IfaceRefDType. + std::vector arrs; // outer dim first + AstNodeDType* dtp = varrefp->dtypep()->skipRefp(); + while (AstUnpackArrayDType* const ap = VN_CAST(dtp, UnpackArrayDType)) { + arrs.push_back(ap); + dtp = ap->subDTypep()->skipRefp(); + } + if (arrs.empty() || sels.size() != arrs.size()) return; + AstIfaceRefDType* const irp = VN_CAST(dtp, IfaceRefDType); + if (!irp || irp->isVirtual()) return; + // Constify bitps and collect indices in outer-dim-first order (sels is inner-first). + std::vector indices(sels.size()); + for (size_t i = 0; i < sels.size(); ++i) { + AstArraySel* const asp = sels[i]; + V3Const::constifyParamsEdit(asp->bitp()); + const AstConst* const constp = VN_CAST(asp->bitp(), Const); if (!constp) { - nodep->bitp()->v3warn(E_UNSUPPORTED, - "Non-constant index in RHS interface array selection"); + asp->bitp()->v3warn(E_UNSUPPORTED, + "Non-constant index in RHS interface array selection"); return; } - const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo()); - const AstVarRef* const varrefp = VN_CAST(nodep->fromp(), VarRef); - UASSERT_OBJ(varrefp, nodep, "No interface varref under array"); - AstVarXRef* const newp = new AstVarXRef{ - nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "", - VAccess::READ}; - newp->dtypep(arrp->subDTypep()); - newp->classOrPackagep(varrefp->classOrPackagep()); - nodep->addNextHere(newp); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + indices[sels.size() - 1 - i] = constp->toSInt(); } + string indexStr; + for (size_t i = 0; i < indices.size(); ++i) { + indexStr += "__BRA__" + AstNode::encodeNumber(indices[i] + arrs[i]->lo()) + "__KET__"; + } + AstVarXRef* const newp + = new AstVarXRef{nodep->fileline(), varrefp->name() + indexStr, "", VAccess::READ}; + newp->dtypep(irp); + newp->classOrPackagep(varrefp->classOrPackagep()); + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstNodeAssign* nodep) override { if (AstSliceSel* const arrslicep = VN_CAST(nodep->rhsp(), SliceSel)) { diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index 3eb3cc60e..48134d037 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -776,12 +776,19 @@ class LinkCellsVisitor final : public VNVisitor { idtypep->cellp(nodep); // Only set when real parent cell known. AstVar* varp; if (nodep->rangep()) { - // For arrayed interfaces, we replace cellp when de-arraying in V3Inst - AstNodeArrayDType* const arrp - = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, idtypep, - nodep->rangep()->cloneTree(true)}; + // For arrayed interfaces, we replace cellp when de-arraying in V3Inst. + // Multi-dim arrays wrap one UnpackArrayDType per range, innermost first. + std::vector rangesp; + for (AstRange* rp = nodep->rangep(); rp; rp = VN_CAST(rp->nextp(), Range)) { + rangesp.push_back(rp); + } + AstNodeDType* dtp = idtypep; + for (auto it = rangesp.rbegin(); it != rangesp.rend(); ++it) { + dtp = new AstUnpackArrayDType{nodep->fileline(), VFlagChildDType{}, dtp, + (*it)->cloneTree(false)}; + } varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName, - VFlagChildDType{}, arrp}; + VFlagChildDType{}, dtp}; } else { varp = new AstVar{nodep->fileline(), VVarType::IFACEREF, varName, VFlagChildDType{}, idtypep}; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 31826e53f..2aa4561c9 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -524,17 +524,19 @@ public: void insertIfaceVarSym(VSymEnt* symp) { // Where sym is for a VAR of dtype IFACEREFDTYPE m_ifaceVarSyms.push_back(symp); } - // Iface for a raw or arrayed iface + // Iface for a raw or arrayed iface; peels nested array layers for multi-dim arrays. static AstIfaceRefDType* ifaceRefFromArray(AstNodeDType* nodep) { - AstIfaceRefDType* ifacerefp = VN_CAST(nodep, IfaceRefDType); - if (!ifacerefp) { + while (nodep) { + if (AstIfaceRefDType* const ifp = VN_CAST(nodep, IfaceRefDType)) return ifp; if (const AstBracketArrayDType* const arrp = VN_CAST(nodep, BracketArrayDType)) { - ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType); + nodep = arrp->subDTypep(); } else if (const AstUnpackArrayDType* const arrp = VN_CAST(nodep, UnpackArrayDType)) { - ifacerefp = VN_CAST(arrp->subDTypep(), IfaceRefDType); + nodep = arrp->subDTypep(); + } else { + return nullptr; } } - return ifacerefp; + return nullptr; } // Given a pin expression, resolve it to a live AstIface* (or nullptr). // Handles both simple VarRef and dotted VarXRef pin connections. @@ -770,10 +772,16 @@ public: if (forPrearray()) { // GENFOR Begin is foo__BRA__##__KET__ after we've genloop unrolled, // but presently should be just "foo". - // Likewise cell foo__[array] before we've expanded arrays is just foo - if ((pos = ident.rfind("__BRA__")) != string::npos) { - altIdent = ident.substr(0, pos); + // Likewise cell foo__[array] before we've expanded arrays is just foo. + // Multi-dim iface arrays append multiple __BRA__..__KET__ suffixes; strip them + // all. + altIdent = ident; + while (VString::endsWith(altIdent, "__KET__")) { + const auto braPos = altIdent.rfind("__BRA__"); + if (braPos == string::npos) break; + altIdent = altIdent.substr(0, braPos); } + if (altIdent == ident) altIdent.clear(); } UINFO(8, " id " << ident << " alt " << altIdent << " left " << leftname << " at se" << lookupSymp); @@ -5316,10 +5324,18 @@ class LinkDotResolveVisitor final : public VNVisitor { symIterateNull(nodep->attrp(), m_curSymp); if (m_ds.m_unresolvedCell && (m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST)) { AstNodeExpr* const exprp = nodep->bitp()->unlinkFrBack(); - AstCellArrayRef* const newp - = new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp}; - nodep->replaceWith(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + if (AstCellArrayRef* const fromArrRefp = VN_CAST(nodep->fromp(), CellArrayRef)) { + // Multi-dim iface array access: append this select to the existing chain + fromArrRefp->addSelp(exprp); + AstCellArrayRef* const movedp = fromArrRefp->unlinkFrBack(); + nodep->replaceWith(movedp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else { + AstCellArrayRef* const newp + = new AstCellArrayRef{nodep->fileline(), nodep->fromp()->name(), exprp}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } } } void visit(AstNodePreSel* nodep) override { diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 8a3a70605..0bc8a68a8 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -475,6 +475,12 @@ class ParamProcessor final { } return nullptr; } + // Peel all unpacked-array layers to reach the innermost subDTypep. + // Used for multi-dim iface arrays where port dtype is nested UnpackArrayDType. + static AstNodeDType* arraySubDTypeDeepp(AstNodeDType* nodep) { + while (AstNodeDType* const subp = arraySubDTypep(nodep)) nodep = subp; + return nodep; + } static bool isString(AstNodeDType* nodep) { if (AstBasicDType* const basicp = VN_CAST(nodep->skipRefToNonRefp(), BasicDType)) return basicp->isString(); @@ -1613,38 +1619,32 @@ class ParamProcessor final { const AstVar* const modvarp = pinp->modVarp(); if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue; if (modvarp->isIfaceRef()) { - AstIfaceRefDType* portIrefp = VN_CAST(modvarp->subDTypep(), IfaceRefDType); - if (!portIrefp && arraySubDTypep(modvarp->subDTypep())) { - portIrefp = VN_CAST(arraySubDTypep(modvarp->subDTypep()), IfaceRefDType); - } - AstIfaceRefDType* pinIrefp = nullptr; + // arraySubDTypeDeepp returns input unchanged if not an array. + AstIfaceRefDType* const portIrefp + = VN_CAST(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceRefDType); const AstNode* const exprp = pinp->exprp(); - const AstVar* const varp = (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()) - && VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType)) { - pinIrefp = VN_CAST(arraySubDTypep(varp->subDTypep()), IfaceRefDType); - } else if (exprp && exprp->op1p() && VN_IS(exprp->op1p(), VarRef) - && VN_CAST(exprp->op1p(), VarRef)->varp() - && VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep() - && arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()) - && VN_CAST( - arraySubDTypep(VN_CAST(exprp->op1p(), VarRef)->varp()->subDTypep()), - IfaceRefDType)) { - 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. + if (VN_IS(exprp, CellArrayRef)) { + // CellArrayRef not yet linked; V3LinkDot resolves this pin later. UINFO(9, "Skipping interface cleanup for CellArrayRef pin: " << pinp); continue; } + AstIfaceRefDType* pinIrefp = nullptr; + // Pin is a VarRef to a var of (possibly arrayed) iface type. + if (const AstNodeVarRef* const vrp = VN_CAST(exprp, NodeVarRef)) { + if (vrp->varp()) { + pinIrefp + = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType); + } + } + // Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array). + if (!pinIrefp && exprp) { + if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) { + if (vrp->varp()) { + pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), + IfaceRefDType); + } + } + } UINFO(9, " portIfaceRef " << portIrefp); @@ -2837,33 +2837,46 @@ class ParamVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstCellArrayRef* nodep) override { - V3Const::constifyParamsEdit(nodep->selp()); - if (const AstConst* const constp = VN_CAST(nodep->selp(), Const)) { - const string index = AstNode::encodeNumber(constp->toSInt()); - // 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); - // 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); + // Multi-dim iface arrays chain multiple selps via nextp(); constify each in place. + for (AstNode *s = nodep->selp(), *nxt; s; s = nxt) { + nxt = s->nextp(); // cache before constifyParamsEdit replaces s + V3Const::constifyParamsEdit(s); + } + std::vector indices; + for (AstNode* s = nodep->selp(); s; s = s->nextp()) { + const AstConst* const constp = VN_CAST(s, Const); + if (!constp) { + nodep->v3error("Could not expand constant selection inside dotted reference: " + << s->prettyNameQ()); return; } - m_unlinkedTxt.replace(pos, replacestr.length(), - baseName + "__BRA__" + index + "__KET__"); - } else { - nodep->v3error("Could not expand constant selection inside dotted reference: " - << nodep->selp()->prettyNameQ()); + indices.push_back(constp->toSInt()); + } + // Nested iface-array ports may carry a __Viftop suffix that's not in m_unlinkedTxt. + 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 placeholder = "__BRA__??__KET__"; + size_t pos = m_unlinkedTxt.find(baseName + placeholder); + // An AstCellArrayRef for an iface-array pin expr (e.g. l1(l2.l1[0])) is visited + // outside AstUnlinkedRef, so m_unlinkedTxt has no placeholder; V3LinkDot resolves later. + if (pos == string::npos) { + UINFO(9, "Skipping unlinked text replacement for " << nodep); return; } + pos += baseName.length(); + for (int idx : indices) { + const string replacement = "__BRA__" + AstNode::encodeNumber(idx) + "__KET__"; + if (m_unlinkedTxt.compare(pos, placeholder.length(), placeholder) != 0) { + nodep->v3fatalSrc( // LCOV_EXCL_LINE + "Expected placeholder at position in multi-dim iface dotted reference"); + return; + } + m_unlinkedTxt.replace(pos, placeholder.length(), replacement); + pos += replacement.length(); + } } // Generate Statements diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index fdc0e218b..d0c5ac659 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -106,8 +106,8 @@ AstAssignW* V3ParseGrammar::createSupplyExpr(FileLine* fileline, const string& n return assignp; } -AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { - // Remove any UnsizedRange's from list +AstRange* V3ParseGrammar::scrubRangeMulti(AstNodeRange* nrangep) { + // Remove any UnsizedRange's from list; preserves multi-dim chain via nextp(). for (AstNodeRange *nodep = nrangep, *nextp; nodep; nodep = nextp) { nextp = VN_AS(nodep->nextp(), NodeRange); if (!VN_IS(nodep, Range)) { @@ -117,14 +117,17 @@ AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { VL_DO_DANGLING(nodep->deleteTree(), nodep); } } - if (nrangep && nrangep->nextp()) { - // Not supported by at least 2 of big 3 - nrangep->nextp()->v3warn(E_UNSUPPORTED, - "Unsupported: Multidimensional instances/interfaces."); - nrangep->nextp()->unlinkFrBackWithNext()->deleteTree(); - } return VN_CAST(nrangep, Range); } +AstRange* V3ParseGrammar::scrubRange(AstNodeRange* nrangep) { + AstRange* const rangep = scrubRangeMulti(nrangep); + if (rangep && rangep->nextp()) { + // Gate primitives only support a single dimension + rangep->nextp()->v3warn(E_UNSUPPORTED, "Unsupported: Multidimensional gate instances."); + rangep->nextp()->unlinkFrBackWithNext()->deleteTree(); + } + return rangep; +} AstNodePreSel* V3ParseGrammar::scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED { // SEL(PARSELVALUE, ...) -> SEL(fromp, ...) diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index c7b251f5a..d861d0988 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -71,6 +71,7 @@ public: return v3Global.opt.trace() && m_tracingParse && fl->tracingOn(); } AstRange* scrubRange(AstNodeRange* rangep) VL_MT_DISABLED; + AstRange* scrubRangeMulti(AstNodeRange* rangep) VL_MT_DISABLED; AstNodePreSel* scrubSel(AstNodeExpr* fromp, AstNodePreSel* selp) VL_MT_DISABLED; AstNodeDType* createArray(AstNodeDType* basep, AstNodeRange* rangep, bool isPacked) VL_MT_DISABLED; @@ -90,7 +91,7 @@ public: singletonp()->m_instModule, pinlistp, (singletonp()->m_instParamp ? singletonp()->m_instParamp->cloneTree(true) : nullptr), - singletonp()->scrubRange(rangelistp)}; + singletonp()->scrubRangeMulti(rangelistp)}; nodep->trace(singletonp()->allTracingOn(fileline)); return nodep; } diff --git a/test_regress/t/t_dist_warn_coverage.py b/test_regress/t/t_dist_warn_coverage.py index ea7e24054..a26a23d0b 100755 --- a/test_regress/t/t_dist_warn_coverage.py +++ b/test_regress/t/t_dist_warn_coverage.py @@ -93,6 +93,7 @@ for s in [ 'Invalid reference: Process might outlive variable', 'Modport item is not a function/task:', 'Modport item is not a variable:', + 'Multi-dim iface pin expression rank does not match port', 'Modport not referenced as .', 'Modport not referenced from underneath an interface:', 'Need $SYSTEMC_INCLUDE in environment or when Verilator configured,', diff --git a/test_regress/t/t_gate_array_multidim_bad.out b/test_regress/t/t_gate_array_multidim_bad.out new file mode 100644 index 000000000..78c1a33ab --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_bad.out @@ -0,0 +1,5 @@ +%Error-UNSUPPORTED: t/t_gate_array_multidim_bad.v:12:14: Unsupported: Multidimensional gate instances. + 12 | and g [1:0][1:0] (y, a, b); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_gate_array_multidim_bad.py b/test_regress/t/t_gate_array_multidim_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_gate_array_multidim_bad.v b/test_regress/t/t_gate_array_multidim_bad.v new file mode 100644 index 000000000..509636c7e --- /dev/null +++ b/test_regress/t/t_gate_array_multidim_bad.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Verify multi-dim gate primitive arrays are rejected. + +module t; + wire a, b; + wire [1:0][1:0] y; + and g [1:0][1:0] (y, a, b); +endmodule diff --git a/test_regress/t/t_iface_array_multidim.py b/test_regress/t/t_iface_array_multidim.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim.v b/test_regress/t/t_iface_array_multidim.v new file mode 100644 index 000000000..bb7cbd43b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim.v @@ -0,0 +1,54 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dimensional interface instance arrays: minimal 2D reproducer. +// Exercises declaration, dotted access, multi-dim port connection and +// VCD-relevant naming. + +interface simple_if; + logic [7:0] data; +endinterface + +module t; + localparam int A = 2; + localparam int B = 3; + + simple_if bus [A-1:0][B-1:0] (); + + genvar gi, gj; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + initial bus[gi][gj].data = 8'(gi * B + gj + 1); + end + end + endgenerate + + // Runtime check via a chk array populated by the same genvar generate block. + logic [7:0] chk [A-1:0][B-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + always_comb chk[gi][gj] = bus[gi][gj].data; + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + if (chk[i][j] !== 8'(i * B + j + 1)) begin + $write("%%Error: bus[%0d][%0d].data=%0d expected %0d\n", + i, j, chk[i][j], i * B + j + 1); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_3d.py b/test_regress/t/t_iface_array_multidim_3d.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_3d.v b/test_regress/t/t_iface_array_multidim_3d.v new file mode 100644 index 000000000..26fcc510b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// 3D interface instance arrays. Ensures arbitrary rank, not just 2D. + +interface simple_if; + logic [15:0] data; +endinterface + +module t; + localparam int A = 2; + localparam int B = 2; + localparam int C = 3; + + simple_if bus [A-1:0][B-1:0][C-1:0] (); + + genvar gi, gj, gk; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + for (gk = 0; gk < C; gk++) begin : g_c + initial bus[gi][gj][gk].data = 16'(gi * B * C + gj * C + gk + 1); + end + end + end + endgenerate + + logic [15:0] chk [A-1:0][B-1:0][C-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + for (gk = 0; gk < C; gk++) begin : g_c_chk + always_comb chk[gi][gj][gk] = bus[gi][gj][gk].data; + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + for (int k = 0; k < C; k++) begin + if (chk[i][j][k] !== 16'(i * B * C + j * C + k + 1)) begin + $write("%%Error: bus[%0d][%0d][%0d].data=%0d expected %0d\n", + i, j, k, chk[i][j][k], i * B * C + j * C + k + 1); + $stop; + end + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_3d_port.py b/test_regress/t/t_iface_array_multidim_3d_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d_port.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_3d_port.v b/test_regress/t/t_iface_array_multidim_3d_port.v new file mode 100644 index 000000000..029abf42d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_3d_port.v @@ -0,0 +1,59 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// 3D iface-array port passed to a submodule. t_iface_array_multidim_3d +// covers 3D local declaration; this covers 3D through a port. + +interface simple_if; + logic [15:0] data; +endinterface + +module sink (simple_if b [1:0][1:0][2:0]); + logic [15:0] chk [1:0][1:0][2:0]; + genvar gi, gj, gk; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 2; gj++) begin : g_b + for (gk = 0; gk < 3; gk++) begin : g_c + always_comb chk[gi][gj][gk] = b[gi][gj][gk].data; + end + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][1:0][2:0] (); + sink inst (.b(bus)); + + genvar gi, gj, gk; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 2; gj++) begin : g_drive_b + for (gk = 0; gk < 3; gk++) begin : g_drive_c + initial bus[gi][gj][gk].data = 16'(gi * 6 + gj * 3 + gk + 1); + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 2; j++) begin + for (int k = 0; k < 3; k++) begin + if (inst.chk[i][j][k] !== 16'(i * 6 + j * 3 + k + 1)) begin + $write("%%Error: chk[%0d][%0d][%0d]=%0d expected %0d\n", + i, j, k, inst.chk[i][j][k], i * 6 + j * 3 + k + 1); + $stop; + end + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_hier.py b/test_regress/t/t_iface_array_multidim_hier.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_hier.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_hier.v b/test_regress/t/t_iface_array_multidim_hier.v new file mode 100644 index 000000000..40b0a100d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_hier.v @@ -0,0 +1,58 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Three-level hierarchy passing a multi-dim iface array by port at each +// boundary. Top reads leaf's chk array via dotted cross-hier reference, +// which also exercises the multi-dim dotted-access resolver. + +interface simple_if; + logic [7:0] data; +endinterface + +module leaf (simple_if b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module mid (simple_if b [1:0][2:0]); + leaf leaf_inst (.b(b)); +endmodule + +module t; + simple_if bus [1:0][2:0] (); + mid mid_inst (.b(bus)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 3; gj++) begin : g_drive_b + initial bus[gi][gj].data = 8'(gi * 3 + gj + 7); + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (mid_inst.leaf_inst.chk[i][j] !== 8'(i * 3 + j + 7)) begin + $write("%%Error: leaf.chk[%0d][%0d]=%0d expected %0d\n", + i, j, mid_inst.leaf_inst.chk[i][j], i * 3 + j + 7); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_modport.py b/test_regress/t/t_iface_array_multidim_modport.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_modport.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_modport.v b/test_regress/t/t_iface_array_multidim_modport.v new file mode 100644 index 000000000..d30703f2d --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_modport.v @@ -0,0 +1,59 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dim iface array ports typed with a modport (both source and sink). +// Mirrors 1D modport-port coverage in t_interface_array_loop for the +// multi-dim pin-fanout path. + +interface simple_if; + logic [7:0] data; + modport source(output data); + modport sink(input data); +endinterface + +module src (simple_if.source b [1:0][2:0]); + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + initial b[gi][gj].data = 8'(gi * 3 + gj + 20); + end + end + endgenerate +endmodule + +module snk (simple_if.sink b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + src src_inst (.b(bus)); + snk snk_inst (.b(bus)); + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (snk_inst.chk[i][j] !== 8'(i * 3 + j + 20)) begin + $write("%%Error: chk[%0d][%0d]=%0d expected %0d\n", + i, j, snk_inst.chk[i][j], i * 3 + j + 20); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_nested.py b/test_regress/t/t_iface_array_multidim_nested.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_nested.v b/test_regress/t/t_iface_array_multidim_nested.v new file mode 100644 index 000000000..6a134d92c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested.v @@ -0,0 +1,68 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dim iface array where outer iface contains an inner iface. +// outer_if oarr[A][B](), outer_if holds a single inner_if instance. +// Access via oarr[i][j].inner.data. + +interface inner_if; + logic [7:0] data; +endinterface + +interface outer_if; + inner_if inner(); + logic [7:0] tag; +endinterface + +module t; + localparam int A = 2; + localparam int B = 2; + + outer_if oarr [A-1:0][B-1:0] (); + + genvar gi, gj; + generate + for (gi = 0; gi < A; gi++) begin : g_a + for (gj = 0; gj < B; gj++) begin : g_b + initial begin + oarr[gi][gj].tag = 8'(gi * 16 + gj); + oarr[gi][gj].inner.data = 8'(gi * B + gj + 100); + end + end + end + endgenerate + + logic [7:0] chk_tag [A-1:0][B-1:0]; + logic [7:0] chk_inner [A-1:0][B-1:0]; + generate + for (gi = 0; gi < A; gi++) begin : g_a_chk + for (gj = 0; gj < B; gj++) begin : g_b_chk + always_comb chk_tag[gi][gj] = oarr[gi][gj].tag; + always_comb chk_inner[gi][gj] = oarr[gi][gj].inner.data; + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < A; i++) begin + for (int j = 0; j < B; j++) begin + if (chk_tag[i][j] !== 8'(i * 16 + j)) begin + $write("%%Error: oarr[%0d][%0d].tag=%0d expected %0d\n", + i, j, chk_tag[i][j], i * 16 + j); + $stop; + end + if (chk_inner[i][j] !== 8'(i * B + j + 100)) begin + $write("%%Error: oarr[%0d][%0d].inner.data=%0d expected %0d\n", + i, j, chk_inner[i][j], i * B + j + 100); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_nested_port.py b/test_regress/t/t_iface_array_multidim_nested_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested_port.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_nested_port.v b/test_regress/t/t_iface_array_multidim_nested_port.v new file mode 100644 index 000000000..443b4255b --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_nested_port.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// outer_if contains inner_if; an array of outer_if is passed as a port to +// a submodule which reaches through to inner_if's signal. Exercises +// hierarchical iface reference across the fanned-out multi-dim port. + +interface inner_if; + logic [7:0] data; +endinterface + +interface outer_if; + inner_if inner(); + logic [7:0] tag; +endinterface + +module sink (outer_if b [1:0][1:0]); + logic [7:0] chk_tag [1:0][1:0]; + logic [7:0] chk_inner [1:0][1:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 2; gj++) begin : g_b + always_comb chk_tag[gi][gj] = b[gi][gj].tag; + always_comb chk_inner[gi][gj] = b[gi][gj].inner.data; + end + end + endgenerate +endmodule + +module t; + outer_if oarr [1:0][1:0] (); + sink inst (.b(oarr)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 2; gj++) begin : g_drive_b + initial begin + oarr[gi][gj].tag = 8'(gi * 16 + gj); + oarr[gi][gj].inner.data = 8'(gi * 2 + gj + 100); + end + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 2; j++) begin + if (inst.chk_tag[i][j] !== 8'(i * 16 + j)) begin + $write("%%Error: chk_tag[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk_tag[i][j], i * 16 + j); + $stop; + end + if (inst.chk_inner[i][j] !== 8'(i * 2 + j + 100)) begin + $write("%%Error: chk_inner[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk_inner[i][j], i * 2 + j + 100); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_port.py b/test_regress/t/t_iface_array_multidim_port.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_port.v b/test_regress/t/t_iface_array_multidim_port.v new file mode 100644 index 000000000..7ceb44155 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port.v @@ -0,0 +1,55 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dim iface array passed as a port to a submodule (the point of +// multi-dim iface arrays). Exercises the multi-dim pin-fanout branch in +// V3Inst::visit(AstPin) and the multi-dim branch in V3Inst::visit(AstVar) +// on the sink's port var. + +interface simple_if; + logic [7:0] data; +endinterface + +module sink (simple_if b [1:0][2:0]); + logic [7:0] chk [1:0][2:0]; + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + always_comb chk[gi][gj] = b[gi][gj].data; + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + sink inst (.b(bus)); + + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_drive_a + for (gj = 0; gj < 3; gj++) begin : g_drive_b + initial bus[gi][gj].data = 8'(gi * 3 + gj + 1); + end + end + endgenerate + + initial begin + #1; + for (int i = 0; i < 2; i++) begin + for (int j = 0; j < 3; j++) begin + if (inst.chk[i][j] !== 8'(i * 3 + j + 1)) begin + $write("%%Error: inst.chk[%0d][%0d]=%0d expected %0d\n", + i, j, inst.chk[i][j], i * 3 + j + 1); + $stop; + end + end + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_port_write.py b/test_regress/t/t_iface_array_multidim_port_write.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port_write.py @@ -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() diff --git a/test_regress/t/t_iface_array_multidim_port_write.v b/test_regress/t/t_iface_array_multidim_port_write.v new file mode 100644 index 000000000..161ea9b52 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_port_write.v @@ -0,0 +1,40 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dim iface array port, sink WRITES into the iface signals, top reads. +// Complements t_iface_array_multidim_port (which has sink reading). + +interface simple_if; + logic [7:0] data; +endinterface + +module src (simple_if b [1:0][2:0]); + genvar gi, gj; + generate + for (gi = 0; gi < 2; gi++) begin : g_a + for (gj = 0; gj < 3; gj++) begin : g_b + initial b[gi][gj].data = 8'(gi * 3 + gj + 50); + end + end + endgenerate +endmodule + +module t; + simple_if bus [1:0][2:0] (); + src inst (.b(bus)); + + initial begin + #1; + if (bus[0][0].data !== 8'd50) begin $write("%%Error: bus[0][0]=%0d\n", bus[0][0].data); $stop; end + if (bus[0][1].data !== 8'd51) begin $write("%%Error: bus[0][1]=%0d\n", bus[0][1].data); $stop; end + if (bus[0][2].data !== 8'd52) begin $write("%%Error: bus[0][2]=%0d\n", bus[0][2].data); $stop; end + if (bus[1][0].data !== 8'd53) begin $write("%%Error: bus[1][0]=%0d\n", bus[1][0].data); $stop; end + if (bus[1][1].data !== 8'd54) begin $write("%%Error: bus[1][1]=%0d\n", bus[1][1].data); $stop; end + if (bus[1][2].data !== 8'd55) begin $write("%%Error: bus[1][2]=%0d\n", bus[1][2].data); $stop; end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_iface_array_multidim_ranges.py b/test_regress/t/t_iface_array_multidim_ranges.py new file mode 100755 index 000000000..c39bdad54 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_ranges.py @@ -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') + +# -Wno-ASCRANGE: this test intentionally declares ascending iface array ranges +# ([0:N-1]) to exercise the ascending() branch in V3Inst. +test.compile(verilator_flags2=["--binary", "-Wno-ASCRANGE"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_iface_array_multidim_ranges.v b/test_regress/t/t_iface_array_multidim_ranges.v new file mode 100644 index 000000000..865630b09 --- /dev/null +++ b/test_regress/t/t_iface_array_multidim_ranges.v @@ -0,0 +1,81 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Multi-dim iface arrays with ascending (left