diff --git a/src/V3Ast.cpp b/src/V3Ast.cpp index 0015f4e80..bbd0a64a8 100644 --- a/src/V3Ast.cpp +++ b/src/V3Ast.cpp @@ -208,6 +208,10 @@ string AstNode::dedotName(const string& namein) { return pretty; } +string AstNode::nameNoArray(const string& namein) { + return namein.substr(0, namein.find("__BRA__")); +} + string AstNode::vcdName(const string& namein) { // VCD tracing expects space to separate hierarchy // Dots are reserved for dots the user put in the name diff --git a/src/V3Ast.h b/src/V3Ast.h index 97e2b7138..8a9425dd9 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -658,6 +658,7 @@ public: string origNameProtect() const; // origName with --protect-id applied string shortName() const; // Name with __PVT__ removed for concatenating scopes static string dedotName(const string& namein); // Name with dots removed + static string nameNoArray(const string& namein); // Name with any array index removed static string prettyName(const string& namein) VL_PURE; // Name for printing out to the user static string vpiName(const string& namein); // Name for vpi access static string prettyNameQ(const string& namein) { // Quoted pretty name (for errors) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index a960d7e79..52d53a57e 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -700,7 +700,7 @@ class AstCell final : public AstNode { // @astgen op2 := paramsp : List[AstPin] // List of parameter assignments // Pre V3Param: Range(s) for arrayed instances. // From V3Param to V3Width: ranges of the arrayed cell this element cell was expanded from - // After V3Width: nullptr, except for interface arrays + // After V3Width: nullptr // @astgen op3 := rangep : List[AstRange] // @astgen op4 := intfRefsp : List[AstIntfRef] // List of interface references, for tracing/VPI // @@ -2318,6 +2318,8 @@ class AstVar final : public AstNode { bool m_isPulldown : 1; // Tri0 bool m_isPullup : 1; // Tri1 bool m_isIfaceParent : 1; // dtype is reference to interface present in this module + bool m_isIfaceArraySplit : 1; // Interface array that was split into element variables by + // V3Param, until V3Width removes it bool m_isInternal : 1; // Internal state, don't add to method pinter bool m_isIfaceParam : 1; // Parameter belongs to an interface/modport bool m_isDpiOpenArray : 1; // DPI import open array @@ -2382,6 +2384,7 @@ class AstVar final : public AstNode { m_isPulldown = false; m_isPullup = false; m_isIfaceParent = false; + m_isIfaceArraySplit = false; m_isInternal = false; m_isIfaceParam = false; m_isDpiOpenArray = false; @@ -2543,6 +2546,7 @@ public: void isContinuously(bool flag) { m_isContinuously = flag; } void isStatic(bool flag) { m_isStatic = flag; } void isIfaceParent(bool flag) { m_isIfaceParent = flag; } + void isIfaceArraySplit(bool flag) { m_isIfaceArraySplit = flag; } void isInternal(bool flag) { m_isInternal = flag; } void isIfaceParam(bool flag) { m_isIfaceParam = flag; } void funcLocal(bool flag) { @@ -2630,6 +2634,7 @@ public: m_varType = VVarType::IFACEREF; } bool isIfaceParent() const { return m_isIfaceParent; } + bool isIfaceArraySplit() const { return m_isIfaceArraySplit; } bool isInternal() const { return m_isInternal; } bool isIfaceParam() const { return m_isIfaceParam; } bool isSignal() const { return varType().isSignal(); } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index f5203c98d..671ffabb9 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3924,6 +3924,7 @@ void AstVar::dump(std::ostream& str) const { if (isConst()) str << " [CONST]"; if (isPullup()) str << " [PULLUP]"; if (isPulldown()) str << " [PULLDOWN]"; + if (isIfaceArraySplit()) str << " [IFACEARRAYSPLIT]"; if (isSigPublic()) str << " [P]"; if (isSigUserRdPublic()) str << " [PRD]"; if (isSigUserRWPublic()) str << " [PWR]"; @@ -3968,6 +3969,7 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isConst); dumpJsonBoolFuncIf(str, isPullup); dumpJsonBoolFuncIf(str, isPulldown); + dumpJsonBoolFuncIf(str, isIfaceArraySplit); dumpJsonBoolFuncIf(str, isSigPublic); dumpJsonBoolFuncIf(str, isLatched); dumpJsonBoolFuncIf(str, isUsedLoopIdx); diff --git a/src/V3Inst.cpp b/src/V3Inst.cpp index ed5e60c7b..6c65c5700 100644 --- a/src/V3Inst.cpp +++ b/src/V3Inst.cpp @@ -193,666 +193,6 @@ public: ~InstVisitor() override = default; }; -//###################################################################### - -class InstDeModVarVisitor final : public VNVisitorConst { - // Expand all module variables, and save names for later reference -private: - // STATE - std::map m_modVarNameMap; // Per module, name of cloned variables - - // VISITORS - void visit(AstVar* nodep) override { - if (VN_IS(nodep->dtypep()->skipRefp(), IfaceRefDType)) { - UINFO(8, " dm-1-VAR " << nodep); - insert(nodep); - } - iterateChildrenConst(nodep); - } - void visit(AstNodeExpr*) override {} // Accelerate - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } - -public: - // METHODS - void insert(AstVar* nodep) { - UINFO(8, " dmINSERT " << nodep); - m_modVarNameMap.emplace(nodep->name(), nodep); - } - AstVar* find(const string& name) { - const auto it = m_modVarNameMap.find(name); - if (it != m_modVarNameMap.end()) { - return it->second; - } else { - return nullptr; - } - } - void dump() { - for (const auto& itr : m_modVarNameMap) { - cout << "-namemap: " << itr.first << " -> " << itr.second << '\n'; - } - } - - // CONSTRUCTORS - InstDeModVarVisitor() = default; - ~InstDeModVarVisitor() override = default; - void main(AstNodeModule* nodep) { - UINFO(8, " dmMODULE " << nodep); - m_modVarNameMap.clear(); - iterateConst(nodep); - } -}; - -//###################################################################### -// Replace any leftover VarRef to an interface-array var that the cell -// dearrayer just deleted. The dearrayer handles VarRefs in pin / array-select -// / array-assign contexts itself; this rewriter catches the rest (a class -// new() arg, a function call arg, etc) by rebuilding an array literal over -// the per-element vars. Runs once, only if anything was actually deleted. - -class InstDeOrphanVisitor final : public VNVisitor { - // STATE - const std::unordered_map>& m_dearrayed; - - static bool isDearrayerHandled(const AstNode* backp) { - // VarRefs under these are rewritten by the cell dearrayer itself. - return VN_IS(backp, Pin) || VN_IS(backp, ArraySel) || VN_IS(backp, SliceSel) - || VN_IS(backp, NodeAssign); - } - - // Build a (possibly nested) InitArray matching dtp's array nesting; leaves - // are VarRefs to perElem, consumed in flat row-major order. - static AstNodeExpr* buildPattern(FileLine* flp, AstNodeDType* dtp, - const std::vector& perElem, size_t& idxr, - const VAccess& access) { - AstNodeDType* const skipDtp = dtp->skipRefp(); - if (AstUnpackArrayDType* const arrp = VN_CAST(skipDtp, UnpackArrayDType)) { - AstInitArray* const initp = new AstInitArray{flp, arrp, nullptr}; - const int elems = arrp->elementsConst(); - for (int i = 0; i < elems; ++i) { - initp->addIndexValuep(static_cast(i), - buildPattern(flp, arrp->subDTypep(), perElem, idxr, access)); - } - return initp; - } - UASSERT(idxr < perElem.size(), "buildPattern outran per-element vars"); - return new AstVarRef{flp, perElem[idxr++], access}; - } - - // VISITORS - void visit(AstVarRef* nodep) override { - const auto it = m_dearrayed.find(nodep->varp()); - if (it == m_dearrayed.end()) return; - if (isDearrayerHandled(nodep->backp())) return; - size_t idx = 0; - AstNodeExpr* const newp = buildPattern(nodep->fileline(), it->first->dtypep(), it->second, - idx, nodep->access()); - nodep->replaceWith(newp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - InstDeOrphanVisitor(AstNode* rootp, - const std::unordered_map>& dearrayed) - : m_dearrayed{dearrayed} { - iterate(rootp); - } - ~InstDeOrphanVisitor() override = default; -}; - -//###################################################################### - -class InstDeVisitor final : public VNVisitor { - // Find all cells with arrays, and convert to non-arrayed -private: - // STATE - 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 - // Iface-array vars deleted by visit(AstCell), mapped to their per-element - // replacements in row-major order. The post-pass uses this to fix up any - // leftover VarRefs (see InstDeOrphanVisitor). - std::unordered_map> m_dearrayedIfaceVars; - - // VISITORS - void visit(AstVar* nodep) override { - // cppcheck-suppress constVariablePointer - AstNode* const dtp = nodep->dtypep()->skipRefp(); - if (VN_IS(dtp, UnpackArrayDType) - && VN_IS(VN_AS(dtp, UnpackArrayDType)->subDTypep()->skipRefp(), IfaceRefDType)) { - if (VN_AS(VN_AS(dtp, UnpackArrayDType)->subDTypep()->skipRefp(), IfaceRefDType) - ->isVirtual()) - return; - UINFO(8, " dv-vec-VAR " << nodep); - AstUnpackArrayDType* const arrdtype = VN_AS(dtp, UnpackArrayDType); - AstNode* prevp = nullptr; - for (int i = arrdtype->lo(); i <= arrdtype->hi(); ++i) { - const string varNewName = nodep->name() + "__BRA__" + cvtToStr(i) + "__KET__"; - UINFO(8, "VAR name insert " << varNewName << " " << nodep); - if (!m_deModVars.find(varNewName)) { - AstIfaceRefDType* const ifaceRefp - = VN_AS(arrdtype->subDTypep()->skipRefp(), IfaceRefDType) - ->cloneTree(false); - arrdtype->addNextHere(ifaceRefp); - ifaceRefp->cellp(nullptr); - - AstVar* const varNewp = nodep->cloneTree(false); - varNewp->name(varNewName); - varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(i) + "__KET__"); - varNewp->dtypep(ifaceRefp); - m_deModVars.insert(varNewp); - if (!prevp) { - prevp = varNewp; - } else { - prevp->addNextHere(varNewp); - } - } - } - if (prevp) nodep->addNextHere(prevp); - if (prevp && debug() == 9) { - prevp->dumpTree("- newintf: "); - cout << '\n'; - } - } - iterateChildren(nodep); - } - - void visit(AstCell* nodep) override { - UINFO(4, " CELL " << nodep); - // Find submodule vars - UASSERT_OBJ(nodep->modp(), nodep, "Unlinked"); - m_deModVars.main(nodep->modp()); - // - if (nodep->rangep()) { - // Only interface arrays, V3Param expanded other instance arrays - UASSERT_OBJ(VN_IS(nodep->modp(), Iface), nodep, "Unexpanded instance array"); - // 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); - // 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(); - - // Collected in row-major order for the orphan VarRef rewriter. - std::vector perElemVarps; - if (isIface) perElemVarps.reserve(totalElems); - - 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); - while (newp->rangep()) newp->rangep()->unlinkFrBack()->deleteTree(); - newp->name(newp->name() + suffix); - newp->origName(newp->origName() + suffix); - UINFO(8, " CELL loop " << newp); - - // Interface instantiation: also clone the IfaceRef in the parent module. - if (isIface) { - // Cache the interface module on the dtype so ifaceViaCellp() - // still works after we clear cellp and delete the cell. - if (!origIfaceRefp->ifacep()) { - origIfaceRefp->ifacep(VN_AS(nodep->modp(), Iface)); - } - origIfaceRefp->cellp(nullptr); - AstVar* const varNewp = ifaceVarp->cloneTree(false); - AstIfaceRefDType* const ifaceRefp = origIfaceRefp->cloneTree(false); - innermostArrp->addNextHere(ifaceRefp); - ifaceRefp->cellp(newp); - ifaceRefp->cellName(newp->name()); - varNewp->name(varNewp->name() + suffix); - varNewp->origName(varNewp->origName() + suffix); - varNewp->dtypep(ifaceRefp); - newp->addNextHere(varNewp); - perElemVarps.emplace_back(varNewp); - if (debug() == 9) { - varNewp->dumpTree("- newintf: "); - cout << '\n'; - } - } - // Fixup pins - iterateAndNextNull(newp->pinsp()); - if (debug() == 9) { - newp->dumpTree("- newcell: "); - cout << '\n'; - } - } - - // Done. Delete original - m_cellRangep = nullptr; - if (isIface) { - // Hand the per-element vars to the orphan-VarRef rewriter. - if (!perElemVarps.empty()) { - m_dearrayedIfaceVars.emplace(ifaceVarp, std::move(perElemVarps)); - } - ifaceVarp->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(ifaceVarp), ifaceVarp); - } - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } else { - m_cellRangep = nullptr; - iterateChildren(nodep); - } - } - - void visit(AstPin* nodep) override { - // Any non-direct pins need reconnection with a part-select - if (!nodep->exprp()) return; // No-connect - const AstNodeDType* expDtp = nodep->exprp()->dtypep()->skipRefp(); - if (m_cellRangep) { - UINFO(4, " PIN " << nodep); - const int modwidth = nodep->modVarp()->width(); - const int expwidth = nodep->exprp()->width(); - const std::pair pinDim - = nodep->modVarp()->dtypep()->skipRefp()->dimensions(false); - const std::pair expDim = expDtp->dimensions(false); - UINFO(4, " PINVAR " << nodep->modVarp()); - UINFO(4, " EXP " << nodep->exprp()); - UINFO(4, " expwidth=" << expwidth << " modwidth=" << modwidth - << " expDim(p,u)=" << expDim.first << "," << expDim.second - << " pinDim(p,u)=" << pinDim.first << "," << pinDim.second); - if (expDim.second == pinDim.second + 1) { - // Connection to array, where array dimensions match the instant dimension - const AstRange* const rangep = VN_AS(expDtp, UnpackArrayDType)->rangep(); - const int arraySelNum = rangep->ascending() - ? (rangep->elementsConst() - 1 - m_instSelNum) - : m_instSelNum; - AstNodeExpr* exprp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack(); - exprp = new AstArraySel{exprp->fileline(), exprp, arraySelNum}; - nodep->exprp(exprp); - } else if (expwidth == modwidth) { - // NOP: Arrayed instants: widths match so connect to each instance - } else if (expwidth == modwidth * m_cellRangep->elementsConst()) { - // Arrayed instants: one bit for each of the instants (each - // assign is 1 modwidth wide) - if (m_cellRangep->ascending()) { - nodep->exprp()->v3warn(ASCRANGE, "Ascending instance range connecting to " - "vector: left < right of instance range: [" - << m_cellRangep->leftConst() << ":" - << m_cellRangep->rightConst() << "]"); - } - AstNodeExpr* exprp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack(); - exprp = new AstSel{exprp->fileline(), exprp, modwidth * m_instSelNum, modwidth}; - nodep->exprp(exprp); - } else { - nodep->v3fatalSrc("Width mismatch; V3Width should have errored out."); - } - } // end expanding ranged cell - else if (AstArraySel* const arrselp = VN_CAST(nodep->exprp(), ArraySel)) { - if (const AstUnpackArrayDType* const arrp - = VN_CAST(arrselp->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) { - if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; - // Interface pin attaches to one element of arrayed interface - V3Const::constifyParamsEdit(arrselp->bitp()); - const AstConst* const constp = VN_CAST(arrselp->bitp(), Const); - if (!constp) { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: Non-constant index when passing interface to module"); - return; - } - const string index = AstNode::encodeNumber(constp->toSInt() + arrp->lo()); - if (VN_IS(arrselp->fromp(), SliceSel)) - arrselp->fromp()->v3warn(E_UNSUPPORTED, "Unsupported: interface slices"); - const AstVarRef* const varrefp = VN_CAST(arrselp->fromp(), VarRef); - UASSERT_OBJ(varrefp, arrselp, "No interface varref under array"); - AstVarXRef* const newp = new AstVarXRef{ - nodep->fileline(), varrefp->name() + "__BRA__" + index + "__KET__", "", - VAccess::WRITE}; - newp->dtypep(nodep->modVarp()->dtypep()); - newp->classOrPackagep(varrefp->classOrPackagep()); - arrselp->addNextHere(newp); - VL_DO_DANGLING(arrselp->unlinkFrBack()->deleteTree(), arrselp); - } - } 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]; - } - // V3Width should have already rejected non-VarRef and rank-mismatch pins; - // these are defensive internal-invariant guards. - const AstVarRef* const varrefp = VN_CAST(nodep->exprp(), VarRef); - UASSERT_OBJ(varrefp, nodep->exprp(), "Unexpected connection to arrayed port"); - 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; - } - } - UASSERT_OBJ(exprArrs.size() == static_cast(ndim), nodep->exprp(), - "Multi-dim iface pin expression rank does not match port"); - 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); - prevp = AstNode::addNextNull(prevp, 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); - prevPinp = AstNode::addNextNull(prevPinp, newp); - } - if (prevp) { - pinVarp->replaceWith(prevp); - VL_DO_DANGLING(pushDeletep(pinVarp), 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; - if (VN_AS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; - // Arrayed pin/var attaches to arrayed submodule lower port/var, expand it - AstNode* prevp = nullptr; - AstNode* prevPinp = nullptr; - // Clone the var referenced by the pin, and clone each var referenced by the varref - // Clone pin varp: - for (int in = 0; in < pinArrp->elementsConst(); ++in) { // 0 = leftmost - const int i = pinArrp->left() + in * pinArrp->declRange().leftToRightInc(); - const string varNewName = pinVarp->name() + "__BRA__" + cvtToStr(i) + "__KET__"; - AstVar* varNewp = nullptr; - - // Only clone the var once for all usages of a given child module - if (!pinVarp->backp()) { - varNewp = m_deModVars.find(varNewName); - } else { - AstIfaceRefDType* const ifaceRefp - = VN_AS(pinArrp->subDTypep()->skipRefp(), IfaceRefDType); - ifaceRefp->cellp(nullptr); - varNewp = pinVarp->cloneTree(false); - varNewp->name(varNewName); - varNewp->origName(varNewp->origName() + "__BRA__" + cvtToStr(i) + "__KET__"); - varNewp->dtypep(ifaceRefp); - m_deModVars.insert(varNewp); - prevp = AstNode::addNextNull(prevp, varNewp); - } - if (!varNewp) { - if (debug() >= 9) m_deModVars.dump(); // LCOV_EXCL_LINE - nodep->v3fatalSrc("Module dearray failed for " - << AstNode::prettyNameQ(varNewName)); - } - - // But clone the pin for each module instance - // Now also clone the pin itself and update its varref - AstPin* const newp = nodep->cloneTree(false); - newp->modVarp(varNewp); - newp->name(newp->name() + "__BRA__" + cvtToStr(i) + "__KET__"); - // And replace exprp with a new varxref - const AstVarRef* varrefp = VN_CAST(newp->exprp(), VarRef); // Maybe null - int expr_i = i; - if (const AstSliceSel* const slicep = VN_CAST(newp->exprp(), SliceSel)) { - varrefp = VN_AS(slicep->fromp(), VarRef); - UASSERT_OBJ(VN_IS(slicep->rhsp(), Const), slicep, "Slices should be constant"); - const int slice_index - = slicep->declRange().left() + in * slicep->declRange().leftToRightInc(); - const auto* const exprArrp - = VN_AS(varrefp->dtypep()->skipRefp(), UnpackArrayDType); - UASSERT_OBJ(exprArrp, slicep, "Slice of non-array"); - expr_i = slice_index + exprArrp->lo(); - } else if (!varrefp) { - newp->exprp()->v3error("Unexpected connection to arrayed port"); - } else if (const auto* const exprArrp - = VN_CAST(varrefp->dtypep()->skipRefp(), UnpackArrayDType)) { - expr_i = exprArrp->left() + in * exprArrp->declRange().leftToRightInc(); - } - - const string newname = varrefp->name() + "__BRA__" + cvtToStr(expr_i) + "__KET__"; - AstVarXRef* const newVarXRefp - = new AstVarXRef{nodep->fileline(), newname, "", VAccess::WRITE}; - newVarXRefp->varp(newp->modVarp()); - newp->exprp()->unlinkFrBack()->deleteTree(); - newp->exprp(newVarXRefp); - prevPinp = AstNode::addNextNull(prevPinp, newp); - } - if (prevp) { - pinVarp->replaceWith(prevp); - VL_DO_DANGLING(pushDeletep(pinVarp), pinVarp); - } // else pinVarp already unlinked when another instance did this step - nodep->replaceWith(prevPinp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - void visit(AstArraySel* nodep) override { - // 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) { - asp->bitp()->v3warn(E_UNSUPPORTED, - "Non-constant index in RHS interface array selection"); - return; - } - 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__"; - } - AstMemberSel* const parentSelp = VN_CAST(nodep->backp(), MemberSel); - if (parentSelp && parentSelp->fromp() == nodep && parentSelp->varp()) { - AstVar* const memberVarp = parentSelp->varp(); - AstVarXRef* const newp - = new AstVarXRef{parentSelp->fileline(), memberVarp->name(), - varrefp->name() + indexStr, parentSelp->access()}; - newp->varp(memberVarp); - newp->dtypep(parentSelp->dtypep()); - newp->classOrPackagep(varrefp->classOrPackagep()); - parentSelp->replaceWith(newp); - VL_DO_DANGLING(pushDeletep(parentSelp), parentSelp); - return; - } - 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)) { - if (const AstUnpackArrayDType* const arrp - = VN_CAST(arrslicep->fromp()->dtypep()->skipRefp(), UnpackArrayDType)) { - if (!VN_IS(arrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (VN_AS(arrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) return; - arrslicep->v3warn(E_UNSUPPORTED, "Interface slices unsupported"); - return; - } - } else { - if (const AstUnpackArrayDType* const rhsarrp - = VN_CAST(nodep->rhsp()->dtypep()->skipRefp(), UnpackArrayDType)) { - if (const AstUnpackArrayDType* const lhsarrp - = VN_CAST(nodep->lhsp()->dtypep()->skipRefp(), UnpackArrayDType)) { - // copy between arrays - if (!VN_IS(lhsarrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (!VN_IS(rhsarrp->subDTypep()->skipRefp(), IfaceRefDType)) return; - if (VN_AS(rhsarrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) - return; - if (!VN_AS(lhsarrp->subDTypep()->skipRefp(), IfaceRefDType)->isVirtual()) { - nodep->v3warn(E_UNSUPPORTED, "Unexpected target of interface assignment [" - << rhsarrp->prettyDTypeNameQ() << "]"); - return; - } - if (lhsarrp->elementsConst() != rhsarrp->elementsConst()) { - nodep->v3warn(E_UNSUPPORTED, - "Array size mismatch in interface assignment"); - return; - } - for (int i = 0; i < lhsarrp->elementsConst(); ++i) { - const string index = AstNode::encodeNumber(i); - AstNodeExpr* lhsp = nullptr; - if (AstVarRef* const varrefp = VN_CAST(nodep->lhsp(), VarRef)) { - AstVarRef* const newvarp = varrefp->cloneTree(false); - AstArraySel* newarrselp = new AstArraySel{ - nodep->fileline(), newvarp, - new AstConst{nodep->fileline(), static_cast(i)}}; - lhsp = newarrselp; - } else if (AstMemberSel* const prevselp - = VN_CAST(nodep->lhsp(), MemberSel)) { - AstMemberSel* membselp = prevselp->cloneTree(false); - AstArraySel* newarrselp = new AstArraySel{ - nodep->fileline(), membselp, - new AstConst{nodep->fileline(), static_cast(i)}}; - lhsp = newarrselp; - } else { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported LHS node type in array assignment"); - return; - } - const AstVarRef* const rhsrefp = VN_CAST(nodep->rhsp(), VarRef); - AstVarXRef* const rhsp = new AstVarXRef{ - nodep->fileline(), rhsrefp->name() + "__BRA__" + index + "__KET__", "", - VAccess::READ}; - rhsp->dtypep(rhsarrp->subDTypep()->skipRefp()); - rhsp->classOrPackagep(rhsrefp->classOrPackagep()); - AstAssign* const assignp = new AstAssign{nodep->fileline(), lhsp, rhsp}; - nodep->addNextHere(assignp); - } - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - } - } - iterateChildren(nodep); - } - - //-------------------- - void visit(AstNode* nodep) override { iterateChildren(nodep); } - void visit(AstNew* nodep) override { iterateChildren(nodep); } - void visit(AstMethodCall* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit InstDeVisitor(AstNetlist* nodep) { - iterate(nodep); - // Skip when nothing was deleted; designs without iface arrays pay nothing. - if (!m_dearrayedIfaceVars.empty()) { InstDeOrphanVisitor{nodep, m_dearrayedIfaceVars}; } - } - ~InstDeVisitor() override = default; -}; - //###################################################################### // Inst static function @@ -1005,9 +345,3 @@ void V3Inst::instAll(AstNetlist* nodep) { { InstVisitor{nodep}; } // Destruct before checking V3Global::dumpCheckGlobalTree("inst", 0, dumpTreeEitherLevel() >= 3); } - -void V3Inst::dearrayAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { InstDeVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("dearray", 0, dumpTreeEitherLevel() >= 6); -} diff --git a/src/V3Inst.h b/src/V3Inst.h index 91bba478d..576b80001 100644 --- a/src/V3Inst.h +++ b/src/V3Inst.h @@ -30,7 +30,6 @@ class AstPin; class V3Inst final { public: static void instAll(AstNetlist* nodep) VL_MT_DISABLED; - static void dearrayAll(AstNetlist* nodep) VL_MT_DISABLED; static AstAssignW* pinReconnectSimple(AstPin* pinp, AstCell* cellp, bool forTristate, bool alwaysCvt = false) VL_MT_DISABLED; static void checkOutputShort(const AstPin* nodep) VL_MT_DISABLED; diff --git a/src/V3LinkCells.cpp b/src/V3LinkCells.cpp index efa744199..ed14045ac 100644 --- a/src/V3LinkCells.cpp +++ b/src/V3LinkCells.cpp @@ -780,7 +780,6 @@ 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. // 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)) { diff --git a/src/V3LinkDotIfaceCapture.cpp b/src/V3LinkDotIfaceCapture.cpp index 686b68bbb..79a859edc 100644 --- a/src/V3LinkDotIfaceCapture.cpp +++ b/src/V3LinkDotIfaceCapture.cpp @@ -540,13 +540,14 @@ AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp, component = remaining.substr(0, dotPos); remaining = remaining.substr(dotPos + 1); } - const size_t braPos = component.find("__BRA__"); - const string componentBase - = (braPos == string::npos) ? component : component.substr(0, braPos); + // Matched without any array index; the elements of an instance array (see V3Param) all + // instantiate the same module, so any of them will do + const string componentBase = AstNode::nameNoArray(component); AstNodeModule* nextModp = nullptr; for (AstNode* sp = curModp->stmtsp(); sp; sp = sp->nextp()) { if (AstCell* const cellp = VN_CAST(sp, Cell)) { - if ((cellp->name() == component || cellp->name() == componentBase) + if ((cellp->name() == component + || AstNode::nameNoArray(cellp->name()) == componentBase) && cellp->modp()) { nextModp = cellp->modp(); break; @@ -559,7 +560,8 @@ AstNodeModule* V3LinkDotIfaceCapture::followCellPath(AstNodeModule* startModp, if (viftopPos != string::npos) { varBaseName = varBaseName.substr(0, viftopPos); } - if (varBaseName == component || varBaseName == componentBase) { + if (varBaseName == component + || AstNode::nameNoArray(varBaseName) == componentBase) { if (AstIfaceRefDType* const irefp = ifaceRefFromVarDType(varp->subDTypep())) { AstIface* const ifacep = irefp->ifaceViaCellp(); diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 066097239..42493dd46 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -357,59 +357,6 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { pinp->modVarp(oldvarp); cellp->addPinsp(pinp); } - } else if (VN_IS(subtypep, UnpackArrayDType)) { - const AstUnpackArrayDType* const oldarrp - = VN_AS(subtypep, UnpackArrayDType); - const AstNodeDType* const arrsubtypep = oldarrp->subDTypep(); - if (VN_IS(arrsubtypep, IfaceRefDType)) { - const AstIfaceRefDType* const ifacerefp - = VN_AS(arrsubtypep, IfaceRefDType); - if (!ifacerefp->cellp()) { - string name = oldvarp->name(); - if (dupNames.find(name) != dupNames.end()) { - // __02E=. while __DOT__ looks nicer but will break V3LinkDot - name = oldmodp->name() + "__02E" + name; - } - - AstUnpackArrayDType* arraydtypep - = VN_AS(oldvarp->dtypep(), UnpackArrayDType); - AstCell* ifacearraycellp - = new AstCell{newmodp->fileline(), - newmodp->fileline(), - name, - ifacerefp->ifaceName(), - nullptr, - nullptr, - arraydtypep->rangep()->cloneTree(true)}; - ifacearraycellp->modp(ifacerefp->ifacep()); - newmodp->addStmtsp(ifacearraycellp); - - AstIfaceRefDType* const idtypep = new AstIfaceRefDType{ - newmodp->fileline(), name, ifacerefp->ifaceName()}; - idtypep->ifacep(nullptr); - idtypep->dtypep(idtypep); - idtypep->cellp(ifacearraycellp); - rootp->typeTablep()->addTypesp(idtypep); - - AstNodeArrayDType* const arrp = new AstUnpackArrayDType{ - newmodp->fileline(), idtypep, - arraydtypep->rangep()->cloneTree(true)}; - AstVar* varp = new AstVar{newmodp->fileline(), VVarType::IFACEREF, - name + "__Viftop", arrp}; - varp->isIfaceParent(true); - ifacearraycellp->addNextHere(varp); - ifacearraycellp->hasIfaceVar(true); - rootp->typeTablep()->addTypesp(arrp); - - AstPin* const pinp = new AstPin{ - oldvarp->fileline(), 0, varp->name(), - new AstVarRef{varp->fileline(), varp, - oldvarp->isWritable() ? VAccess::WRITE - : VAccess::READ}}; - pinp->modVarp(oldvarp); - cellp->addPinsp(pinp); - } - } } } } diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 112f234ee..6465b7ce8 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -40,9 +40,10 @@ // Then process all modules called by that cell. // (Cells never referenced after parameters expanded must be ignored.) // -// Arrayed instances of modules are expanded into their elements before -// the above, each element knowing its position in the array (for V3Width -// to select its part of the port connections). +// Arrayed instances of modules and interfaces are expanded into their +// elements before the above, each element knowing its position in the +// array (for V3Width to select its part of the port connections). +// Interface array ports are split into a variable for each element. // // After we complete parameters, the varp's will be wrong (point to old module) // and must be relinked. @@ -855,10 +856,10 @@ class ParamProcessor final { const size_t lastDot = cellPath.rfind('.'); const string lastComp = (lastDot == string::npos) ? cellPath : cellPath.substr(lastDot + 1); - const size_t braPos = lastComp.find("__BRA__"); - const string lastCompBase - = (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos); - if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false; + if (lastComp != cloneCellp->name() + && AstNode::nameNoArray(lastComp) != cloneCellp->name()) { + return false; + } // No parent portion to verify - startModp itself must be the expected parent if (lastDot == string::npos) return startModp == expectModp; const string parentPath = cellPath.substr(0, lastDot); @@ -1832,9 +1833,14 @@ class ParamProcessor final { = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType); } } - // Pin's op1p is a VarRef (e.g. SelBit/ArraySel into an iface array). + // Pin is a select, of any depth, of a VarRef (e.g. SelBit/ArraySel into an iface + // array, or a slice of a row of a multi-dimensional one) if (!pinIrefp && exprp) { - if (const AstVarRef* const vrp = VN_CAST(exprp->op1p(), VarRef)) { + const AstNode* basep = exprp; + while (const AstNodePreSel* const selp = VN_CAST(basep, NodePreSel)) { + basep = selp->fromp(); + } + if (const AstVarRef* const vrp = VN_CAST(basep, VarRef)) { if (vrp->varp()) { pinIrefp = VN_CAST(arraySubDTypeDeepp(vrp->varp()->subDTypep()), IfaceRefDType); @@ -3049,11 +3055,45 @@ class ParamVisitor final : public VNVisitor { return false; } + // Split interface array port 'portp' into a variable for each of its elements, in the + // dimensions of 'dtypep' inwards. 'suffix' is the name suffix of the dimensions outside + // 'dtypep'. + void expandIfaceArrayPortDimensions(AstVar* portp, AstNodeDType* dtypep, + const std::string& suffix) { + const AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType); + // Base case: add the element variable when no dimensions left + if (!arrp) { + AstVar* const varp = portp->cloneTree(false); + varp->name(portp->name() + suffix); + varp->origName(portp->origName() + suffix); + if (AstNodeDType* const oldp = varp->childDTypep()) { + VL_DO_DANGLING(pushDeletep(oldp->unlinkFrBack()), oldp); + } + varp->dtypep(nullptr); + varp->childDTypep(portp->subDTypep()->elemDTypep()->cloneTree(false)); + portp->addNextHere(varp); + return; + } + + // Enumerate the current dimension given by 'arrp' + // Each element is added right after 'portp', so go from right to left, + // to end with an enumeration from the left index to the right index. + const int left = arrp->left(); + const int right = arrp->right(); + const int step = arrp->declRange().ascending() ? 1 : -1; + for (int n = right; n != left - step; n -= step) { + const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(n) + "__KET__"; + expandIfaceArrayPortDimensions(portp, arrp->subDTypep(), s); + } + } + // Add the elements of instance array 'arrayedCellp' in the dimensions from 'rangep' inwards. // 'suffix' and 'idx' are the name suffix and row-major position (each dimension counted from - // the left) of the dimensions outside 'rangep'. - void expandCellArrayDimensions(AstCell* arrayedCellp, const AstRange* rangep, - const std::string& suffix, int idx) { + // the left) of the dimensions outside 'rangep'. For an array of interfaces, 'ifaceVarp' is + // the variable referencing the whole array (see V3LinkCells), which is also split into + // variables referencing the elements. + void expandCellArrayDimensions(AstCell* arrayedCellp, AstVar* ifaceVarp, + const AstRange* rangep, const std::string& suffix, int idx) { // Base case: insert the element when no dimensions left if (!rangep) { AstCell* const elemp = arrayedCellp->cloneTree(false); @@ -3061,6 +3101,22 @@ class ParamVisitor final : public VNVisitor { elemp->origName(arrayedCellp->origName() + suffix); elemp->arrayIdx(idx); arrayedCellp->addNextHere(elemp); + if (ifaceVarp) { + AstIfaceRefDType* const irefp + = VN_AS(ifaceVarp->subDTypep()->elemDTypep(), IfaceRefDType)->cloneTree(false); + irefp->cellp(elemp); + irefp->cellName(elemp->name()); + // Named like the variable of any interface cell (see V3LinkCells) + AstVar* const varp = ifaceVarp->cloneTree(false); + varp->name(elemp->name() + "__Viftop"); + varp->origName(elemp->origName() + "__Viftop"); + if (AstNodeDType* const oldp = varp->childDTypep()) { + VL_DO_DANGLING(pushDeletep(oldp->unlinkFrBack()), oldp); + } + varp->dtypep(nullptr); + varp->childDTypep(irefp); + ifaceVarp->addNextHere(varp); + } return; } @@ -3074,7 +3130,7 @@ class ParamVisitor final : public VNVisitor { const AstRange* const subRangep = VN_AS(rangep->nextp(), Range); for (int n = right; n != left - step; n -= step) { const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(n) + "__KET__"; - expandCellArrayDimensions(arrayedCellp, subRangep, s, --idx); + expandCellArrayDimensions(arrayedCellp, ifaceVarp, subRangep, s, --idx); } } @@ -3178,14 +3234,34 @@ class ParamVisitor final : public VNVisitor { iterateChildren(nodep); } void visit(AstCell* nodep) override { - // Interface arrays are still expanded in V3Inst - if (nodep->rangep() && nodep->arrayIdx() < 0 && !VN_IS(nodep->modp(), Iface)) { - // Expand the instance array into its elements + if (nodep->rangep() && nodep->arrayIdx() < 0) { + // Type check the ranges for (AstRange* rangep = nodep->rangep(); rangep; rangep = VN_AS(rangep->nextp(), Range)) { rangep = VN_AS(V3Width::widthParamsEdit(rangep), Range); } - expandCellArrayDimensions(nodep, nodep->rangep(), "", 0); + // If it's an interface, pick up the corresponding AstIfaceRefDType variable + AstVar* const ifaceVarp = [&]() -> AstVar* { + if (!VN_IS(nodep->modp(), Iface)) return nullptr; + // The variable referencing the whole array, right after the cell (see V3LinkCells) + AstVar* const varp = VN_CAST(nodep->nextp(), Var); + UASSERT_OBJ(varp && varp->name() == nodep->name() + "__Viftop", nodep, + "No __Viftop variable for interface array"); + return varp; + }(); + // Expand the instance array into its elements + expandCellArrayDimensions(nodep, ifaceVarp, nodep->rangep(), "", 0); + if (ifaceVarp) { + ifaceVarp->isIfaceArraySplit(true); + // Until V3Width removes it, the whole array variable refers to the first + // element, which is also used for accessing the parameters of the array + AstCell* const firstp = VN_AS(nodep->nextp(), Cell); + AstIfaceRefDType* const irefp + = VN_AS(ifaceVarp->subDTypep()->elemDTypep(), IfaceRefDType); + irefp->cellp(firstp); + irefp->cellName(firstp->name()); + m_ifaceInstCells.emplace(nodep->name(), firstp); + } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); // The elements replaced the arrayed cell in place, so will be iterated next. return; @@ -3283,6 +3359,15 @@ class ParamVisitor final : public VNVisitor { // Make sure all parameters are constantified void visit(AstVar* nodep) override { if (nodep->user2SetOnce()) return; // Process once + // Interface array port: split it into its elements + if (nodep->isIfaceRef() && !nodep->isIfaceParent() + && (VN_IS(nodep->subDTypep()->skipRefp(), UnpackArrayDType) + || VN_IS(nodep->subDTypep()->skipRefp(), BracketArrayDType))) { + // Also converts any C-style [N] dimensions, the ranges must be constant + V3Width::widthParamsEdit(nodep->subDTypep()); + expandIfaceArrayPortDimensions(nodep, nodep->subDTypep(), ""); + nodep->isIfaceArraySplit(true); + } // Build cache of interface port names as we encounter them if (nodep->isIfaceRef()) { m_ifacePortNames.insert(nodep->name()); } iterateChildren(nodep); @@ -3398,6 +3483,11 @@ class ParamVisitor final : public VNVisitor { nodep->varp(nullptr); return; } + // Parameters of an interface array are accessed through its first element + if (nodep->varp() && nodep->varp()->isParam()) { + const auto it = m_ifaceInstCells.find(nodep->dotted()); + if (it != m_ifaceInstCells.end()) nodep->dotted(it->second->name()); + } // Check to see if the scope is just an interface because interfaces are special const string dotted = nodep->dotted(); if (!dotted.empty() && nodep->varp() && nodep->varp()->isParam()) { @@ -3409,20 +3499,9 @@ class ParamVisitor final : public VNVisitor { } if (const AstVar* const varp = VN_CAST(backp, Var)) { if (!varp->isIfaceRef()) continue; - const AstIfaceRefDType* ifacerefp = nullptr; - if (const AstNodeDType* const typep = varp->childDTypep()) { - ifacerefp = VN_CAST(typep, IfaceRefDType); - if (!ifacerefp) { - if (VN_IS(typep, UnpackArrayDType)) { - ifacerefp = VN_CAST(typep->getChildDTypep(), IfaceRefDType); - } - } - if (!ifacerefp) { - if (VN_IS(typep, BracketArrayDType)) { - ifacerefp = VN_CAST(typep->subDTypep(), IfaceRefDType); - } - } - } + // Through all dimensions of an array + const AstIfaceRefDType* const ifacerefp + = VN_CAST(varp->subDTypep()->elemDTypep(), IfaceRefDType); if (!ifacerefp) continue; // Interfaces passed in on the port map have ifaces if (const AstIface* const ifacep = ifacerefp->ifacep()) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 1fe5f3367..d69fc5fd0 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -272,6 +272,7 @@ class WidthVisitor final : public VNVisitor { AstNode* m_seqUnsupp = nullptr; // Property has unsupported node bool m_hasSExpr = false; // Property has a sequence expression const AstCell* m_cellp = nullptr; // Current cell for arrayed instantiations + std::vector m_splitIfaceArrayVarps; // Interface arrays split by V3Param const AstEnumItem* m_enumItemp = nullptr; // Current enum item AstNodeFTask* m_ftaskp = nullptr; // Current function/task AstNodeModule* m_modep = nullptr; // Current module @@ -1301,6 +1302,26 @@ class WidthVisitor final : public VNVisitor { if (m_vup->prelim()) { iterateCheckSizedSelf(nodep, "Bit select", nodep->bitp(), SELF, BOTH); userIterateAndNext(nodep->fromp(), WidthVP{SELF, BOTH}.p()); + // Element of a whole array of interfaces + if (VN_IS(nodep->fromp(), InitArray) && isIfaceArrayDType(nodep->fromp()->dtypep())) { + V3Const::constifyParamsEdit(nodep->bitp()); // May relink pointed to node + if (const AstConst* const constp = VN_CAST(nodep->bitp(), Const)) { + const int bit = constp->toSInt(); + AstNodeExpr* newp = newIfaceArrayElement(nodep->fromp(), bit); + if (!newp) { + // No such instance, so an error, unlike other out of range selects + const AstUnpackArrayDType* const arrp + = VN_AS(nodep->fromp()->dtypep()->skipRefp(), UnpackArrayDType); + nodep->v3error("Selection index out of range of interface array: " + << bit + arrp->lo() << " outside " << arrp->hi() << ":" + << arrp->lo() << " (IEEE 1800-2023 23.6)"); + newp = newIfaceArrayElement(nodep->fromp(), 0); + } + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } // int frommsb; int fromlsb; @@ -1482,6 +1503,19 @@ class WidthVisitor final : public VNVisitor { << adtypep->declRange() << "'"); } } + // A slice of a whole array of interfaces is the array of the selected elements + if (VN_IS(nodep->fromp(), InitArray) && isIfaceArrayDType(nodep->fromp()->dtypep())) { + AstInitArray* const newp = new AstInitArray{nodep->fileline(), newDtp, nullptr}; + const VNumRange range = nodep->declRange(); + for (int n = 0; n < range.elements(); ++n) { + AstNodeExpr* ep = newIfaceArrayElement(nodep->fromp(), range.lo() + n); + // If out of range, reported above, so use any element to continue + if (!ep) ep = newIfaceArrayElement(nodep->fromp(), 0); + newp->addIndexValuep(n, ep); + } + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } } } @@ -3172,6 +3206,8 @@ class WidthVisitor final : public VNVisitor { // Make sure dtype is sized nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->subDTypep())); UASSERT_OBJ(nodep->dtypep(), nodep, "No dtype determined for var"); + // Removed at the end, see mainAcceptEdit + if (!m_paramsOnly && nodep->isIfaceArraySplit()) m_splitIfaceArrayVarps.push_back(nodep); if (nodep->attrsp()) { nodep->attrsp()->foreach([this, nodep](AstAttrOf* attrp) { if (attrp->attrType() == VAttrType::VAR_PORT_DTYPE) { @@ -3345,6 +3381,10 @@ class WidthVisitor final : public VNVisitor { // Var hasn't been widthed, so make it so. userIterate(nodep->varp(), nullptr); } + if (!m_paramsOnly && nodep->varp()->isIfaceArraySplit()) { + replaceSplitIfaceArrayRef(nodep); + return; + } // UINFOTREE(9, nodep, "", "VRin"); // UINFOTREE(9, nodep->varp(), "", "forvar"); // Note genvar's are also entered as integers @@ -3661,7 +3701,8 @@ class WidthVisitor final : public VNVisitor { assertAtExpr(nodep); if (m_vup->prelim()) { // First stage evaluation AstNodeDType* const vdtypep = m_vup->dtypeNullp(); - if (!nodep->dtypep() || vdtypep) { + // A whole array of interfaces keeps its type, for its connections to be checked + if (!nodep->dtypep() || (vdtypep && !isIfaceArrayDType(nodep->dtypep()))) { UASSERT_OBJ(vdtypep, nodep, "InitArray type not assigned by AstPattern/Var visitor"); nodep->dtypep(vdtypep); @@ -4095,10 +4136,22 @@ class WidthVisitor final : public VNVisitor { foundp = clockingp->ensureEventp(); if (AstVar* const varp = VN_CAST(foundp, Var)) { if (!varp->didWidth()) userIterate(varp, nullptr); - nodep->dtypep(foundp->dtypep()); - nodep->varp(varp); AstIface* const ifacep = adtypep->ifaceViaCellp(); varp->sensIfacep(ifacep); + // Member of a non-virtual interface reference, e.g. an element selected + // from a whole array of interfaces: reference it through the interface, + // as V3LinkDot does for 'iface.member' + const AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef); + if (refp && !adtypep->isVirtual() && refp->varp()->isIfaceRef()) { + AstVarXRef* const newp = new AstVarXRef{nodep->fileline(), varp, + refp->name(), nodep->access()}; + newp->didWidth(true); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + nodep->dtypep(foundp->dtypep()); + nodep->varp(varp); nodep->didWidth(true); return; } @@ -6628,6 +6681,16 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->lhsp(), WidthVP{SELF, BOTH}.p()); UASSERT_OBJ(nodep->lhsp()->dtypep(), nodep, "How can LHS be untyped?"); UASSERT_OBJ(nodep->lhsp()->dtypep()->widthSized(), nodep, "How can LHS be unsized?"); + // An interface instance, or an array or slice of them, is not a variable + if (const AstIfaceRefDType* const irefp + = VN_CAST(nodep->lhsp()->dtypep()->elemDTypep(), IfaceRefDType)) { + if (!irefp->isVirtual()) { + nodep->v3error( + "Illegal assignment to an interface instance (IEEE 1800-2023 10.4)"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + return; + } + } nodep->dtypeFrom(nodep->lhsp()); // // AstPattern needs to know the proposed data type of the lhs, so pass on the prelim @@ -7186,9 +7249,14 @@ class WidthVisitor final : public VNVisitor { // Connection is self-determined userIterateAndNext(nodep->exprp(), WidthVP{conDTypep, FINAL, STREAM_USE_ASSIGN}.p()); AstNodeExpr* const fromp = VN_AS(nodep->exprp(), NodeExpr)->unlinkFrBack(); - AstArraySel* const selp = new AstArraySel{fromp->fileline(), fromp, bit}; - selp->didWidth(true); - nodep->exprp(selp); + if (AstNodeExpr* const elemp = newIfaceArrayElement(fromp, bit)) { + nodep->exprp(elemp); + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else { + AstArraySel* const selp = new AstArraySel{fromp->fileline(), fromp, bit}; + selp->didWidth(true); + nodep->exprp(selp); + } return pinInstArrayElement(nodep, subRangep, idx % subElems); } @@ -7256,6 +7324,7 @@ class WidthVisitor final : public VNVisitor { userIterate(nodep->modVarp(), nullptr); } if (!nodep->exprp()) { // No-connect + if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep); return; } // Very much like like an assignment, but which side is LH/RHS @@ -7273,6 +7342,18 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(conDTypep, nodep, "Unlinked pin data type"); modDTypep = modDTypep->skipRefp(); conDTypep = conDTypep->skipRefp(); + // An interface port, or an array of them, needs interface instances, not virtual + // interfaces + if (const AstIfaceRefDType* const modIrefp + = VN_CAST(modDTypep->elemDTypep(), IfaceRefDType)) { + const AstIfaceRefDType* const conIrefp + = VN_CAST(conDTypep->elemDTypep(), IfaceRefDType); + if (!modIrefp->isVirtual() && conIrefp && conIrefp->isVirtual()) { + nodep->v3error("Illegal " << nodep->prettyOperatorName() + << ", interface port connected to a virtual" + " interface (IEEE 1800-2023 23.3.3.4)"); + } + } AstNodeDType* subDTypep = modDTypep; const int modwidth = modDTypep->width(); int conwidth = conDTypep->width(); @@ -7280,34 +7361,14 @@ class WidthVisitor final : public VNVisitor { || similarDTypeRecurse(conDTypep, modDTypep)) { userIterateAndNext(nodep->exprp(), WidthVP{subDTypep, FINAL, STREAM_USE_ASSIGN}.p()); - } else if (m_cellp->rangep() && m_cellp->arrayIdx() < 0) { - // Interface instance array, expanded later in V3Inst - const int numInsts = m_cellp->rangep()->elementsConst(); - if (conwidth == modwidth) { - // Arrayed instants: widths match so connect to each instance - subDTypep = conDTypep; // = same expr dtype - } else if (conwidth == numInsts * modwidth) { - // Arrayed instants: one bit for each of the instants (each - // assign is 1 modwidth wide) - subDTypep = conDTypep; // = same expr dtype (but numInst*pin_dtype) - } else { - // Must be a error according to spec - // (Because we need to know if to connect to one or all instants) - nodep->v3error(ucfirst(nodep->prettyOperatorName()) - << " as part of a module instance array" << " requires " - << modwidth << " or " << modwidth * numInsts - << " bits, but connection's " - << nodep->exprp()->prettyTypeName() << " generates " << conwidth - << " bits. (IEEE 1800-2023 23.3.3)"); - subDTypep = conDTypep; // = same expr dtype - } - userIterateAndNext(nodep->exprp(), - WidthVP{subDTypep, FINAL, STREAM_USE_ASSIGN}.p()); } else { if (m_cellp->arrayIdx() >= 0) { // Element of an instance array, select its part of the connection, // then check it like any other pin, unless that is all done already - if (pinInstArrayElement(nodep, m_cellp->rangep(), m_cellp->arrayIdx())) return; + if (pinInstArrayElement(nodep, m_cellp->rangep(), m_cellp->arrayIdx())) { + if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep); + return; + } conDTypep = nodep->exprp()->dtypep()->skipRefp(); conwidth = conDTypep->width(); } @@ -7352,16 +7413,14 @@ class WidthVisitor final : public VNVisitor { // TODO Simple dtype checking, should be a more general check const AstNodeArrayDType* const exprArrayp = VN_CAST(conDTypep, UnpackArrayDType); const AstNodeArrayDType* const modArrayp = VN_CAST(modDTypep, UnpackArrayDType); - if (exprArrayp && modArrayp && VN_IS(exprArrayp->subDTypep(), IfaceRefDType) - && exprArrayp->declRange().elements() != modArrayp->declRange().elements()) { - const int exprSize = exprArrayp->declRange().elements(); - const int modSize = modArrayp->declRange().elements(); + if (exprArrayp && modArrayp && VN_IS(conDTypep->elemDTypep(), IfaceRefDType) + && unpackedArraySize(conDTypep) != unpackedArraySize(modDTypep)) { nodep->v3error("Illegal " << nodep->prettyOperatorName() << "," << " mismatch between port which is an interface array of size " - << modSize << "," + << unpackedArraySize(modDTypep) << "," << " and expression which is an interface array of size " - << exprSize << "."); + << unpackedArraySize(conDTypep) << "."); UINFO(1, " Related lo: " << modDTypep); UINFO(1, " Related hi: " << conDTypep); } else if ((exprArrayp && !modArrayp) || (!exprArrayp && modArrayp)) { @@ -7380,6 +7439,8 @@ class WidthVisitor final : public VNVisitor { } iterateCheckAssign(nodep, "pin connection", nodep->exprp(), FINAL, subDTypep); } + // Interface array port: connect its elements + if (nodep->modVarp()->isIfaceArraySplit()) expandIfaceArrayPin(nodep); } // UINFOTREE(1, nodep, "", "PinOut"); } @@ -10632,6 +10693,168 @@ class WidthVisitor final : public VNVisitor { return nullptr; } + //---------------------------------------------------------------------- + // METHODS - interface arrays + + // True if an unpacked array of non-virtual interfaces + static bool isIfaceArrayDType(const AstNodeDType* dtypep) { + if (!VN_IS(dtypep->skipRefp(), UnpackArrayDType)) return false; + const AstIfaceRefDType* const irefp = VN_CAST(dtypep->elemDTypep(), IfaceRefDType); + return irefp && !irefp->isVirtual(); + } + + // Number of elements of each unpacked dimension of 'dtypep', from the left, e.g. "2x3" + static std::string unpackedArraySize(const AstNodeDType* dtypep) { + std::string size; + dtypep = dtypep->skipRefp(); + while (const AstUnpackArrayDType* const arrp = VN_CAST(dtypep, UnpackArrayDType)) { + if (!size.empty()) size += "x"; + size += std::to_string(arrp->elementsConst()); + dtypep = arrp->subDTypep()->skipRefp(); + } + return size; + } + + // A whole array of interfaces is an AstInitArray of references to its elements, so select + // its element 'bit' (counted from lo) directly. Returns nullptr if not such an array, or + // 'bit' is out of range. + static AstNodeExpr* newIfaceArrayElement(AstNodeExpr* fromp, int bit) { + AstInitArray* const initp = VN_CAST(fromp, InitArray); + if (!initp || !isIfaceArrayDType(initp->dtypep())) return nullptr; + const AstUnpackArrayDType* const arrp + = VN_AS(initp->dtypep()->skipRefp(), UnpackArrayDType); + if (bit < 0 || bit >= arrp->elementsConst()) return nullptr; + return initp->getIndexDefaultedValuep(bit)->cloneTree(false); + } + + // References to the element variables of the whole interface array referenced by 'refp', in + // the dimensions of 'dtypep' inwards. 'name' is the element name so far, the element + // variables being named 'name' + 'suffix'. 'elemVarpr' is the element variable of the + // leftmost element in the dimensions of 'dtypep', and is moved to the right past them. + // E.g. 'ifc' of dimensions [0:1][3:2] -> '{'{ifc[0][3], ifc[0][2]}, '{ifc[1][3], ifc[1][2]}} + static AstNodeExpr* newIfaceArrayInit(const AstNodeVarRef* refp, AstNodeDType* dtypep, + const std::string& name, const std::string& suffix, + AstVar*& elemVarpr) { + FileLine* const flp = refp->fileline(); + AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType); + // Base case: reference the element variable when no dimensions left + if (!arrp) { + AstVar* const varp = elemVarpr; + UASSERT_OBJ(varp && varp->name() == name + suffix, refp, + "Missing element variable of interface array " << name); + elemVarpr = VN_CAST(varp->nextp(), Var); + if (const AstVarXRef* const xrefp = VN_CAST(refp, VarXRef)) { + return new AstVarXRef{flp, varp, xrefp->dotted(), refp->access()}; + } + return new AstVarRef{flp, varp, refp->access()}; + } + + // Enumerate the current dimension given by 'arrp', from the left index to the right, + // as the element variables are in that order, indexed from lo + AstInitArray* const initp = new AstInitArray{flp, arrp, nullptr}; + const VNumRange range = arrp->declRange(); + for (int n = 0, i = range.left(); n < range.elements(); ++n, i += range.leftToRightInc()) { + const std::string s = name + "__BRA__" + AstNode::encodeNumber(i) + "__KET__"; + initp->addIndexValuep( + i - range.lo(), newIfaceArrayInit(refp, arrp->subDTypep(), s, suffix, elemVarpr)); + } + return initp; + } + + // Replace a reference to a whole interface array variable (a __Viftop companion, or an + // interface array port) split by V3Param, with the AstInitArray of references to its + // elements, which is how whole arrays of interfaces are handled from here on + void replaceSplitIfaceArrayRef(AstNodeVarRef* nodep) { + AstVar* const varp = nodep->varp(); + UASSERT_OBJ(varp->isIfaceArraySplit(), nodep, "Not a split interface array"); + std::string name = varp->name(); + std::string suffix; + if (varp->isIfaceParent()) { + name = name.substr(0, name.rfind("__Viftop")); + suffix = "__Viftop"; + } + // The element variables follow the whole array variable in order (see V3Param) + AstVar* elemVarp = VN_CAST(varp->nextp(), Var); + AstNodeExpr* const newp = newIfaceArrayInit(nodep, varp->dtypep(), name, suffix, elemVarp); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + userIterate(newp, WidthVP{SELF, BOTH}.p()); + } + + // Add a pin for each element port of interface array port of pin 'nodep', split by V3Param, + // in the dimensions of 'dtypep' inwards, connected to the element at the same position, + // counted from the left, of 'conp', which is the corresponding (sub)array of the pin's + // connection, if any. 'suffix' is the name suffix of the dimensions outside 'dtypep'. + // 'elemVarpr' is the element port variable of the rightmost element in the dimensions of + // 'dtypep', and is moved to the left past them. + void expandIfaceArrayPinDimensions(AstPin* nodep, const AstNodeDType* dtypep, + AstNodeExpr* conp, const std::string& suffix, + AstVar*& elemVarpr) { + const AstUnpackArrayDType* const arrp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType); + // Base case: add the pin of the element port after 'nodep' when no dimensions left + if (!arrp) { + UASSERT_OBJ(elemVarpr && elemVarpr->name() == nodep->modVarp()->name() + suffix, nodep, + "Missing element variable of interface array port " << suffix); + AstPin* const newp = nodep->cloneTree(false); + newp->name(nodep->name() + suffix); + newp->modVarp(elemVarpr); + elemVarpr = VN_CAST(elemVarpr->backp(), Var); + if (conp) newp->exprp(conp->cloneTree(false)); + nodep->addNextHere(newp); + return; + } + + // Enumerate the current dimension given by 'arrp' + // Each element is added right after 'nodep', so go from right to left, + // to end with an enumeration from the left index to the right index. + const VNumRange range = arrp->declRange(); + for (int n = range.elements() - 1; n >= 0; --n) { + const int i = range.left() + n * range.leftToRightInc(); + const std::string s = suffix + "__BRA__" + AstNode::encodeNumber(i) + "__KET__"; + // Element of the connection, if any, which is only cloned at the leaves + AstNodeExpr* const elemConnp = [&]() -> AstNodeExpr* { + if (!conp) return nullptr; + const AstUnpackArrayDType* const conArrp + = VN_AS(conp->dtypep()->skipRefp(), UnpackArrayDType); + const int elements = conArrp->elementsConst(); + // Counted from lo + AstNodeExpr* const resultp + = VN_AS(conp, InitArray) + ->getIndexDefaultedValuep( + conArrp->declRange().ascending() ? n : elements - 1 - n); + UASSERT_OBJ(resultp, conp, "Interface array connection of mismatched shape"); + return resultp; + }(); + expandIfaceArrayPinDimensions(nodep, arrp->subDTypep(), elemConnp, s, elemVarpr); + } + } + + // Replace the given interface array port pin with a pin for each element port split by V3Param + void expandIfaceArrayPin(AstPin* nodep) { + AstVar* const portp = nodep->modVarp(); + UASSERT_OBJ(portp->isIfaceArraySplit(), nodep, "Not a split interface array port"); + // The element variables follow the port in order (see V3Param). Pins are added from the + // right, so start at the last. + AstVar* elemVarp = portp; + for (uint32_t n = portp->dtypep()->arrayUnpackedElements(); n; --n) { + elemVarp = VN_CAST(elemVarp->nextp(), Var); + UASSERT_OBJ(elemVarp, portp, "Missing element variables of interface array port"); + } + // Remove the connection, so the element pins do not clone it all. Deletion is deferred, + // so its elements can still be cloned from. + AstNodeExpr* const exprp = VN_AS(nodep->exprp(), NodeExpr); + if (exprp) pushDeletep(exprp->unlinkFrBack()); + // Connect nothing if not a whole array of interfaces of the shape of the port, which is + // reported elsewhere, so the element pins do not report it again + const bool connect + = exprp && isIfaceArrayDType(exprp->dtypep()) + && unpackedArraySize(exprp->dtypep()) == unpackedArraySize(portp->dtypep()); + expandIfaceArrayPinDimensions(nodep, portp->dtypep(), connect ? exprp : nullptr, "", + elemVarp); + UASSERT_OBJ(elemVarp == portp, portp, "Mismatched element port variable"); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + //---------------------------------------------------------------------- // METHODS - special iterators // These functions save/restore the AstNUser information so it can pass to child nodes. @@ -10682,7 +10905,13 @@ public: , m_paramsOnly{paramsOnly} , m_doGenerate{doGenerate} {} AstNode* mainAcceptEdit(AstNode* nodep) { - return userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p()); + nodep = userIterateSubtreeReturnEdits(nodep, WidthVP{SELF, BOTH}.p()); + // All references to the whole interface array variables split by V3Param are now replaced + for (AstVar* const varp : m_splitIfaceArrayVarps) { + VL_DO_DANGLING(pushDeletep(varp->unlinkFrBack()), varp); + } + m_splitIfaceArrayVarps.clear(); + return nodep; } static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 1af950e98..8cb60b194 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -284,8 +284,8 @@ static void process() { // should be after constifyAllLint() which flattens to 1D bit vector V3SplitVar::splitVariable(v3Global.rootp()); - // Remove interface arrays (must be between V3Width and scoping) - V3Inst::dearrayAll(v3Global.rootp()); + // Nothing to relink, but LinkDot names blocks created since V3Width, + // e.g. by V3AssertNfa. TODO: get rid of this V3LinkDot::linkDotArrayed(v3Global.rootp()); if (v3Global.opt.timing().isSetTrue()) { diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index d0bde2bbb..a0d89a90b 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -40,7 +40,10 @@ module Vt_debug_emitv_t; disable label0; end endfunction - Iface the_ifaces[3:0] (); + Iface the_ifaces[3] (); + Iface the_ifaces[2] (); + Iface the_ifaces[1] (); + Iface the_ifaces[0] (); initial begin begin if ((| $test$plusargs("HELLO"))) begin diff --git a/test_regress/t/t_dist_warn_coverage.py b/test_regress/t/t_dist_warn_coverage.py index 00e83e366..3b29393d4 100755 --- a/test_regress/t/t_dist_warn_coverage.py +++ b/test_regress/t/t_dist_warn_coverage.py @@ -74,12 +74,9 @@ for s in [ 'Pattern replication value of 0 is not legal.', 'Signals inside functions/tasks cannot be marked forceable', 'Slice size cannot be zero.', - 'Slices of arrays in assignments have different unpacked dimensions,', 'Symbol matching', 'Thread scheduler is unable to provide requested', - 'Unexpected connection to arrayed port', 'Unsized numbers/parameters not allowed in streams.', - 'Unsupported LHS node type in array assignment', 'Unsupported RHS tristate construct:', 'Unsupported or syntax error: Unsized range in instance or other declaration', 'Unsupported pullup/down (weak driver) construct.', @@ -96,7 +93,6 @@ for s in [ 'Unsupported: Default value on module inout/ref/constref:', 'Unsupported: Modport empty expression', 'Unsupported: Non-constant default value in missing argument', - 'Unsupported: Non-constant index when passing interface to module', 'Unsupported: Only one PSL clock allowed per assertion', 'Unsupported: Public functions with >64 bit outputs;', 'Unsupported: Public functions with return > 64 bits wide.', diff --git a/test_regress/t/t_interface_ar3.out b/test_regress/t/t_interface_ar3.out deleted file mode 100644 index 7d55bb39e..000000000 --- a/test_regress/t/t_interface_ar3.out +++ /dev/null @@ -1,10 +0,0 @@ -%Error-UNSUPPORTED: t/t_interface_ar3.v:18:18: Unsupported: interface slices - : ... note: In instance 't' - 18 | .infc(iinst[0:1]) - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Internal Error: t/t_interface_ar3.v:18:18: ../V3Inst.cpp:#: No interface varref under array - : ... note: In instance 't' - 18 | .infc(iinst[0:1]) - | ^ - ... This fatal error may be caused by the earlier error(s); resolve those first. diff --git a/test_regress/t/t_interface_ar3.py b/test_regress/t/t_interface_ar3.py index b9952dbf2..3cc73805c 100755 --- a/test_regress/t/t_interface_ar3.py +++ b/test_regress/t/t_interface_ar3.py @@ -11,11 +11,8 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile( - fails=test.vlt_all, # Verilator unsupported, bug546 - expect_filename=test.golden_filename) +test.compile() -if not test.vlt_all: - test.execute() +test.execute() test.passes() diff --git a/test_regress/t/t_interface_array_bad.out b/test_regress/t/t_interface_array_bad.out index 70d632635..963ab010b 100644 --- a/test_regress/t/t_interface_array_bad.out +++ b/test_regress/t/t_interface_array_bad.out @@ -7,4 +7,8 @@ : ... note: In instance 't' 23 | assign foos[bar].a = 1'b1; | ^ +%Error: t/t_interface_array_bad.v:23:20: Can't find definition of 'foos[??]' + : ... note: In instance 't' + 23 | assign foos[bar].a = 1'b1; + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_interface_array_class_new.v b/test_regress/t/t_interface_array_class_new.v index c52ccc502..3499464a3 100644 --- a/test_regress/t/t_interface_array_class_new.v +++ b/test_regress/t/t_interface_array_class_new.v @@ -1,8 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // Passing a real interface array as a class new() argument bound to a -// virtual interface array formal parameter. Exercises the cell dearrayer's -// orphan-VarRef fixup in V3Inst. +// virtual interface array formal parameter. // // This file ONLY is placed under the Creative Commons Public Domain. // SPDX-FileCopyrightText: 2026 Wilson Snyder diff --git a/test_regress/t/t_interface_array_loop_bad.out b/test_regress/t/t_interface_array_loop_bad.out index 91bdf6740..0f72f5062 100644 --- a/test_regress/t/t_interface_array_loop_bad.out +++ b/test_regress/t/t_interface_array_loop_bad.out @@ -7,4 +7,8 @@ : ... note: In instance 't' 24 | ifaces[i].value = 8'(i); | ^ +%Error: t/t_interface_array_loop_bad.v:24:17: Can't find definition of 'ifaces[??]' + : ... note: In instance 't' + 24 | ifaces[i].value = 8'(i); + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_interface_array_nocolon_bad.out b/test_regress/t/t_interface_array_nocolon_bad.out index a44b41cd9..6157cc4be 100644 --- a/test_regress/t/t_interface_array_nocolon_bad.out +++ b/test_regress/t/t_interface_array_nocolon_bad.out @@ -1,19 +1,19 @@ -%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:28:24: Ascending instance range connecting to vector: left < right of instance range: [0:2] - : ... note: In instance 't' - 28 | foo_subm subs[N] (.x(X)); - | ^ - ... For warning description see https://verilator.org/warn/ASCRANGE?v=latest - ... Use "/* verilator lint_off ASCRANGE */" and lint_on around source to disable this message. -%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:29:26: Ascending instance range connecting to vector: left < right of instance range: [1:3] - : ... note: In instance 't' - 29 | foo_subm subl[1:3] (.x(X)); - | ^ %Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:24:24: Ascending instance range connecting to vector: left < right of instance range: [0:2] : ... note: In instance 't' 24 | foo_intf foos[N] (.x(X)); | ^ + ... For warning description see https://verilator.org/warn/ASCRANGE?v=latest + ... Use "/* verilator lint_off ASCRANGE */" and lint_on around source to disable this message. %Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:25:26: Ascending instance range connecting to vector: left < right of instance range: [1:3] : ... note: In instance 't' 25 | foo_intf fool[1:3] (.x(X)); | ^ +%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:28:24: Ascending instance range connecting to vector: left < right of instance range: [0:2] + : ... note: In instance 't' + 28 | foo_subm subs[N] (.x(X)); + | ^ +%Warning-ASCRANGE: t/t_interface_array_nocolon_bad.v:29:26: Ascending instance range connecting to vector: left < right of instance range: [1:3] + : ... note: In instance 't' + 29 | foo_subm subl[1:3] (.x(X)); + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_interface_array_size_bad.out b/test_regress/t/t_interface_array_size_bad.out new file mode 100644 index 000000000..d9bdedbac --- /dev/null +++ b/test_regress/t/t_interface_array_size_bad.out @@ -0,0 +1,5 @@ +%Error: t/t_interface_array_size_bad.v:23:13: Slices of arrays in assignments have different unpacked dimensions, 6 versus 7 + 23 | c.vif = b; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_interface_array_size_bad.py b/test_regress/t/t_interface_array_size_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_interface_array_size_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_interface_array_size_bad.v b/test_regress/t/t_interface_array_size_bad.v new file mode 100644 index 000000000..431de47a8 --- /dev/null +++ b/test_regress/t/t_interface_array_size_bad.v @@ -0,0 +1,25 @@ +// 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 + +interface A; +endinterface + +typedef virtual A a_t; +typedef a_t a_array_t[6]; + +class C; + a_array_t vif; +endclass + +module t; + A b[7] (); + C c; + + initial begin + c = new(); + c.vif = b; + end +endmodule diff --git a/test_regress/t/t_interface_array_virtual.py b/test_regress/t/t_interface_array_virtual.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_array_virtual.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_interface_array_virtual.v b/test_regress/t/t_interface_array_virtual.v new file mode 100644 index 000000000..19fd8ae74 --- /dev/null +++ b/test_regress/t/t_interface_array_virtual.v @@ -0,0 +1,93 @@ +// 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 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +interface a_if; + int x; +endinterface + +typedef virtual a_if va_t; + +module sub; + a_if ifs[2] (); + for (genvar i = 0; i < 2; ++i) begin : g + initial ifs[i].x = 'h40 + i; + end +endmodule + +class C; + va_t vif[2]; +endclass + +module t; + + a_if asc[0:3] (); + a_if desc[3:0] (); + a_if a2d[1:0][0:2] (); + + for (genvar i = 0; i < 4; ++i) begin : g_1d + initial begin + asc[i].x = 'h10 + i; + desc[i].x = 'h20 + i; + end + end + for (genvar i = 0; i < 2; ++i) begin : g_2d_i + for (genvar j = 0; j < 3; ++j) begin : g_2d_j + initial a2d[i][j].x = 'h30 + 3 * i + j; + end + end + + va_t w[4]; + va_t s[2]; + va_t w2d[2][3]; + C c; + + sub i_sub (); + + initial begin + #1; + // Whole array, left element to left element + w = asc; + for (int i = 0; i < 4; ++i) `checkh(w[i].x, 'h10 + i); + w = desc; + for (int i = 0; i < 4; ++i) `checkh(w[i].x, 'h20 + 3 - i); + // Slices + s = asc[1:2]; + `checkh(s[0].x, 'h11); + `checkh(s[1].x, 'h12); + s = desc[2:1]; + `checkh(s[0].x, 'h22); + `checkh(s[1].x, 'h21); + // Multi dimensional, a2d[1] is the left row + w2d = a2d; + for (int i = 0; i < 2; ++i) begin + for (int j = 0; j < 3; ++j) `checkh(w2d[i][j].x, 'h30 + 3 * (1 - i) + j); + end + // Hierarchical reference to a whole array + s = i_sub.ifs; + `checkh(s[0].x, 'h40); + `checkh(s[1].x, 'h41); + `checkh(i_sub.ifs[0].x, 'h40); + `checkh(i_sub.ifs[1].x, 'h41); + // Class member + c = new; + c.vif = asc[2:3]; + `checkh(c.vif[0].x, 'h12); + `checkh(c.vif[1].x, 'h13); + // Writes through the virtual interfaces reach the instances + c.vif[1].x = 'h99; + w2d[0][2].x = 'h98; + #1; + `checkh(asc[3].x, 'h99); + `checkh(a2d[1][2].x, 'h98); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_interface_dearray_bad.out b/test_regress/t/t_interface_dearray_bad.out index 7624661c7..bb9933116 100644 --- a/test_regress/t/t_interface_dearray_bad.out +++ b/test_regress/t/t_interface_dearray_bad.out @@ -1,27 +1,30 @@ -%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:23:7: Unexpected target of interface assignment ['IFACEREFDTYPE$[0:5]'] - : ... note: In instance 'tb_top' - 23 | a = f; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:26:11: Array size mismatch in interface assignment - : ... note: In instance 'tb_top' - 26 | c.vif = b; - | ^ -%Error: t/t_interface_dearray_bad.v:31:20: Expecting expression to be constant, but variable isn't const: 'i' +%Error: t/t_interface_dearray_bad.v:27:21: Selection index out of range of interface array: 6 outside 5:0 (IEEE 1800-2023 23.6) : ... note: In instance 'tb_top' - 31 | d.vif[i] = a[i]; - | ^ + 27 | sub s_oob_hi (.p(a[6])); + | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:31:20: Non-constant index in RHS interface array selection - : ... note: In instance 'tb_top' - 31 | d.vif[i] = a[i]; +%Error: t/t_interface_dearray_bad.v:28:21: Selection index out of range of interface array: -1 outside 5:0 (IEEE 1800-2023 23.6) + : ... note: In instance 'tb_top' + 28 | sub s_oob_lo (.p(a[-1])); + | ^ +%Error: t/t_interface_dearray_bad.v:31:7: Illegal assignment to an interface instance (IEEE 1800-2023 10.4) + : ... note: In instance 'tb_top' + 31 | a = f; + | ^ +%Error: t/t_interface_dearray_bad.v:32:12: Illegal assignment to an interface instance (IEEE 1800-2023 10.4) + : ... note: In instance 'tb_top' + 32 | a[0:1] = f[0:1]; + | ^ +%Error: t/t_interface_dearray_bad.v:33:10: Illegal assignment to an interface instance (IEEE 1800-2023 10.4) + : ... note: In instance 'tb_top' + 33 | a[2] = f[2]; + | ^ +%Error: t/t_interface_dearray_bad.v:38:20: Expecting expression to be constant, but variable isn't const: 'i' + : ... note: In instance 'tb_top' + 38 | d.vif[i] = a[i]; | ^ -%Error-UNSUPPORTED: t/t_interface_dearray_bad.v:35:14: Interface slices unsupported - : ... note: In instance 'tb_top' - 35 | e.vif = b[0:5]; +%Error: t/t_interface_dearray_bad.v:40:14: Slice selection index '[3:8]' outside data type's '[0:5]' + : ... note: In instance 'tb_top' + 40 | d.vif = a[3:8]; | ^ -%Error: Internal Error: t/t_interface_dearray_bad.v:23:9: ../V3Broken.cpp:#: Broken link in node (or something without maybePointedTo): 'm_varp && !m_varp->brokeExists()' @ ./V3Ast__gen_impl.h:# - : ... note: In instance 'tb_top' - 23 | a = f; - | ^ - ... This fatal error may be caused by the earlier error(s); resolve those first. +%Error: Exiting due to diff --git a/test_regress/t/t_interface_dearray_bad.v b/test_regress/t/t_interface_dearray_bad.v index 1cf7692f8..4deee1bf9 100644 --- a/test_regress/t/t_interface_dearray_bad.v +++ b/test_regress/t/t_interface_dearray_bad.v @@ -7,6 +7,11 @@ interface A; endinterface +module sub ( + A p +); +endmodule + typedef virtual A a_t; typedef a_t a_array_t[6]; @@ -15,24 +20,24 @@ class C; endclass module tb_top (); - A a[6] (), b[7] (), f[6] (); - C c, d, e; - a_array_t g; + A a[6] (), f[6] (); + C d; + + // Bad: no such instances + sub s_oob_hi (.p(a[6])); + sub s_oob_lo (.p(a[-1])); initial begin a = f; - - c = new(); - c.vif = b; + a[0:1] = f[0:1]; + a[2] = f[2]; d = new(); for (int i = 0; i < 6; ++i) begin d.vif[i] = a[i]; end - - e = new(); - e.vif = b[0:5]; + d.vif = a[3:8]; $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_interface_size_bad.out b/test_regress/t/t_interface_size_bad.out index e4ce1eb32..1b4286c4f 100644 --- a/test_regress/t/t_interface_size_bad.out +++ b/test_regress/t/t_interface_size_bad.out @@ -7,4 +7,24 @@ : ... note: In instance 't' 17 | baz baz6_inst (.foo(foo6)); | ^~~ +%Error: t/t_interface_size_bad.v:23:22: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 2x4. + : ... note: In instance 't' + 23 | baz2 baz2x4_inst (.foo(foo2x4)); + | ^~~ +%Error: t/t_interface_size_bad.v:24:22: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 3x3. + : ... note: In instance 't' + 24 | baz2 baz3x3_inst (.foo(foo3x3)); + | ^~~ +%Error: t/t_interface_size_bad.v:25:21: Illegal port connection 'foo', mismatch between port which is an interface array of size 2x3, and expression which is an interface array of size 6. + : ... note: In instance 't' + 25 | baz2 baz6r_inst (.foo(foo6)); + | ^~~ +%Error: t/t_interface_size_bad.v:26:21: Illegal port connection 'foo', mismatch between port which is an interface array of size 5, and expression which is an interface array of size 2x5. + : ... note: In instance 't' + 26 | baz baz2x5_inst (.foo(foo2x5)); + | ^~~ +%Error: t/t_interface_size_bad.v:29:27: Port connection 'foo' as part of a module instance array requires the connection's leading unpacked dimensions to match the instance array dimensions (IEEE 1800-2023 23.3.3.5) + : ... note: In instance 't' + 29 | baz baz_arr_inst[1:0] (.foo(foo3x3)); + | ^~~ %Error: Exiting due to diff --git a/test_regress/t/t_interface_size_bad.v b/test_regress/t/t_interface_size_bad.v index 1ea08d36e..b90f1bd00 100644 --- a/test_regress/t/t_interface_size_bad.v +++ b/test_regress/t/t_interface_size_bad.v @@ -16,9 +16,26 @@ module t; baz baz4_inst (.foo(foo4)); baz baz6_inst (.foo(foo6)); + // Multi dimensional, each dimension must match + foo_intf foo2x4[1:0][3:0] (); + foo_intf foo3x3[2:0][2:0] (); + foo_intf foo2x5[1:0][4:0] (); + baz2 baz2x4_inst (.foo(foo2x4)); + baz2 baz3x3_inst (.foo(foo3x3)); + baz2 baz6r_inst (.foo(foo6)); + baz baz2x5_inst (.foo(foo2x5)); + + // Instance array, leading dimension must match the instance array + baz baz_arr_inst[1:0] (.foo(foo3x3)); + endmodule module baz ( foo_intf foo[4:0] ); endmodule + +module baz2 ( + foo_intf foo[1:0][2:0] +); +endmodule diff --git a/test_regress/t/t_interface_virtual_bad.out b/test_regress/t/t_interface_virtual_bad.out index ddfc3670e..d14f7440a 100644 --- a/test_regress/t/t_interface_virtual_bad.out +++ b/test_regress/t/t_interface_virtual_bad.out @@ -1,8 +1,12 @@ +%Error: t/t_interface_virtual_bad.v:30:8: Illegal assignment to an interface instance (IEEE 1800-2023 10.4) + : ... note: In instance 't' + 30 | p8 = v8; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: t/t_interface_virtual_bad.v:31:10: Operator ASSIGN expected 'PBus' interface on Assign RHS but 'q8' is a different interface ('QBus'). : ... note: In instance 't' 31 | v8 = q8; | ^~ - ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: t/t_interface_virtual_bad.v:35:10: Operator ASSIGN expected no interface modport on Assign RHS but got 'phy' modport. : ... note: In instance 't' 35 | v8 = v8_phy; diff --git a/test_regress/t/t_interface_virtual_port_bad.out b/test_regress/t/t_interface_virtual_port_bad.out new file mode 100644 index 000000000..496c06c05 --- /dev/null +++ b/test_regress/t/t_interface_virtual_port_bad.out @@ -0,0 +1,14 @@ +%Error: t/t_interface_virtual_port_bad.v:26:15: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4) + : ... note: In instance 't' + 26 | sub i_sub (.p(vs)); + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_interface_virtual_port_bad.v:27:23: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4) + : ... note: In instance 't' + 27 | sub_arr i_sub_arr (.p(va)); + | ^ +%Error: t/t_interface_virtual_port_bad.v:28:19: Illegal port connection 'p', interface port connected to a virtual interface (IEEE 1800-2023 23.3.3.4) + : ... note: In instance 't' + 28 | sub i_subs[2] (.p(va)); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_interface_virtual_port_bad.py b/test_regress/t/t_interface_virtual_port_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_interface_virtual_port_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_interface_virtual_port_bad.v b/test_regress/t/t_interface_virtual_port_bad.v new file mode 100644 index 000000000..501e0a004 --- /dev/null +++ b/test_regress/t/t_interface_virtual_port_bad.v @@ -0,0 +1,29 @@ +// 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 + +interface a_if; + int x; +endinterface + +module sub ( + a_if p +); +endmodule + +module sub_arr ( + a_if p[2] +); +endmodule + +module t; + virtual a_if vs; + virtual a_if va[2]; + + // Bad: interface ports need interface instances, not virtual interfaces + sub i_sub (.p(vs)); + sub_arr i_sub_arr (.p(va)); + sub i_subs[2] (.p(va)); +endmodule diff --git a/test_regress/t/t_mod_interface_array3.v b/test_regress/t/t_mod_interface_array3.v index 68ceef9eb..3ff78fdda 100644 --- a/test_regress/t/t_mod_interface_array3.v +++ b/test_regress/t/t_mod_interface_array3.v @@ -14,13 +14,34 @@ interface a_if (); string s; endinterface -// TODO: V3Inst only handles a one-dimensional interface array element as a pin connection module sub ( output string s - // , a_if i ); initial s = $sformatf("%m"); - // initial i.s = $sformatf("%m-iface"); +endmodule + +module isub ( + output string s, + a_if i +); + initial s = $sformatf("%m"); + initial i.s = $sformatf("%m-iface"); +endmodule + +// Writes each element of its interface array port +module awrite ( + a_if p[3] +); + for (genvar k = 0; k < 3; ++k) begin : g + initial p[k].s = $sformatf("%m"); + end +endmodule + +// Passes a slice of its interface array port down +module amid ( + a_if p[4] +); + awrite i_w (.p(p[1:3])); endmodule module pass ( @@ -51,7 +72,7 @@ module t; a_if iface[3:1][1:0] (); - sub i_sub[3:1][1:0] (.s(str) /*, .i(iface)*/); + isub i_sub[3:1][1:0] (.s(str), .i(iface)); // Hierarchical references to elements, with genvar indices for (genvar a = 1; a < 4; ++a) begin : g_a @@ -103,6 +124,26 @@ module t; logic [3:0] uo3[1:0]; usub i_u[1:0] (.a(ux), .o0(uo0), .o3(uo3)); + // Elements of a module instance array with an interface array port, connected to a + // two-dimensional interface array, in opposite directions: connect left to left + a_if i2d[2:1][2:0] (); + awrite i_aw[1:2] (.p(i2d)); + + // One row of a two-dimensional interface array connected to an interface array port + a_if irow[1:0][0:2] (); + awrite i_row (.p(irow[1])); + + // A slice of one row of a two-dimensional interface array connected to an interface array + // port, and an element of one connected to an interface port + a_if irs[1:0][0:3] (); + awrite i_rsl (.p(irs[1][1:3])); + string elstr; + isub i_el (.s(elstr), .i(irs[0][2])); + + // A slice of an interface array port passed down + a_if i4[4] (); + amid i_mid (.p(i4)); + // Ref port connected to an element of an unpacked array string rstr[1:0]; rsub i_ref[1:0] (.r(rstr)); @@ -142,6 +183,27 @@ module t; `checkh(uo0[0], 4'h9); `checkh(uo3[1], 4'h6); `checkh(uo3[0], 4'h6); + // i_aw[1] is the left element, i2d[2]; port element p[0] is the left element, [2] + `checks(i2d[2][2].s, "t.i_aw[1].g[0]"); + `checks(i2d[2][1].s, "t.i_aw[1].g[1]"); + `checks(i2d[2][0].s, "t.i_aw[1].g[2]"); + `checks(i2d[1][2].s, "t.i_aw[2].g[0]"); + `checks(i2d[1][1].s, "t.i_aw[2].g[1]"); + `checks(i2d[1][0].s, "t.i_aw[2].g[2]"); + `checks(irow[1][0].s, "t.i_row.g[0]"); + `checks(irow[1][1].s, "t.i_row.g[1]"); + `checks(irow[1][2].s, "t.i_row.g[2]"); + `checks(irow[0][0].s, ""); + `checks(irs[1][0].s, ""); + `checks(irs[1][1].s, "t.i_rsl.g[0]"); + `checks(irs[1][2].s, "t.i_rsl.g[1]"); + `checks(irs[1][3].s, "t.i_rsl.g[2]"); + `checks(irs[0][2].s, "t.i_el-iface"); + `checks(irs[0][1].s, ""); + `checks(i4[0].s, ""); + `checks(i4[1].s, "t.i_mid.i_w.g[0]"); + `checks(i4[2].s, "t.i_mid.i_w.g[1]"); + `checks(i4[3].s, "t.i_mid.i_w.g[2]"); `checks(rstr[1], "t.i_ref[1]"); `checks(rstr[0], "t.i_ref[0]"); `checks(str[1][0], "t.i_sub[1][0]"); @@ -150,13 +212,12 @@ module t; `checks(str[2][1], "t.i_sub[2][1]"); `checks(str[3][0], "t.i_sub[3][0]"); `checks(str[3][1], "t.i_sub[3][1]"); - // TODO: enable with the interface port of 'sub' - // `checks(iface[1][0].s, "t.i_sub[1][0]-iface"); - // `checks(iface[1][1].s, "t.i_sub[1][1]-iface"); - // `checks(iface[2][0].s, "t.i_sub[2][0]-iface"); - // `checks(iface[2][1].s, "t.i_sub[2][1]-iface"); - // `checks(iface[3][0].s, "t.i_sub[3][0]-iface"); - // `checks(iface[3][1].s, "t.i_sub[3][1]-iface"); + `checks(iface[1][0].s, "t.i_sub[1][0]-iface"); + `checks(iface[1][1].s, "t.i_sub[1][1]-iface"); + `checks(iface[2][0].s, "t.i_sub[2][0]-iface"); + `checks(iface[2][1].s, "t.i_sub[2][1]-iface"); + `checks(iface[3][0].s, "t.i_sub[3][0]-iface"); + `checks(iface[3][1].s, "t.i_sub[3][1]-iface"); $write("*-* All Finished *-*\n"); $finish; end