From 7664bbb3ef3e2249de83b0dd1b52f516283005bf Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Wed, 3 Jun 2026 04:28:50 +0200 Subject: [PATCH] Support generic interface arrays (#7604) --- src/V3AstNodeDType.h | 7 +++ src/V3AstNodes.cpp | 19 ++++++ src/V3Inst.cpp | 13 ++++ src/V3LinkDot.cpp | 26 ++------ src/V3LinkParse.cpp | 3 +- src/V3Param.cpp | 16 ++++- src/V3ParseGrammar.h | 3 +- src/V3Width.cpp | 6 +- src/verilog.y | 4 +- test_regress/t/t_interface_generic.py | 2 +- test_regress/t/t_interface_generic.v | 5 +- test_regress/t/t_interface_generic_array.py | 2 +- test_regress/t/t_interface_generic_array.v | 3 + test_regress/t/t_interface_generic_array2.py | 18 ++++++ test_regress/t/t_interface_generic_array2.v | 65 ++++++++++++++++++++ 15 files changed, 157 insertions(+), 35 deletions(-) create mode 100755 test_regress/t/t_interface_generic_array2.py create mode 100644 test_regress/t/t_interface_generic_array2.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 8e121aa5e..666570d1e 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -106,6 +106,13 @@ public: return const_cast( static_cast(this)->skipRefIterp(false, false)); } + // If array, returns element dtype, otherwise returns skipRef dtype + // If skipRef is false, RefDTypes are not followed (safe before typedef linking) + const AstNodeDType* elemDTypep(bool skipRef = true) const VL_MT_STABLE; + AstNodeDType* elemDTypep(bool skipRef = true) VL_MT_STABLE { + return const_cast( + static_cast(this)->elemDTypep(skipRef)); + } // (Slow) recurses - Structure alignment 1,2,4 or 8 bytes (arrays affect this) virtual int widthAlignBytes() const = 0; // (Slow) recurses - Width in bytes rounding up 1,2,4,8,12,... diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 0ef2f26ca..a4119ead5 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1050,6 +1050,25 @@ const AstNodeDType* AstNodeDType::skipRefIterp(bool skipConst, bool skipEnum, return nullptr; } +const AstNodeDType* AstNodeDType::elemDTypep(bool skipRef) const { + const AstNodeDType* dtypep = this; + while (true) { + if (skipRef) dtypep = dtypep->skipRefp(); + if (const AstBracketArrayDType* const adtypep = VN_CAST(dtypep, BracketArrayDType)) { + dtypep = adtypep->subDTypep(); + } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { + dtypep = adtypep->subDTypep(); + } else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) { + dtypep = adtypep->subDTypep(); + } else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + dtypep = adtypep->subDTypep(); + } else { + break; + } + } + return dtypep; +} + bool AstNodeDType::similarDType(const AstNodeDType* samep) const { const AstNodeDType* nodep = this; nodep = nodep->skipRefToNonRefp(); diff --git a/src/V3Inst.cpp b/src/V3Inst.cpp index 5a0f9279c..43cea12ef 100644 --- a/src/V3Inst.cpp +++ b/src/V3Inst.cpp @@ -704,6 +704,19 @@ private: 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); diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index edace0f79..a04b7bbdf 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3553,24 +3553,6 @@ class LinkDotResolveVisitor final : public VNVisitor { m_ds.init(m_curSymp); iterateNull(nodep); } - static const AstNodeDType* getElemDTypep(const AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); - while (true) { - if (const AstBracketArrayDType* const adtypep = VN_CAST(dtypep, BracketArrayDType)) { - dtypep = adtypep->subDTypep()->skipRefp(); - } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { - dtypep = adtypep->subDTypep()->skipRefp(); - } else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) { - dtypep = adtypep->subDTypep()->skipRefp(); - } else if (const AstUnpackArrayDType* const adtypep - = VN_CAST(dtypep, UnpackArrayDType)) { - dtypep = adtypep->subDTypep()->skipRefp(); - } else { - break; - } - } - return dtypep; - } static const AstNodeDType* getExprDTypep(const AstNodeExpr* selp) { while (const AstNodePreSel* const sp = VN_CAST(selp, NodePreSel)) selp = sp->fromp(); if (const AstMemberSel* const sp = VN_CAST(selp, MemberSel)) { @@ -3582,7 +3564,7 @@ class LinkDotResolveVisitor final : public VNVisitor { dtypep = nodep->childDTypep(); return nodep->name() == name; }); - if (found) return getElemDTypep(dtypep); + if (found) return dtypep->elemDTypep(); selp->v3error("Class " << classRefp->prettyNameQ() << " does not contain field " << selp->prettyNameQ()); } else { @@ -3592,7 +3574,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } } } else if (const AstNodeVarRef* const varRefp = VN_CAST(selp, NodeVarRef)) { - return getElemDTypep(varRefp->varp()->childDTypep()); + return varRefp->varp()->childDTypep()->elemDTypep(); } return nullptr; } @@ -3617,7 +3599,7 @@ class LinkDotResolveVisitor final : public VNVisitor { pinp = VN_CAST(pinp->nextp(), Pin), modIfaceVarp = getNextVarp(modIfaceVarp->nextp())) { if (modIfaceVarp->varType() != VVarType::IFACEREF - || !VN_IS(modIfaceVarp->childDTypep(), IfaceGenericDType)) { + || !VN_IS(modIfaceVarp->childDTypep()->elemDTypep(), IfaceGenericDType)) { continue; } AstNode* exprp = pinp->exprp(); @@ -3662,7 +3644,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } else if (varRefp) { const AstVar* const varp = varRefp->varp(); if (const AstIfaceRefDType* const refp - = VN_CAST(getElemDTypep(varp->childDTypep()), IfaceRefDType)) { + = VN_CAST(varp->childDTypep()->elemDTypep(), IfaceRefDType)) { AstIface* const ifacep = VN_AS(refp->cellp()->modp(), Iface); AstIfaceRefDType* newIfaceRefp; if (refp->modportp()) { diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 5123ee4da..c55a5e7c5 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -460,7 +460,8 @@ class LinkParseVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); return; } - m_moduleWithGenericIface |= VN_IS(nodep->childDTypep(), IfaceGenericDType); + const AstNodeDType* const dtypep = nodep->childDTypep(); + m_moduleWithGenericIface |= dtypep && VN_IS(dtypep->elemDTypep(false), IfaceGenericDType); // Maybe this variable has a signal attribute V3Control::applyVarAttr(m_modp, m_ftaskp, nodep); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 7d0770da3..fe2273d98 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -1710,7 +1710,9 @@ class ParamProcessor final { bool& any_overridesr, IfaceRefRefs& ifaceRefRefs) { for (AstPin* pinp = pinsp; pinp; pinp = VN_AS(pinp->nextp(), Pin)) { const AstVar* const modvarp = pinp->modVarp(); - if (modvarp && VN_IS(modvarp->subDTypep(), IfaceGenericDType)) continue; + if (modvarp && VN_IS(arraySubDTypeDeepp(modvarp->subDTypep()), IfaceGenericDType)) { + continue; + } if (modvarp->isIfaceRef()) { // arraySubDTypeDeepp returns input unchanged if not an array. AstIfaceRefDType* const portIrefp @@ -1798,10 +1800,12 @@ class ParamProcessor final { for (const AstNode* nodep = pinsp; nodep; nodep = nodep->nextp()) { if (const AstPin* const pinp = VN_CAST(nodep, Pin)) { if (AstVar* const varp = pinp->modVarp()) { + AstNodeDType* dtypep = varp->childDTypep()->elemDTypep(); if (AstIfaceGenericDType* const ifaceGDTypep - = VN_CAST(varp->childDTypep(), IfaceGenericDType)) { + = VN_CAST(dtypep, IfaceGenericDType)) { const auto iter = paramspMap.find(varp->name()); if (iter == paramspMap.end()) continue; + AstNode* const backp = ifaceGDTypep->backp(); ifaceGDTypep->unlinkFrBack(); const AstPin* const paramp = iter->second; paramspMap.erase(iter); @@ -1812,7 +1816,13 @@ class ParamProcessor final { ifaceGDTypep->name(), ifacerefp->ifaceName(), ifaceGDTypep->modportName()}; newIfacerefp->ifacep(ifacerefp->ifacep()); - varp->childDTypep(newIfacerefp); + if (auto* const arrDtp = VN_CAST(backp, NodeArrayDType)) { + arrDtp->childDTypep(newIfacerefp); + } else if (auto* const arrDtp = VN_CAST(backp, BracketArrayDType)) { + arrDtp->childDTypep(newIfacerefp); + } else { + varp->childDTypep(newIfacerefp); + } VL_DO_DANGLING(m_deleter.pushDeletep(ifaceGDTypep), ifaceGDTypep); if (paramspMap.empty()) return; } diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index d861d0988..42a3db9c4 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -338,7 +338,8 @@ public: m_varDeclTyped = true; const std::string uniqueName = "__VGIfaceParam" + nodep->name(); AstNode::addNext(nodep, - createVariable(nodep->fileline(), uniqueName, rangep, sigAttrListp)); + createVariable(nodep->fileline(), uniqueName, nullptr, + sigAttrListp ? sigAttrListp->cloneTree(true) : nullptr)); m_varDecl = VVarType::IFACEREF; AstIfaceGenericDType* const refdtypep = new AstIfaceGenericDType{nodep->fileline(), modportFileline, modportstrp}; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index c932255df..00ed32999 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1033,8 +1033,10 @@ class WidthVisitor final : public VNVisitor { const bool inParameterizedTemplate = m_modep && (m_modep->dead() || m_modep->parameterizedTemplate()); const bool inTypeTable = !m_modep; - if (nodep->ascending() && !VN_IS(nodep->backp(), UnpackArrayDType) - && !VN_IS(nodep->backp(), Cell) // For cells we warn in V3Inst + const AstNode* basep = nodep->backp(); + while (VN_IS(basep, Range)) basep = basep->backp(); + if (nodep->ascending() && !VN_IS(basep, UnpackArrayDType) + && !VN_IS(basep, Cell) // For cells we warn in V3Inst && !m_paramsOnly // Skip during parameter evaluation && !inDeadModule && !inParameterizedTemplate && !inTypeTable) { nodep->v3warn(ASCRANGE, "Ascending bit range vector: left < right of bit range: [" diff --git a/src/verilog.y b/src/verilog.y index aa6ca3b4b..f21b76bde 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -1385,9 +1385,9 @@ port: // ==IEEE: port AstNodeDType* const dtp = new AstIfaceRefDType{$2, $4, "", *$2, *$4}; VARDTYPE(dtp); VARIOANSI(); addNextNull($$, VARDONEP($$, $6, $7)); } - | portDirNetE yINTERFACE portSig rangeListE sigAttrListE + | portDirNetE yINTERFACE portSig variable_dimensionListE sigAttrListE { $$ = $3; GRAMMARP->createGenericIface($3, $4, $5); } - | portDirNetE yINTERFACE '.' idAny/*modport*/ portSig rangeListE sigAttrListE + | portDirNetE yINTERFACE '.' idAny/*modport*/ portSig variable_dimensionListE sigAttrListE { $$ = $5; GRAMMARP->createGenericIface($5, $6, $7, $4, *$4); } // | portDirNetE yINTERCONNECT signingE rangeListE portSig variable_dimensionListE sigAttrListE diff --git a/test_regress/t/t_interface_generic.py b/test_regress/t/t_interface_generic.py index a8d4caabb..4ee7f9e14 100755 --- a/test_regress/t/t_interface_generic.py +++ b/test_regress/t/t_interface_generic.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['--timing']) +test.compile(verilator_flags2=['--binary']) test.execute() diff --git a/test_regress/t/t_interface_generic.v b/test_regress/t/t_interface_generic.v index 2ea8f4256..77d3aa1c6 100644 --- a/test_regress/t/t_interface_generic.v +++ b/test_regress/t/t_interface_generic.v @@ -16,7 +16,7 @@ module GenericModule (interface a, interface b); initial begin #1; if (a.v != 7) $stop; - if (b.k != 9) $stop; + b.k = 9; end endmodule @@ -26,7 +26,8 @@ module t; GenericModule genericModule (inf_inst, inf_inst2); initial begin inf_inst.v = 7; - inf_inst2.k = 9; + #2; + if (inf_inst2.k != 9) $stop; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_interface_generic_array.py b/test_regress/t/t_interface_generic_array.py index a8d4caabb..4ee7f9e14 100755 --- a/test_regress/t/t_interface_generic_array.py +++ b/test_regress/t/t_interface_generic_array.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=['--timing']) +test.compile(verilator_flags2=['--binary']) test.execute() diff --git a/test_regress/t/t_interface_generic_array.v b/test_regress/t/t_interface_generic_array.v index d732f6a43..f6380d68d 100644 --- a/test_regress/t/t_interface_generic_array.v +++ b/test_regress/t/t_interface_generic_array.v @@ -12,6 +12,7 @@ module GenericModule (interface a); initial begin #1; if (a.v != 7) $stop; + a.v = 9; end endmodule @@ -20,6 +21,8 @@ module t; GenericModule genericModule (inf_inst[2]); initial begin inf_inst[2].v = 7; + #2; + if (inf_inst[2].v != 9) $stop; $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_interface_generic_array2.py b/test_regress/t/t_interface_generic_array2.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_interface_generic_array2.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_generic_array2.v b/test_regress/t/t_interface_generic_array2.v new file mode 100644 index 000000000..654923311 --- /dev/null +++ b/test_regress/t/t_interface_generic_array2.v @@ -0,0 +1,65 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +interface inf; + int v; +endinterface + +module GenericModule1D (interface a[4]); + initial begin + #1; + if (a[0].v != 'hdead) $stop; + if (a[1].v != 'hbeef) $stop; + a[2].v = 'hface; + a[3].v = 'hcafe; + end +endmodule + +module GenericModule2D (interface a[2][2]); + initial begin + #3; + if (a[0][0].v != 'hdead) $stop; + a[0][1].v = 'hbeef; + if (a[1][0].v != 'hface) $stop; + a[1][1].v = 'hcafe; + end +endmodule + +module GenericModuleRng (interface a[5:3]); + initial begin + #5; + if (a[3].v != 'hdead) $stop; + if (a[4].v != 'hbeef) $stop; + a[5].v = 'hface; + end +endmodule + +module t; + inf inf1d[4](); + inf inf2d[2][2](); + inf infrng[5:3](); + GenericModule1D mod1d(inf1d); + GenericModule2D mod2d(inf2d); + GenericModuleRng modrng(infrng); + initial begin + inf1d[0].v = 'hdead; + inf1d[1].v = 'hbeef; + #2; + if (inf1d[2].v != 'hface) $stop; + if (inf1d[3].v != 'hcafe) $stop; + inf2d[0][0].v = 'hdead; + inf2d[1][0].v = 'hface; + #2; + if (inf2d[0][1].v != 'hbeef) $stop; + if (inf2d[1][1].v != 'hcafe) $stop; + infrng[3].v = 'hdead; + infrng[4].v = 'hbeef; + #2; + if (infrng[5].v != 'hface) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule