From 6ba6447483cfff0bc3737a302e1adccff2b4c77e Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 23 Feb 2026 09:43:04 -0500 Subject: [PATCH 1/9] V3Tristate: handle AstVarXRef as AstVarRef (#3466) --- src/V3Tristate.cpp | 15 +++++++++------ test_regress/t/t_interface_modport.v | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 39aa49a8d..64691bba7 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -411,9 +411,9 @@ class TristateVisitor final : public TristateBaseVisitor { // TYPES struct RefStrength final { - AstVarRef* m_varrefp; + AstNodeVarRef* m_varrefp; VStrength m_strength; - RefStrength(AstVarRef* varrefp, VStrength strength) + RefStrength(AstNodeVarRef* varrefp, VStrength strength) : m_varrefp{varrefp} , m_strength{strength} {} }; @@ -471,7 +471,7 @@ class TristateVisitor final : public TristateBaseVisitor { } AstNodeExpr* getEnp(AstNode* nodep) { if (nodep->user1p()) { - if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + if (AstNodeVarRef* const refp = VN_CAST(nodep, NodeVarRef)) { if (refp->varp()->isIO()) { // When reading a tri-state port, we can always use the value // because such port will have resolution logic in upper module. @@ -561,7 +561,7 @@ class TristateVisitor final : public TristateBaseVisitor { return newp; } - void mapInsertLhsVarRef(AstVarRef* nodep) { + void mapInsertLhsVarRef(AstNodeVarRef* nodep) { UINFO(9, " mapInsertLhsVarRef " << nodep); AstVar* const key = nodep->varp(); const auto pair = m_lhsmap.emplace(key, nullptr); @@ -663,7 +663,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstNodeExpr* enp = nullptr; for (auto it = beginStrength; it != endStrength; it++) { - AstVarRef* refp = it->m_varrefp; + AstNodeVarRef* refp = it->m_varrefp; // create the new lhs driver for this var AstVar* const newLhsp = new AstVar{varp->fileline(), VVarType::MODULETEMP, @@ -1752,7 +1752,7 @@ class TristateVisitor final : public TristateBaseVisitor { } } - void visit(AstVarRef* nodep) override { + void handleNodeVarRef(AstNodeVarRef* nodep) { UINFO(9, dbgState() << nodep); if (m_graphing) { if (nodep->access().isWriteOrRW()) associateLogic(nodep, nodep->varp()); @@ -1794,6 +1794,9 @@ class TristateVisitor final : public TristateBaseVisitor { } } + void visit(AstVarRef* nodep) override { handleNodeVarRef(nodep); } + void visit(AstVarXRef* nodep) override { handleNodeVarRef(nodep); } + void visit(AstVar* nodep) override { iterateChildren(nodep); UINFO(9, dbgState() << nodep); diff --git a/test_regress/t/t_interface_modport.v b/test_regress/t/t_interface_modport.v index c4a6580b5..a49d6a172 100644 --- a/test_regress/t/t_interface_modport.v +++ b/test_regress/t/t_interface_modport.v @@ -24,6 +24,25 @@ module counter_ansi end endmodule : counter_ansi +// Issue 3466: inout inside interface modport +interface inout_if; + wire we; + wire d; + modport ctrl (output we, inout d); + modport prph (input we, inout d); +endinterface + +module inout_mod(inout_if.prph io_if); + assign io_if.d = io_if.we ? 1'b1 : 1'bz; +endmodule + +module inout_mod_wrap(input we, inout d); + inout_if io_if(); + assign io_if.we = we; + assign io_if.d = d; + inout_mod prph (.*); +endmodule + module t (/*AUTOARG*/ // Inputs clk @@ -52,11 +71,16 @@ module t (/*AUTOARG*/ .c_data(c4_data), .i_value(4'h4)); + logic inout_we; + tri inout_d; + inout_mod_wrap inout_u (.we(inout_we), .d(inout_d)); + initial begin c1_data.value = 4'h4; c2_data.value = 4'h5; c3_data.value = 4'h6; c4_data.value = 4'h7; + inout_we = 1'b0; end always @ (posedge clk) begin From d2f8ac85ad936ad1a62d10e7532f616412ba5a44 Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Sat, 28 Feb 2026 10:42:47 -0500 Subject: [PATCH 2/9] V3Tristate: handle interface tristate signals accessed via VarXRef (#3466) Extend the tristate pass to treat side-band enable and output signals as first-class cross-module variables. Previously, these were assumed local to the driving module and only accessed via VarRef. Now they are created in the interface module alongside the original signal and accessed via VarXRef using the same hierarchical path, allowing the rest of Verilator's infrastructure to resolve them normally. This enables support for interface tristate signals accessed through hierarchical references and modports, including === / !== comparisons, mixed local and external drivers, and deep hierarchy. Modports that expose a tristate signal are automatically extended to include the generated enable signal so that modport-qualified paths resolve correctly. --- src/V3AstInlines.h | 9 + src/V3LinkDotIfaceCapture.cpp | 5 +- src/V3Tristate.cpp | 340 ++++++++++++++++-- test_regress/t/t_interface_tristate_hier.py | 18 + test_regress/t/t_interface_tristate_hier.v | 258 +++++++++++++ test_regress/t/t_tri_hier_ref_unsup.out | 10 + test_regress/t/t_tri_hier_ref_unsup.py | 16 + test_regress/t/t_tri_hier_ref_unsup.v | 69 ++++ test_regress/t/t_tri_iface_eqcase.py | 18 + test_regress/t/t_tri_iface_eqcase.v | 158 ++++++++ .../t/t_tri_iface_eqcase_modport_bad.out | 6 + .../t/t_tri_iface_eqcase_modport_bad.py | 16 + .../t/t_tri_iface_eqcase_modport_bad.v | 31 ++ test_regress/t/t_tri_iface_mixed.py | 18 + test_regress/t/t_tri_iface_mixed.v | 136 +++++++ test_regress/t/t_tri_root_ref.py | 18 + test_regress/t/t_tri_root_ref.v | 51 +++ 17 files changed, 1142 insertions(+), 35 deletions(-) create mode 100755 test_regress/t/t_interface_tristate_hier.py create mode 100755 test_regress/t/t_interface_tristate_hier.v create mode 100644 test_regress/t/t_tri_hier_ref_unsup.out create mode 100644 test_regress/t/t_tri_hier_ref_unsup.py create mode 100644 test_regress/t/t_tri_hier_ref_unsup.v create mode 100644 test_regress/t/t_tri_iface_eqcase.py create mode 100644 test_regress/t/t_tri_iface_eqcase.v create mode 100644 test_regress/t/t_tri_iface_eqcase_modport_bad.out create mode 100644 test_regress/t/t_tri_iface_eqcase_modport_bad.py create mode 100644 test_regress/t/t_tri_iface_eqcase_modport_bad.v create mode 100644 test_regress/t/t_tri_iface_mixed.py create mode 100644 test_regress/t/t_tri_iface_mixed.v create mode 100644 test_regress/t/t_tri_root_ref.py create mode 100644 test_regress/t/t_tri_root_ref.v diff --git a/src/V3AstInlines.h b/src/V3AstInlines.h index bb7fffb45..2cc946748 100644 --- a/src/V3AstInlines.h +++ b/src/V3AstInlines.h @@ -211,4 +211,13 @@ AstVarXRef::AstVarXRef(FileLine* fl, AstVar* varp, const string& dotted, const V AstStmtExpr* AstNodeExpr::makeStmt() { return new AstStmtExpr{fileline(), this}; } +// Walk up the AST via backp() to find the containing AstNodeModule. +// Returns nullptr if not found. +inline AstNodeModule* findParentModule(AstNode* nodep) { + for (AstNode* curp = nodep; curp; curp = curp->backp()) { + if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp; + } + return nullptr; +} + #endif // Guard diff --git a/src/V3LinkDotIfaceCapture.cpp b/src/V3LinkDotIfaceCapture.cpp index 9c6a6bad0..0e880172d 100644 --- a/src/V3LinkDotIfaceCapture.cpp +++ b/src/V3LinkDotIfaceCapture.cpp @@ -26,10 +26,7 @@ V3LinkDotIfaceCapture::CapturedMap V3LinkDotIfaceCapture::s_map{}; bool V3LinkDotIfaceCapture::s_enabled = true; AstNodeModule* V3LinkDotIfaceCapture::findOwnerModule(AstNode* nodep) { - for (AstNode* curp = nodep; curp; curp = curp->backp()) { - if (AstNodeModule* const modp = VN_CAST(curp, NodeModule)) return modp; - } - return nullptr; + return findParentModule(nodep); // Shared utility in V3AstInlines.h } bool V3LinkDotIfaceCapture::finalizeCapturedEntry(CapturedMap::iterator it, const char* reasonp) { diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 64691bba7..eaa300e6a 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -441,6 +441,8 @@ class TristateVisitor final : public TristateBaseVisitor { // Used only on LHS of assignment const AstNode* m_logicp = nullptr; // Current logic being built TristateGraph m_tgraph; // Logic graph + // Map: interface AstVar* -> list of per-module (enVarp, outVarp) contribution pairs + std::map>> m_ifaceContribs; // STATS VDouble0 m_statTriSigs; // stat tracking @@ -469,6 +471,29 @@ class TristateVisitor final : public TristateBaseVisitor { AstConst* const newp = new AstConst{nodep->fileline(), num}; return newp; } + // Create AstVarXRef with inlinedDots copied from a source xref + AstVarXRef* newVarXRef(FileLine* fl, AstVar* varp, const string& dotted, + VAccess access, const string& inlinedDots) { + AstVarXRef* const xrefp = new AstVarXRef{fl, varp, dotted, access}; + xrefp->inlinedDots(inlinedDots); + return xrefp; + } + // Check if a module accesses an interface through a modport, by looking up + // the first component of the dotted path among the module's interface port vars. + // No symbol table is available at V3Tristate time, so linear scan is required. + AstModport* findModportForDotted(AstNodeModule* modp, const string& dottedPath) { + if (dottedPath.empty()) return nullptr; + const string firstComp = dottedPath.substr(0, dottedPath.find('.')); + for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstVar* const ivarp = VN_CAST(stmtp, Var); + if (!ivarp || ivarp->name() != firstComp) continue; + const AstIfaceRefDType* const ifaceDtp + = VN_CAST(ivarp->dtypep()->skipRefp(), IfaceRefDType); + if (ifaceDtp && ifaceDtp->modportp()) return ifaceDtp->modportp(); + break; + } + return nullptr; + } AstNodeExpr* getEnp(AstNode* nodep) { if (nodep->user1p()) { if (AstNodeVarRef* const refp = VN_CAST(nodep, NodeVarRef)) { @@ -486,6 +511,17 @@ class TristateVisitor final : public TristateBaseVisitor { // Otherwise return the previous output enable return VN_AS(nodep->user1p(), NodeExpr); } + // Add a ModportVarRef for varp to modportp if not already present + void addModportVarRefIfMissing(AstModport* modportp, AstVar* varp, VDirection dir) { + for (AstNode* mrp = modportp->varsp(); mrp; mrp = mrp->nextp()) { + AstModportVarRef* const mvrp = VN_CAST(mrp, ModportVarRef); + if (mvrp && mvrp->varp() == varp) return; // Already present + } + AstModportVarRef* const mvrp + = new AstModportVarRef{varp->fileline(), varp->name(), dir}; + mvrp->varp(varp); + modportp->addVarsp(mvrp); + } AstVar* getCreateEnVarp(AstVar* invarp, bool isTop) { // Return the master __en for the specified input variable if (!invarp->user1p()) { @@ -494,7 +530,27 @@ class TristateVisitor final : public TristateBaseVisitor { invarp->name() + "__en", invarp}; // Inherited VDirection::INPUT UINFO(9, " newenv " << newp); - modAddStmtp(invarp, newp); + // If the variable belongs to a different module (e.g. interface), + // create __en in that module so VarXRefs with the same dotted path can reach it. + AstNodeModule* const ownerModp = findParentModule(invarp); + if (ownerModp && ownerModp != m_modp) { + ownerModp->addStmtsp(newp); + // Add __en to any modports that reference the original var, + // so VarXRefs through modport paths can resolve it. + for (AstNode* stmtp = ownerModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + AstModport* const modportp = VN_CAST(stmtp, Modport); + if (!modportp) continue; + for (AstNode* mrp = modportp->varsp(); mrp; mrp = mrp->nextp()) { + AstModportVarRef* const mvrp = VN_CAST(mrp, ModportVarRef); + if (mvrp && mvrp->varp() == invarp) { + addModportVarRefIfMissing(modportp, newp, VDirection::INPUT); + break; + } + } + } + } else { + modAddStmtp(invarp, newp); + } invarp->user1p(newp); // find envar given invarp } return VN_AS(invarp->user1p(), Var); @@ -511,6 +567,10 @@ class TristateVisitor final : public TristateBaseVisitor { if (AstVarRef* const varrefp = VN_CAST(nodep, VarRef)) { return new AstVarRef{varrefp->fileline(), getCreateEnVarp(varrefp->varp(), false), VAccess::READ}; + } else if (AstVarXRef* const xrefp = VN_CAST(nodep, VarXRef)) { + AstVar* const enVarp = getCreateEnVarp(xrefp->varp(), false); + return newVarXRef(xrefp->fileline(), enVarp, xrefp->dotted(), VAccess::READ, + xrefp->inlinedDots()); } else if (AstConst* const constp = VN_CAST(nodep, Const)) { return getNonZConstp(constp); } else if (AstExtend* const extendp = VN_CAST(nodep, Extend)) { @@ -636,14 +696,65 @@ class TristateVisitor final : public TristateBaseVisitor { // Now go through the lhs driver map and generate the output // enable logic for any tristates. // Note there might not be any drivers. - for (auto varp : vars) { // Use vector instead of m_lhsmap iteration for node stability - const auto it = m_lhsmap.find(varp); + for (AstVar* varp : vars) { // Use vector instead of m_lhsmap iteration for stability + const std::map::iterator it = m_lhsmap.find(varp); if (it == m_lhsmap.end()) continue; AstVar* const invarp = it->first; RefStrengthVec* refsp = it->second; // Figure out if this var needs tristate expanded. if (m_tgraph.isTristate(invarp)) { - insertTristatesSignal(nodep, invarp, refsp); + // Check if the var is owned by a different module (cross-module reference). + // For interface vars this is expected; for regular modules it's unsupported. + AstNodeModule* const ownerModp = findParentModule(invarp); + const bool isCrossModule + = ownerModp && ownerModp != nodep && !invarp->isIO(); + const bool isIfaceTri = isCrossModule && VN_IS(ownerModp, Iface); + + if (isCrossModule && !isIfaceTri) { + // Hierarchical writes to non-interface module tri wires are unsupported + for (RefStrength& rs : *refsp) { + if (VN_IS(rs.m_varrefp, VarXRef)) { + rs.m_varrefp->v3warn( + E_UNSUPPORTED, + "Unsupported tristate construct: hierarchical driver" + " of non-interface tri signal: " + << invarp->prettyNameQ()); + break; + } + } + } else if (isIfaceTri) { + // Interface tristate vars: drivers from different interface instances + // (different VarXRef dotted paths) must be processed separately. + // E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in + // the ifc interface, but each instance needs its own contribution slot. + struct PartitionInfo { + RefStrengthVec refs; + string inlinedDots; + }; + std::map partitions; + for (RefStrength& rs : *refsp) { + if (AstVarXRef* const xrefp = VN_CAST(rs.m_varrefp, VarXRef)) { + PartitionInfo& pi = partitions[xrefp->dotted()]; + pi.refs.push_back(rs); + if (pi.inlinedDots.empty()) { + pi.inlinedDots = xrefp->inlinedDots(); + } + } else { + partitions[""].refs.push_back(rs); + } + } + for (auto& kv : partitions) { + insertTristatesSignal(nodep, invarp, &kv.second.refs, true, + kv.first, kv.second.inlinedDots, + findModportForDotted(nodep, kv.first)); + } + } else if (VN_IS(nodep, Iface)) { + // Local driver in an interface module — use contribution mechanism + // so it can be combined with any external drivers later + insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr); + } else { + insertTristatesSignal(nodep, invarp, refsp, false, "", "", nullptr); + } } else { UINFO(8, " NO TRISTATE ON:" << invarp); } @@ -671,7 +782,19 @@ class TristateVisitor final : public TristateBaseVisitor { varp}; // 2-state ok; sep enable UINFO(9, " newout " << newLhsp); nodep->addStmtsp(newLhsp); - refp->varp(newLhsp); + // When retargeting a VarXRef to a local __out var, the dotted path + // becomes inconsistent. Replace the VarXRef with a local VarRef. + if (VN_IS(refp, VarXRef)) { + AstVarRef* const localRefp = new AstVarRef{refp->fileline(), newLhsp, VAccess::WRITE}; + localRefp->user1p(refp->user1p()); + refp->user1p(nullptr); + refp->replaceWith(localRefp); + VL_DO_DANGLING(pushDeletep(refp), refp); + refp = localRefp; + it->m_varrefp = localRefp; + } else { + refp->varp(newLhsp); + } // create a new var for this drivers enable signal AstVar* const newEnLhsp @@ -708,7 +831,13 @@ class TristateVisitor final : public TristateBaseVisitor { nodep->addStmtsp(new AstAlways{enAssp}); } - void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, RefStrengthVec* refsp) { + // isIfaceTri: true when the var is a tristate in an interface module (local or external). + // ifaceDottedPath/ifaceInlinedDots/ifaceModportp are non-empty only for external + // (cross-module) drivers; empty for local drivers within the interface itself. + void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, + RefStrengthVec* refsp, bool isIfaceTri, + const string& ifaceDottedPath, + const string& ifaceInlinedDots, AstModport* ifaceModportp) { UINFO(8, " TRISTATE EXPANDING:" << invarp); ++m_statTriSigs; m_tgraph.didProcess(invarp); @@ -721,29 +850,32 @@ class TristateVisitor final : public TristateBaseVisitor { // Or if this is a top-level inout, do tristate expand if requested // by pinsInoutEnables(). The resolution will be done outside of // verilator. + // Interface tristate vars skip port conversion (they use contribution vars instead). AstVar* envarp = nullptr; AstVar* outvarp = nullptr; // __out AstVar* lhsp = invarp; // Variable to assign drive-value to ( or __out) - bool isTopInout - = (invarp->direction() == VDirection::INOUT) && invarp->isIO() && nodep->isTop(); - if ((v3Global.opt.pinsInoutEnables() && isTopInout) - || (!nodep->isTop() && invarp->isIO())) { - // This var becomes an input - invarp->varType2In(); // convert existing port to type input - // Create an output port (__out) - outvarp = getCreateOutVarp(invarp, isTopInout); - outvarp->varType2Out(); - lhsp = outvarp; // Must assign to __out, not to normal input signal - UINFO(9, " TRISTATE propagates up with " << lhsp); - // Create an output enable port (__en) - // May already be created if have foo === 1'bz somewhere - envarp = getCreateEnVarp(invarp, isTopInout); // direction to be set in visit(AstPin*) - // - outvarp->user1p(envarp); - m_varAux(outvarp).pullp = m_varAux(invarp).pullp; // AstPull* propagation - if (m_varAux(invarp).pullp) UINFO(9, "propagate pull to " << outvarp); - } else if (invarp->user1p()) { - envarp = VN_AS(invarp->user1p(), Var); // From CASEEQ, foo === 1'bz + const bool isTopInout + = !isIfaceTri && (invarp->direction() == VDirection::INOUT) && invarp->isIO() + && nodep->isTop(); + if (!isIfaceTri) { + if ((v3Global.opt.pinsInoutEnables() && isTopInout) + || (!nodep->isTop() && invarp->isIO())) { + // This var becomes an input + invarp->varType2In(); // convert existing port to type input + // Create an output port (__out) + outvarp = getCreateOutVarp(invarp, isTopInout); + outvarp->varType2Out(); + lhsp = outvarp; // Must assign to __out, not to normal input signal + UINFO(9, " TRISTATE propagates up with " << lhsp); + // Create an output enable port (__en) + // May already be created if have foo === 1'bz somewhere + envarp = getCreateEnVarp(invarp, isTopInout); // dir set in visit(AstPin*) + outvarp->user1p(envarp); + m_varAux(outvarp).pullp = m_varAux(invarp).pullp; // AstPull* propagation + if (m_varAux(invarp).pullp) UINFO(9, "propagate pull to " << outvarp); + } else if (invarp->user1p()) { + envarp = VN_AS(invarp->user1p(), Var); // From CASEEQ, foo === 1'bz + } } AstNodeExpr* orp = nullptr; @@ -752,15 +884,26 @@ class TristateVisitor final : public TristateBaseVisitor { std::sort(refsp->begin(), refsp->end(), [](RefStrength a, RefStrength b) { return a.m_strength > b.m_strength; }); - auto beginStrength = refsp->begin(); + RefStrengthVec::iterator beginStrength = refsp->begin(); while (beginStrength != refsp->end()) { - auto endStrength = beginStrength + 1; + RefStrengthVec::iterator endStrength = beginStrength + 1; while (endStrength != refsp->end() && endStrength->m_strength == beginStrength->m_strength) endStrength++; FileLine* const fl = beginStrength->m_varrefp->fileline(); - const string strengthVarName = lhsp->name() + "__" + beginStrength->m_strength.ascii(); + // For interface tristate vars, uniquify strength var names by including the + // interface instance path, since multiple interface instances may have + // identically-named tristate signals (e.g. io_ifc.d and io_ifc_mc.d both + // create d__strong in the driving module) + string strengthPrefix; + if (isIfaceTri && !ifaceDottedPath.empty()) { + strengthPrefix = ifaceDottedPath; + std::replace(strengthPrefix.begin(), strengthPrefix.end(), '.', '_'); + strengthPrefix += "__"; + } + const string strengthVarName + = strengthPrefix + lhsp->name() + "__" + beginStrength->m_strength.ascii(); // var__strength variable AstVar* const varStrengthp = new AstVar{fl, VVarType::MODULETEMP, strengthVarName, @@ -796,11 +939,73 @@ class TristateVisitor final : public TristateBaseVisitor { beginStrength = endStrength; } + // For interface tristate vars, store per-module contributions for later combining. + // The interface module owns the contribution vars; resolution happens in + // combineIfaceContribs() after all modules are processed. + if (isIfaceTri) { + AstNodeModule* const ifaceModp = findParentModule(invarp); + UASSERT_OBJ(ifaceModp, invarp, "Interface tristate var has no parent module"); + const int contribIdx = static_cast(m_ifaceContribs[invarp].size()); + FileLine* const fl = invarp->fileline(); + + // Create contribution vars in the interface module + AstVar* const contribOutp + = new AstVar{fl, VVarType::MODULETEMP, + invarp->name() + "__out" + cvtToStr(contribIdx), invarp}; + UINFO(9, " iface contribOut " << contribOutp); + ifaceModp->addStmtsp(contribOutp); + + AstVar* const contribEnp + = new AstVar{fl, VVarType::MODULETEMP, + invarp->name() + "__en" + cvtToStr(contribIdx), invarp}; + UINFO(9, " iface contribEn " << contribEnp); + ifaceModp->addStmtsp(contribEnp); + + // If the driving module accesses the interface through a modport, + // add the new contribution vars to the modport so VarXRefs + // can be resolved by V3LinkDot through the modport path. + if (ifaceModportp) { + UINFO(9, " adding to modport " << ifaceModportp->name()); + addModportVarRefIfMissing(ifaceModportp, contribOutp, VDirection::OUTPUT); + addModportVarRefIfMissing(ifaceModportp, contribEnp, VDirection::OUTPUT); + } + + // Assign drive value and enable to contribution vars. + // External drivers use VarXRef; local drivers use VarRef. + { + AstNodeVarRef* const lhsp + = ifaceDottedPath.empty() + ? static_cast( + new AstVarRef{fl, contribOutp, VAccess::WRITE}) + : static_cast( + newVarXRef(fl, contribOutp, ifaceDottedPath, VAccess::WRITE, + ifaceInlinedDots)); + AstAssignW* const assp = new AstAssignW{fl, lhsp, orp}; + assp->user2Or(U2_BOTH); + nodep->addStmtsp(new AstAlways{assp}); + } + { + AstNodeVarRef* const lhsp + = ifaceDottedPath.empty() + ? static_cast( + new AstVarRef{fl, contribEnp, VAccess::WRITE}) + : static_cast( + newVarXRef(fl, contribEnp, ifaceDottedPath, VAccess::WRITE, + ifaceInlinedDots)); + AstAssignW* const assp = new AstAssignW{fl, lhsp, enp}; + assp->user2Or(U2_BOTH); + nodep->addStmtsp(new AstAlways{assp}); + } + + m_ifaceContribs[invarp].push_back({contribEnp, contribOutp}); + return; + } + if (!outvarp) { // This is the final pre-forced resolution of the tristate, so we apply // the pull direction to any undriven pins. const AstPull* const pullp = m_varAux(lhsp).pullp; - bool pull1 = pullp && pullp->direction() == 1; // Else default is down + const bool pull1 = pullp && pullp->direction() == 1; // Else default is down AstNodeExpr* undrivenp; if (envarp) { @@ -1788,8 +1993,14 @@ class TristateVisitor final : public TristateBaseVisitor { && m_tgraph.feedsTri(nodep)) { // Then propagate the enable from the original variable UINFO(9, " Ref-to-tri " << nodep); + FileLine* const fl = nodep->fileline(); AstVar* const enVarp = getCreateEnVarp(nodep->varp(), false); - nodep->user1p(new AstVarRef{nodep->fileline(), enVarp, VAccess::READ}); + if (AstVarXRef* const xrefp = VN_CAST(nodep, VarXRef)) { + nodep->user1p(newVarXRef(fl, enVarp, xrefp->dotted(), VAccess::READ, + xrefp->inlinedDots())); + } else { + nodep->user1p(new AstVarRef{fl, enVarp, VAccess::READ}); + } } } } @@ -1899,12 +2110,79 @@ class TristateVisitor final : public TristateBaseVisitor { checkUnhandled(nodep); } + void combineIfaceContribs() { + // After all modules have been processed, combine per-module contributions + // for each interface tristate signal into final resolution logic. + // Key is the canonical AstVar in the interface module (shared across instances). + for (std::pair>>& kv + : m_ifaceContribs) { + AstVar* const invarp = kv.first; + std::vector>& contribs = kv.second; + AstNodeModule* const ifaceModp = findParentModule(invarp); + UASSERT_OBJ(ifaceModp, invarp, "Interface tristate var has no parent module"); + FileLine* const fl = invarp->fileline(); + + UINFO(8, " IFACE COMBINE " << contribs.size() << " contribs for " << invarp); + + // Build resolution: d = (out0 & en0) | (out1 & en1) | ... | (~combined_en & pull) + AstNodeExpr* orp = nullptr; + AstNodeExpr* enp = nullptr; + + for (std::pair& contrib : contribs) { + AstVar* const contribEnp = contrib.first; + AstVar* const contribOutp = contrib.second; + + AstNodeExpr* const outRefp = new AstVarRef{fl, contribOutp, VAccess::READ}; + AstNodeExpr* const enRefp = new AstVarRef{fl, contribEnp, VAccess::READ}; + AstNodeExpr* const andp = new AstAnd{fl, outRefp, enRefp}; + + orp = (!orp) ? andp : new AstOr{fl, orp, andp}; + + AstNodeExpr* const enRef2p = new AstVarRef{fl, contribEnp, VAccess::READ}; + enp = (!enp) ? enRef2p : new AstOr{fl, enp, enRef2p}; + } + + // Create or get __en var for this interface signal + AstVar* envarp = VN_CAST(invarp->user1p(), Var); + if (!envarp) { + envarp = new AstVar{fl, VVarType::MODULETEMP, invarp->name() + "__en", invarp}; + ifaceModp->addStmtsp(envarp); + invarp->user1p(envarp); + } + + // Assign combined enable + AstAssignW* const enAssp = new AstAssignW{ + fl, new AstVarRef{fl, envarp, VAccess::WRITE}, enp}; + UINFOTREE(9, enAssp, "", "iface-enAssp"); + ifaceModp->addStmtsp(new AstAlways{enAssp}); + + // Pull resolution + const AstPull* const pullp = m_varAux(invarp).pullp; + const bool pull1 = pullp && pullp->direction() == 1; // Else default is down + + AstNodeExpr* undrivenp + = new AstNot{fl, new AstVarRef{fl, envarp, VAccess::READ}}; + undrivenp = new AstAnd{fl, undrivenp, newAllZerosOrOnes(invarp, pull1)}; + orp = new AstOr{fl, orp, undrivenp}; + + // Assign final value to invarp + AstAssignW* const assp + = new AstAssignW{fl, new AstVarRef{fl, invarp, VAccess::WRITE}, orp}; + assp->user2Or(U2_BOTH); + UINFOTREE(9, assp, "", "iface-lhsp-eqn"); + ifaceModp->addStmtsp(new AstAlways{assp}); + } + } + public: // CONSTRUCTORS explicit TristateVisitor(AstNetlist* netlistp) { m_tgraph.clearAndCheck(); iterateChildrenBackwardsConst(netlistp); + // Combine interface tristate contributions after all modules processed + combineIfaceContribs(); + #ifdef VL_LEAK_CHECKS // It's a bit chaotic up there std::vector unusedRootps; diff --git a/test_regress/t/t_interface_tristate_hier.py b/test_regress/t/t_interface_tristate_hier.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_interface_tristate_hier.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_tristate_hier.v b/test_regress/t/t_interface_tristate_hier.v new file mode 100755 index 000000000..ba0c88ed8 --- /dev/null +++ b/test_regress/t/t_interface_tristate_hier.v @@ -0,0 +1,258 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +// 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 + +// verilator lint_off MULTIDRIVEN + +interface ifc; + wire we0, we2; + tri [15:0] d; +endinterface + +interface ifc_multi; + wire we0, wea, web; + tri [15:0] d; +endinterface + +module bot ( + ifc io_ifc +); + assign io_ifc.d = io_ifc.we2 ? 16'hd2 : 16'hzzzz; +endmodule + +module passthru ( + ifc io_ifc +); + bot u_bot (.*); +endmodule + +module bot_a ( + ifc_multi io_ifc +); + assign io_ifc.d = io_ifc.wea ? 16'hd2 : 16'hzzzz; +endmodule + +module bot_b ( + ifc_multi io_ifc +); + assign io_ifc.d = io_ifc.web ? 16'hd2 : 16'hzzzz; +endmodule + +module passthru_multi_c ( + ifc_multi io_ifc +); + bot_a u_bot_a (.*); + bot_b u_bot_b (.*); +endmodule + +module passthru_deep ( + ifc io_ifc +); + passthru u_inner (.*); +endmodule + +module t; + ifc io_ifc (); + ifc io_ifc_local (); + ifc io_ifc_b0 (); + ifc io_ifc_b1 (); + ifc_multi io_ifc_mc (); + ifc io_arr [1:0](); + ifc io_ifc_deep (); + + // Test top assignment + assign io_ifc.d = io_ifc.we0 ? 16'hd0 : 16'hzzzz; + assign io_ifc_local.d = io_ifc_local.we0 ? 16'hd0 : 16'hzzzz; + assign io_ifc_mc.d = io_ifc_mc.we0 ? 16'hd0 : 16'hzzzz; + + logic we0, we2; + logic we2_b0, we2_b1; + logic we0_mc, wea_mc, web_mc; + assign io_ifc.we0 = we0; + assign io_ifc.we2 = we2; + assign io_ifc_local.we0 = we0; + assign io_ifc_local.we2 = 1'b0; + assign io_ifc_b0.we0 = 1'b0; + assign io_ifc_b0.we2 = we2_b0; + assign io_ifc_b1.we0 = 1'b0; + assign io_ifc_b1.we2 = we2_b1; + assign io_ifc_mc.we0 = we0_mc; + assign io_ifc_mc.wea = wea_mc; + assign io_ifc_mc.web = web_mc; + + // Interface array signals + logic we2_arr0, we2_arr1; + assign io_arr[0].we0 = 1'b0; + assign io_arr[0].we2 = we2_arr0; + assign io_arr[1].we0 = 1'b0; + assign io_arr[1].we2 = we2_arr1; + + // Deep nesting signals + logic we0_deep, we2_deep; + assign io_ifc_deep.we0 = we0_deep; + assign io_ifc_deep.we2 = we2_deep; + assign io_ifc_deep.d = io_ifc_deep.we0 ? 16'hd0 : 16'hzzzz; + + passthru u_passthru (.*); + passthru u_passthru_b0 (.io_ifc(io_ifc_b0)); + passthru u_passthru_b1 (.io_ifc(io_ifc_b1)); + passthru_multi_c u_passthru_mc (.io_ifc(io_ifc_mc)); + passthru u_passthru_arr0 (.io_ifc(io_arr[0])); + passthru u_passthru_arr1 (.io_ifc(io_arr[1])); + passthru_deep u_passthru_deep (.io_ifc(io_ifc_deep)); + + initial begin + #1; + we0 = 1'b0; + we2 = 1'b0; + we2_b0 = 1'b0; + we2_b1 = 1'b0; + we0_mc = 1'b0; + wea_mc = 1'b0; + web_mc = 1'b0; + we2_arr0 = 1'b0; + we2_arr1 = 1'b0; + we0_deep = 1'b0; + we2_deep = 1'b0; + #1; + `checkh(io_ifc.d, 16'hzzzz); + `checkh(io_ifc_local.d, 16'hzzzz); + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hzzzz); + `checkh(io_ifc_mc.d, 16'hzzzz); + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hzzzz); + `checkh(io_ifc_deep.d, 16'hzzzz); + + #1; + we0 = 1'b1; + we2 = 1'b0; + #1; + `checkh(io_ifc.d, 16'hd0); + `checkh(io_ifc_local.d, 16'hd0); + + #1; + we0 = 1'b0; + we2 = 1'b0; + #1; + `checkh(io_ifc.d, 16'hzzzz); + `checkh(io_ifc_local.d, 16'hzzzz); + + #1; + we0 = 1'b0; + we2 = 1'b1; + #1; + `checkh(io_ifc.d, 16'hd2); + `checkh(io_ifc_local.d, 16'hzzzz); + + // Interface passed a->b, where b is instantiated multiple times (separate interfaces) + #1; + we2_b0 = 1'b1; + we2_b1 = 1'b0; + #1; + `checkh(io_ifc_b0.d, 16'hd2); + `checkh(io_ifc_b1.d, 16'hzzzz); + + #1; + we2_b0 = 1'b0; + we2_b1 = 1'b1; + #1; + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hd2); + + #1; + we2_b0 = 1'b0; + we2_b1 = 1'b0; + #1; + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hzzzz); + + // Interface passed a->b, where c is instantiated multiple times (same interface) + #1; + we0_mc = 1'b1; + wea_mc = 1'b0; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hd0); + + #1; + we0_mc = 1'b0; + wea_mc = 1'b1; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hd2); + + #1; + we0_mc = 1'b0; + wea_mc = 1'b0; + web_mc = 1'b1; + #1; + `checkh(io_ifc_mc.d, 16'hd2); + + #1; + wea_mc = 1'b1; + web_mc = 1'b1; + #1; + `checkh(io_ifc_mc.d, 16'hd2); + + #1; + wea_mc = 1'b0; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hzzzz); + + // Interface array: each element is independent + #1; + we2_arr0 = 1'b1; + we2_arr1 = 1'b0; + #1; + `checkh(io_arr[0].d, 16'hd2); + `checkh(io_arr[1].d, 16'hzzzz); + + #1; + we2_arr0 = 1'b0; + we2_arr1 = 1'b1; + #1; + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hd2); + + #1; + we2_arr0 = 1'b0; + we2_arr1 = 1'b0; + #1; + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hzzzz); + + // Deep nesting: passthru_deep -> passthru -> bot (3 levels) + #1; + we0_deep = 1'b1; + we2_deep = 1'b0; + #1; + `checkh(io_ifc_deep.d, 16'hd0); + + #1; + we0_deep = 1'b0; + we2_deep = 1'b1; + #1; + `checkh(io_ifc_deep.d, 16'hd2); + + #1; + we0_deep = 1'b0; + we2_deep = 1'b0; + #1; + `checkh(io_ifc_deep.d, 16'hzzzz); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_tri_hier_ref_unsup.out b/test_regress/t/t_tri_hier_ref_unsup.out new file mode 100644 index 000000000..17b04a233 --- /dev/null +++ b/test_regress/t/t_tri_hier_ref_unsup.out @@ -0,0 +1,10 @@ +%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:34:17: Unsupported tristate construct: hierarchical driver of non-interface tri signal: 'bus' + : ... note: In instance 't' + 34 | assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:23:14: Unsupported tristate construct (in graph; not converted): VAR 'bus' + : ... note: In instance 't.u_sub' + 23 | tri [7:0] bus; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_tri_hier_ref_unsup.py b/test_regress/t/t_tri_hier_ref_unsup.py new file mode 100644 index 000000000..a00127d05 --- /dev/null +++ b/test_regress/t/t_tri_hier_ref_unsup.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=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_tri_hier_ref_unsup.v b/test_regress/t/t_tri_hier_ref_unsup.v new file mode 100644 index 000000000..3ad2edec6 --- /dev/null +++ b/test_regress/t/t_tri_hier_ref_unsup.v @@ -0,0 +1,69 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test case #1: Hierarchical write to a non-interface submodule's tri wire. +// A parent module drives a child module's internal tri wire via hierarchical +// reference, in addition to the child's own internal driver. +// +// 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 + +// 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 + +// verilator lint_off MULTIDRIVEN + +module sub_with_tri ( + input we_internal +); + tri [7:0] bus; + // Internal driver: drives 8'hAA when we_internal is high + assign bus = we_internal ? 8'hAA : 8'hzz; +endmodule + +module t; + logic sub_we_internal; + sub_with_tri u_sub(.we_internal(sub_we_internal)); + + logic hier_we; + // Drive u_sub.bus hierarchically from this module + assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; + + initial begin + // All drivers off -> high-Z + #1; + hier_we = 1'b0; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hzz); + + // External hierarchical driver on + #1; + hier_we = 1'b1; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hBB); + + // Internal driver on, external off + #1; + hier_we = 1'b0; + sub_we_internal = 1'b1; + #1; + `checkh(u_sub.bus, 8'hAA); + + // Both off again + #1; + hier_we = 1'b0; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hzz); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_tri_iface_eqcase.py b/test_regress/t/t_tri_iface_eqcase.py new file mode 100644 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_tri_iface_eqcase.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_tri_iface_eqcase.v b/test_regress/t/t_tri_iface_eqcase.v new file mode 100644 index 000000000..64dd23486 --- /dev/null +++ b/test_regress/t/t_tri_iface_eqcase.v @@ -0,0 +1,158 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test === and !== on interface tristate signals accessed via VarXRef. +// Exercises the getEnExprBasedOnOriginalp() VarXRef path added for #3466. +// +// 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 + +// 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 + +// verilator lint_off MULTIDRIVEN + +// Simple interface with one external driver +interface ifc; + logic we; + tri [7:0] d; +endinterface + +module drv ( + ifc io_ifc +); + assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; +endmodule + +// Module that compares interface tri signal via VarXRef (cross-module === / !==) +module chk ( + ifc io_ifc, + output logic is_z, + output logic is_5a, + output logic not_z, + output logic not_5a +); + assign is_z = (io_ifc.d === 8'hzz); + assign is_5a = (io_ifc.d === 8'h5A); + assign not_z = (io_ifc.d !== 8'hzz); + assign not_5a = (io_ifc.d !== 8'h5A); +endmodule + +// Interface with modport +interface ifc_mp; + logic we; + tri [7:0] d; + modport drv_mp (input we, inout d); + modport chk_mp (input we, inout d); +endinterface + +module drv_mp ( + ifc_mp.drv_mp io_ifc +); + assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; +endmodule + +module chk_mp ( + ifc_mp.chk_mp io_ifc, + output logic is_z, + output logic is_5a, + output logic not_z, + output logic not_5a +); + assign is_z = (io_ifc.d === 8'hzz); + assign is_5a = (io_ifc.d === 8'h5A); + assign not_z = (io_ifc.d !== 8'hzz); + assign not_5a = (io_ifc.d !== 8'h5A); +endmodule + +// Deep hierarchy: passthru -> drv +module passthru ( + ifc io_ifc +); + drv u_drv (.*); +endmodule + +module t; + // ---- Basic (no modport) ---- + ifc i (); + logic is_z, is_5a, not_z, not_5a; + drv u_drv (.io_ifc(i)); + chk u_chk (.io_ifc(i), .is_z(is_z), .is_5a(is_5a), + .not_z(not_z), .not_5a(not_5a)); + + // ---- Modport ---- + ifc_mp i_mp (); + logic mp_is_z, mp_is_5a, mp_not_z, mp_not_5a; + drv_mp u_drv_mp (.io_ifc(i_mp)); + chk_mp u_chk_mp (.io_ifc(i_mp), .is_z(mp_is_z), .is_5a(mp_is_5a), + .not_z(mp_not_z), .not_5a(mp_not_5a)); + + // ---- Deep hierarchy ---- + ifc i_deep (); + logic deep_is_z, deep_is_5a, deep_not_z, deep_not_5a; + passthru u_deep (.io_ifc(i_deep)); + chk u_chk_deep (.io_ifc(i_deep), .is_z(deep_is_z), .is_5a(deep_is_5a), + .not_z(deep_not_z), .not_5a(deep_not_5a)); + + initial begin + // Driver off => high-Z + i.we = 1'b0; + i_mp.we = 1'b0; + i_deep.we = 1'b0; + #1; + + `checkh(is_z, 1'b1); + `checkh(is_5a, 1'b0); + `checkh(not_z, 1'b0); + `checkh(not_5a, 1'b1); + `checkh(mp_is_z, 1'b1); + `checkh(mp_is_5a, 1'b0); + `checkh(mp_not_z, 1'b0); + `checkh(mp_not_5a, 1'b1); + `checkh(deep_is_z, 1'b1); + `checkh(deep_is_5a, 1'b0); + + // Driver on => 8'h5A + #1; + i.we = 1'b1; + i_mp.we = 1'b1; + i_deep.we = 1'b1; + #1; + + `checkh(is_z, 1'b0); + `checkh(is_5a, 1'b1); + `checkh(not_z, 1'b1); + `checkh(not_5a, 1'b0); + `checkh(mp_is_z, 1'b0); + `checkh(mp_is_5a, 1'b1); + `checkh(mp_not_z, 1'b1); + `checkh(mp_not_5a, 1'b0); + `checkh(deep_is_z, 1'b0); + `checkh(deep_is_5a, 1'b1); + + // Driver off again => high-Z + #1; + i.we = 1'b0; + i_mp.we = 1'b0; + i_deep.we = 1'b0; + #1; + + `checkh(is_z, 1'b1); + `checkh(is_5a, 1'b0); + `checkh(not_z, 1'b0); + `checkh(not_5a, 1'b1); + `checkh(mp_is_z, 1'b1); + `checkh(mp_is_5a, 1'b0); + `checkh(mp_not_z, 1'b0); + `checkh(mp_not_5a, 1'b1); + `checkh(deep_is_z, 1'b1); + `checkh(deep_is_5a, 1'b0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_tri_iface_eqcase_modport_bad.out b/test_regress/t/t_tri_iface_eqcase_modport_bad.out new file mode 100644 index 000000000..486d1e507 --- /dev/null +++ b/test_regress/t/t_tri_iface_eqcase_modport_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_tri_iface_eqcase_modport_bad.v:23:26: Can't find definition of 'd' in dotted variable/method: 'io_ifc.d' + 23 | assign is_z = (io_ifc.d === 8'hzz); + | ^ + ... Known scopes under 'io_ifc': + ... 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_tri_iface_eqcase_modport_bad.py b/test_regress/t/t_tri_iface_eqcase_modport_bad.py new file mode 100644 index 000000000..a00127d05 --- /dev/null +++ b/test_regress/t/t_tri_iface_eqcase_modport_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=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_tri_iface_eqcase_modport_bad.v b/test_regress/t/t_tri_iface_eqcase_modport_bad.v new file mode 100644 index 000000000..6f4a13fe4 --- /dev/null +++ b/test_regress/t/t_tri_iface_eqcase_modport_bad.v @@ -0,0 +1,31 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Verify that modport access control is not relaxed by tristate __en +// auto-insertion: accessing a tri signal not exposed through the modport +// should still produce a linker error. +// +// 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 + +interface ifc; + logic we; + tri [7:0] d; + modport no_d_mp (input we); // d is NOT exposed +endinterface + +module chk_bad ( + ifc.no_d_mp io_ifc, + output logic is_z +); + assign is_z = (io_ifc.d === 8'hzz); +endmodule + +module t; + ifc i (); + logic is_z; + chk_bad u (.io_ifc(i), .is_z(is_z)); + initial $finish; +endmodule diff --git a/test_regress/t/t_tri_iface_mixed.py b/test_regress/t/t_tri_iface_mixed.py new file mode 100644 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_tri_iface_mixed.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_tri_iface_mixed.v b/test_regress/t/t_tri_iface_mixed.v new file mode 100644 index 000000000..aad5c5194 --- /dev/null +++ b/test_regress/t/t_tri_iface_mixed.v @@ -0,0 +1,136 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test mixed local + external tristate drivers on the same interface signal. +// The interface contains a local driver, and an external module also drives +// the same tri signal through a port. +// +// 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 + +// 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 + +// verilator lint_off MULTIDRIVEN + +// Basic interface with local driver (no modport) +interface ifc; + logic we_local; + logic we_ext; + tri [7:0] d; + // Local driver inside the interface + assign d = we_local ? 8'hA5 : 8'hzz; +endinterface + +module drv ( + ifc io_ifc +); + // External driver from a different module + assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; +endmodule + +// Interface with modport and local driver +interface ifc_mp; + logic we_local; + logic we_ext; + tri [7:0] d; + modport drv_mp (input we_ext, inout d); + // Local driver inside the interface + assign d = we_local ? 8'hA5 : 8'hzz; +endinterface + +module drv_mp ( + ifc_mp.drv_mp io_ifc +); + // External driver through modport + assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; +endmodule + +module t; + ifc i (); + drv u (.io_ifc(i)); + + ifc_mp i_mp (); + drv_mp u_mp (.io_ifc(i_mp)); + + initial begin + i.we_local = 1'b0; + i.we_ext = 1'b0; + i_mp.we_local = 1'b0; + i_mp.we_ext = 1'b0; + #1; + + // ---- Basic (no modport) ---- + + // Both drivers off => high-Z + `checkh(i.d, 8'hzz); + + // Local driver only + #1; + i.we_local = 1'b1; + i.we_ext = 1'b0; + #1; + `checkh(i.d, 8'hA5); + + // External driver only + #1; + i.we_local = 1'b0; + i.we_ext = 1'b1; + #1; + `checkh(i.d, 8'h3C); + + // Both drivers on (OR of 8'hA5 and 8'h3C in Verilator's tristate model) + #1; + i.we_local = 1'b1; + i.we_ext = 1'b1; + #1; + `checkh(i.d, 8'hBD); + + // Both drivers off again + #1; + i.we_local = 1'b0; + i.we_ext = 1'b0; + #1; + `checkh(i.d, 8'hzz); + + // ---- Modport-based external access ---- + + // Both drivers off => high-Z + `checkh(i_mp.d, 8'hzz); + + // Local driver only + #1; + i_mp.we_local = 1'b1; + i_mp.we_ext = 1'b0; + #1; + `checkh(i_mp.d, 8'hA5); + + // External driver only (through modport) + #1; + i_mp.we_local = 1'b0; + i_mp.we_ext = 1'b1; + #1; + `checkh(i_mp.d, 8'h3C); + + // Both drivers on (OR of 8'hA5 and 8'h3C in Verilator's tristate model) + #1; + i_mp.we_local = 1'b1; + i_mp.we_ext = 1'b1; + #1; + `checkh(i_mp.d, 8'hBD); + + // Both drivers off again + #1; + i_mp.we_local = 1'b0; + i_mp.we_ext = 1'b0; + #1; + `checkh(i_mp.d, 8'hzz); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_tri_root_ref.py b/test_regress/t/t_tri_root_ref.py new file mode 100644 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_tri_root_ref.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(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_tri_root_ref.v b/test_regress/t/t_tri_root_ref.v new file mode 100644 index 000000000..12794c581 --- /dev/null +++ b/test_regress/t/t_tri_root_ref.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test case #4: $root absolute hierarchical reference to tristate signal. +// A submodule reads a tristate signal from the top module via $root. +// +// 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 + +// 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 + +module sub_root_reader ( + output logic [7:0] val +); + assign val = $root.t.root_bus; +endmodule + +module t; + tri [7:0] root_bus; + logic root_we; + assign root_bus = root_we ? 8'hCC : 8'hzz; + + logic [7:0] root_readback; + sub_root_reader u_reader(.val(root_readback)); + + initial begin + #1; + root_we = 1'b0; + #1; + `checkh(root_readback, 8'hzz); + + #1; + root_we = 1'b1; + #1; + `checkh(root_readback, 8'hCC); + + #1; + root_we = 1'b0; + #1; + `checkh(root_readback, 8'hzz); + + #1; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From fb8452c5636e1eefc6041632c6efef8d6f11d938 Mon Sep 17 00:00:00 2001 From: github action Date: Sat, 28 Feb 2026 15:45:52 +0000 Subject: [PATCH 3/9] Apply 'make format' --- src/V3Tristate.cpp | 74 ++++++++----------- test_regress/t/t_interface_tristate_hier.v | 0 test_regress/t/t_tri_hier_ref_unsup.py | 0 test_regress/t/t_tri_iface_eqcase.py | 0 .../t/t_tri_iface_eqcase_modport_bad.py | 0 test_regress/t/t_tri_iface_mixed.py | 0 test_regress/t/t_tri_root_ref.py | 0 7 files changed, 32 insertions(+), 42 deletions(-) mode change 100755 => 100644 test_regress/t/t_interface_tristate_hier.v mode change 100644 => 100755 test_regress/t/t_tri_hier_ref_unsup.py mode change 100644 => 100755 test_regress/t/t_tri_iface_eqcase.py mode change 100644 => 100755 test_regress/t/t_tri_iface_eqcase_modport_bad.py mode change 100644 => 100755 test_regress/t/t_tri_iface_mixed.py mode change 100644 => 100755 test_regress/t/t_tri_root_ref.py diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index eaa300e6a..9596c483f 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -472,8 +472,8 @@ class TristateVisitor final : public TristateBaseVisitor { return newp; } // Create AstVarXRef with inlinedDots copied from a source xref - AstVarXRef* newVarXRef(FileLine* fl, AstVar* varp, const string& dotted, - VAccess access, const string& inlinedDots) { + AstVarXRef* newVarXRef(FileLine* fl, AstVar* varp, const string& dotted, VAccess access, + const string& inlinedDots) { AstVarXRef* const xrefp = new AstVarXRef{fl, varp, dotted, access}; xrefp->inlinedDots(inlinedDots); return xrefp; @@ -517,8 +517,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstModportVarRef* const mvrp = VN_CAST(mrp, ModportVarRef); if (mvrp && mvrp->varp() == varp) return; // Already present } - AstModportVarRef* const mvrp - = new AstModportVarRef{varp->fileline(), varp->name(), dir}; + AstModportVarRef* const mvrp = new AstModportVarRef{varp->fileline(), varp->name(), dir}; mvrp->varp(varp); modportp->addVarsp(mvrp); } @@ -706,8 +705,7 @@ class TristateVisitor final : public TristateBaseVisitor { // Check if the var is owned by a different module (cross-module reference). // For interface vars this is expected; for regular modules it's unsupported. AstNodeModule* const ownerModp = findParentModule(invarp); - const bool isCrossModule - = ownerModp && ownerModp != nodep && !invarp->isIO(); + const bool isCrossModule = ownerModp && ownerModp != nodep && !invarp->isIO(); const bool isIfaceTri = isCrossModule && VN_IS(ownerModp, Iface); if (isCrossModule && !isIfaceTri) { @@ -736,16 +734,14 @@ class TristateVisitor final : public TristateBaseVisitor { if (AstVarXRef* const xrefp = VN_CAST(rs.m_varrefp, VarXRef)) { PartitionInfo& pi = partitions[xrefp->dotted()]; pi.refs.push_back(rs); - if (pi.inlinedDots.empty()) { - pi.inlinedDots = xrefp->inlinedDots(); - } + if (pi.inlinedDots.empty()) { pi.inlinedDots = xrefp->inlinedDots(); } } else { partitions[""].refs.push_back(rs); } } for (auto& kv : partitions) { - insertTristatesSignal(nodep, invarp, &kv.second.refs, true, - kv.first, kv.second.inlinedDots, + insertTristatesSignal(nodep, invarp, &kv.second.refs, true, kv.first, + kv.second.inlinedDots, findModportForDotted(nodep, kv.first)); } } else if (VN_IS(nodep, Iface)) { @@ -785,7 +781,8 @@ class TristateVisitor final : public TristateBaseVisitor { // When retargeting a VarXRef to a local __out var, the dotted path // becomes inconsistent. Replace the VarXRef with a local VarRef. if (VN_IS(refp, VarXRef)) { - AstVarRef* const localRefp = new AstVarRef{refp->fileline(), newLhsp, VAccess::WRITE}; + AstVarRef* const localRefp + = new AstVarRef{refp->fileline(), newLhsp, VAccess::WRITE}; localRefp->user1p(refp->user1p()); refp->user1p(nullptr); refp->replaceWith(localRefp); @@ -834,9 +831,8 @@ class TristateVisitor final : public TristateBaseVisitor { // isIfaceTri: true when the var is a tristate in an interface module (local or external). // ifaceDottedPath/ifaceInlinedDots/ifaceModportp are non-empty only for external // (cross-module) drivers; empty for local drivers within the interface itself. - void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, - RefStrengthVec* refsp, bool isIfaceTri, - const string& ifaceDottedPath, + void insertTristatesSignal(AstNodeModule* nodep, AstVar* const invarp, RefStrengthVec* refsp, + bool isIfaceTri, const string& ifaceDottedPath, const string& ifaceInlinedDots, AstModport* ifaceModportp) { UINFO(8, " TRISTATE EXPANDING:" << invarp); ++m_statTriSigs; @@ -854,9 +850,8 @@ class TristateVisitor final : public TristateBaseVisitor { AstVar* envarp = nullptr; AstVar* outvarp = nullptr; // __out AstVar* lhsp = invarp; // Variable to assign drive-value to ( or __out) - const bool isTopInout - = !isIfaceTri && (invarp->direction() == VDirection::INOUT) && invarp->isIO() - && nodep->isTop(); + const bool isTopInout = !isIfaceTri && (invarp->direction() == VDirection::INOUT) + && invarp->isIO() && nodep->isTop(); if (!isIfaceTri) { if ((v3Global.opt.pinsInoutEnables() && isTopInout) || (!nodep->isTop() && invarp->isIO())) { @@ -949,15 +944,13 @@ class TristateVisitor final : public TristateBaseVisitor { FileLine* const fl = invarp->fileline(); // Create contribution vars in the interface module - AstVar* const contribOutp - = new AstVar{fl, VVarType::MODULETEMP, - invarp->name() + "__out" + cvtToStr(contribIdx), invarp}; + AstVar* const contribOutp = new AstVar{ + fl, VVarType::MODULETEMP, invarp->name() + "__out" + cvtToStr(contribIdx), invarp}; UINFO(9, " iface contribOut " << contribOutp); ifaceModp->addStmtsp(contribOutp); - AstVar* const contribEnp - = new AstVar{fl, VVarType::MODULETEMP, - invarp->name() + "__en" + cvtToStr(contribIdx), invarp}; + AstVar* const contribEnp = new AstVar{ + fl, VVarType::MODULETEMP, invarp->name() + "__en" + cvtToStr(contribIdx), invarp}; UINFO(9, " iface contribEn " << contribEnp); ifaceModp->addStmtsp(contribEnp); @@ -974,24 +967,22 @@ class TristateVisitor final : public TristateBaseVisitor { // External drivers use VarXRef; local drivers use VarRef. { AstNodeVarRef* const lhsp - = ifaceDottedPath.empty() - ? static_cast( - new AstVarRef{fl, contribOutp, VAccess::WRITE}) - : static_cast( - newVarXRef(fl, contribOutp, ifaceDottedPath, VAccess::WRITE, - ifaceInlinedDots)); + = ifaceDottedPath.empty() ? static_cast( + new AstVarRef{fl, contribOutp, VAccess::WRITE}) + : static_cast( + newVarXRef(fl, contribOutp, ifaceDottedPath, + VAccess::WRITE, ifaceInlinedDots)); AstAssignW* const assp = new AstAssignW{fl, lhsp, orp}; assp->user2Or(U2_BOTH); nodep->addStmtsp(new AstAlways{assp}); } { AstNodeVarRef* const lhsp - = ifaceDottedPath.empty() - ? static_cast( - new AstVarRef{fl, contribEnp, VAccess::WRITE}) - : static_cast( - newVarXRef(fl, contribEnp, ifaceDottedPath, VAccess::WRITE, - ifaceInlinedDots)); + = ifaceDottedPath.empty() ? static_cast( + new AstVarRef{fl, contribEnp, VAccess::WRITE}) + : static_cast( + newVarXRef(fl, contribEnp, ifaceDottedPath, + VAccess::WRITE, ifaceInlinedDots)); AstAssignW* const assp = new AstAssignW{fl, lhsp, enp}; assp->user2Or(U2_BOTH); nodep->addStmtsp(new AstAlways{assp}); @@ -2114,8 +2105,8 @@ class TristateVisitor final : public TristateBaseVisitor { // After all modules have been processed, combine per-module contributions // for each interface tristate signal into final resolution logic. // Key is the canonical AstVar in the interface module (shared across instances). - for (std::pair>>& kv - : m_ifaceContribs) { + for (std::pair>>& kv : + m_ifaceContribs) { AstVar* const invarp = kv.first; std::vector>& contribs = kv.second; AstNodeModule* const ifaceModp = findParentModule(invarp); @@ -2151,8 +2142,8 @@ class TristateVisitor final : public TristateBaseVisitor { } // Assign combined enable - AstAssignW* const enAssp = new AstAssignW{ - fl, new AstVarRef{fl, envarp, VAccess::WRITE}, enp}; + AstAssignW* const enAssp + = new AstAssignW{fl, new AstVarRef{fl, envarp, VAccess::WRITE}, enp}; UINFOTREE(9, enAssp, "", "iface-enAssp"); ifaceModp->addStmtsp(new AstAlways{enAssp}); @@ -2160,8 +2151,7 @@ class TristateVisitor final : public TristateBaseVisitor { const AstPull* const pullp = m_varAux(invarp).pullp; const bool pull1 = pullp && pullp->direction() == 1; // Else default is down - AstNodeExpr* undrivenp - = new AstNot{fl, new AstVarRef{fl, envarp, VAccess::READ}}; + AstNodeExpr* undrivenp = new AstNot{fl, new AstVarRef{fl, envarp, VAccess::READ}}; undrivenp = new AstAnd{fl, undrivenp, newAllZerosOrOnes(invarp, pull1)}; orp = new AstOr{fl, orp, undrivenp}; diff --git a/test_regress/t/t_interface_tristate_hier.v b/test_regress/t/t_interface_tristate_hier.v old mode 100755 new mode 100644 diff --git a/test_regress/t/t_tri_hier_ref_unsup.py b/test_regress/t/t_tri_hier_ref_unsup.py old mode 100644 new mode 100755 diff --git a/test_regress/t/t_tri_iface_eqcase.py b/test_regress/t/t_tri_iface_eqcase.py old mode 100644 new mode 100755 diff --git a/test_regress/t/t_tri_iface_eqcase_modport_bad.py b/test_regress/t/t_tri_iface_eqcase_modport_bad.py old mode 100644 new mode 100755 diff --git a/test_regress/t/t_tri_iface_mixed.py b/test_regress/t/t_tri_iface_mixed.py old mode 100644 new mode 100755 diff --git a/test_regress/t/t_tri_root_ref.py b/test_regress/t/t_tri_root_ref.py old mode 100644 new mode 100755 From 44ccdbf669bbb4fc017a3ff4d8f292fd200722a8 Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 2 Mar 2026 19:07:30 -0500 Subject: [PATCH 4/9] V3Dfg: suppress MULTIDRIVEN for intentional interface tristate lowering while retaining coverage multidrive warnings --- src/V3AstNodeOther.h | 8 ++++ src/V3DfgSynthesize.cpp | 45 +++++++++++++++++++ src/V3Tristate.cpp | 10 +++++ .../t/t_lint_multidriven_coverage_bad.out | 11 +++++ .../t/t_lint_multidriven_coverage_bad.py | 16 +++++++ .../t/t_lint_multidriven_coverage_bad.v | 21 +++++++++ 6 files changed, 111 insertions(+) create mode 100644 test_regress/t/t_lint_multidriven_coverage_bad.out create mode 100644 test_regress/t/t_lint_multidriven_coverage_bad.py create mode 100644 test_regress/t/t_lint_multidriven_coverage_bad.v diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 4c3558ba8..d16abcfba 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1939,6 +1939,8 @@ class AstVar final : public AstNode { bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations) bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing + bool m_dfgTriLowered : 1; // Signal/temporary introduced by tristate lowering + bool m_dfgAllowMultidriveTri : 1; // Allow DFG MULTIDRIVEN warning for intentional tri nets bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8 bool m_isStdRandomizeArg : 1; // Argument variable created for std::randomize (__Varg*) void init() { @@ -1993,6 +1995,8 @@ class AstVar final : public AstNode { m_ignorePostWrite = false; m_ignoreSchedWrite = false; m_dfgMultidriven = false; + m_dfgTriLowered = false; + m_dfgAllowMultidriveTri = false; m_globalConstrained = false; m_isStdRandomizeArg = false; } @@ -2164,6 +2168,10 @@ public: void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; } bool dfgMultidriven() const { return m_dfgMultidriven; } void setDfgMultidriven() { m_dfgMultidriven = true; } + bool dfgTriLowered() const { return m_dfgTriLowered; } + void setDfgTriLowered() { m_dfgTriLowered = true; } + bool dfgAllowMultidriveTri() const { return m_dfgAllowMultidriveTri; } + void setDfgAllowMultidriveTri() { m_dfgAllowMultidriveTri = true; } void globalConstrained(bool flag) { m_globalConstrained = flag; } bool globalConstrained() const { return m_globalConstrained; } bool isStdRandomizeArg() const { return m_isStdRandomizeArg; } diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index 9922e8277..e86e03309 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -662,6 +662,39 @@ class AstToDfgSynthesize final { return drivers; } + // Returns true if the driver cone contains any variable introduced by + // tristate lowering. Used to distinguish intentional tristate contributor + // overlap from accidental multidrive. + static bool containsTriLoweredVar(DfgVertex* rootp) { + std::vector stack; + std::vector visited; + stack.emplace_back(rootp); + while (!stack.empty()) { + const DfgVertex* const vtxp = stack.back(); + stack.pop_back(); + if (std::find(visited.begin(), visited.end(), vtxp) != visited.end()) continue; + visited.emplace_back(vtxp); + if (const DfgVertexVar* const varp = vtxp->cast()) { + AstVar* const astVarp = [&]() -> AstVar* { + if VL_CONSTEXPR_CXX17 (T_Scoped) { + return reinterpret_cast(varp->nodep())->varp(); + } else { + return reinterpret_cast(varp->nodep()); + } + }(); + if (astVarp->dfgTriLowered()) return true; + } + vtxp->foreachSource([&](const DfgVertex& src) { + stack.emplace_back(&src); + return false; + }); + } + return false; + } + + // Returns true if the driver is a direct variable forward (no logic). + static bool isDirectVarDriver(const DfgVertex* vtxp) { return vtxp->is(); } + // Gather all synthesized drivers of an unresolved variable static std::vector gatherDriversUnresolved(DfgUnresolved* vtxp) { std::vector drivers; @@ -822,6 +855,18 @@ class AstToDfgSynthesize final { // Loop index often abused, so suppress if (getAstVar(varp)->isUsedLoopIdx()) continue; + // Tristate lowering can intentionally create overlapping contributors. + // Keep the signal marked multidriven for DFG fallback, but suppress + // warning only when both overlapping driver cones look tri-lowered. + if (getAstVar(varp)->dfgAllowMultidriveTri()) { + const bool iTri = containsTriLoweredVar(iD.m_vtxp); + const bool jTri = containsTriLoweredVar(jD.m_vtxp); + const bool triPair = iTri && jTri; + const bool triAndBridge + = (iTri && isDirectVarDriver(jD.m_vtxp)) + || (jTri && isDirectVarDriver(iD.m_vtxp)); + if (triPair || triAndBridge) continue; + } // Warn the user now const std::string lo = std::to_string(jD.m_lo); diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 9596c483f..981b5ccfd 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -776,6 +776,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstVar* const newLhsp = new AstVar{varp->fileline(), VVarType::MODULETEMP, varp->name() + "__out" + cvtToStr(m_unique), varp}; // 2-state ok; sep enable + newLhsp->setDfgTriLowered(); UINFO(9, " newout " << newLhsp); nodep->addStmtsp(newLhsp); // When retargeting a VarXRef to a local __out var, the dotted path @@ -797,6 +798,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstVar* const newEnLhsp = new AstVar{varp->fileline(), VVarType::MODULETEMP, varp->name() + "__en" + cvtToStr(m_unique++), envarp}; // 2-state ok + newEnLhsp->setDfgTriLowered(); UINFO(9, " newenlhsp " << newEnLhsp); nodep->addStmtsp(newEnLhsp); @@ -903,12 +905,14 @@ class TristateVisitor final : public TristateBaseVisitor { // var__strength variable AstVar* const varStrengthp = new AstVar{fl, VVarType::MODULETEMP, strengthVarName, invarp}; // 2-state ok; sep enable; + varStrengthp->setDfgTriLowered(); UINFO(9, " newstrength " << varStrengthp); nodep->addStmtsp(varStrengthp); // var__strength__en variable AstVar* const enVarStrengthp = new AstVar{ fl, VVarType::MODULETEMP, strengthVarName + "__en", invarp}; // 2-state ok; + enVarStrengthp->setDfgTriLowered(); UINFO(9, " newenstrength " << enVarStrengthp); nodep->addStmtsp(enVarStrengthp); @@ -938,6 +942,9 @@ class TristateVisitor final : public TristateBaseVisitor { // The interface module owns the contribution vars; resolution happens in // combineIfaceContribs() after all modules are processed. if (isIfaceTri) { + // This net intentionally has multiple contributors; DFG should not emit + // MULTIDRIVEN warnings for this lowered tristate pattern. + invarp->setDfgAllowMultidriveTri(); AstNodeModule* const ifaceModp = findParentModule(invarp); UASSERT_OBJ(ifaceModp, invarp, "Interface tristate var has no parent module"); const int contribIdx = static_cast(m_ifaceContribs[invarp].size()); @@ -946,11 +953,13 @@ class TristateVisitor final : public TristateBaseVisitor { // Create contribution vars in the interface module AstVar* const contribOutp = new AstVar{ fl, VVarType::MODULETEMP, invarp->name() + "__out" + cvtToStr(contribIdx), invarp}; + contribOutp->setDfgTriLowered(); UINFO(9, " iface contribOut " << contribOutp); ifaceModp->addStmtsp(contribOutp); AstVar* const contribEnp = new AstVar{ fl, VVarType::MODULETEMP, invarp->name() + "__en" + cvtToStr(contribIdx), invarp}; + contribEnp->setDfgTriLowered(); UINFO(9, " iface contribEn " << contribEnp); ifaceModp->addStmtsp(contribEnp); @@ -2137,6 +2146,7 @@ class TristateVisitor final : public TristateBaseVisitor { AstVar* envarp = VN_CAST(invarp->user1p(), Var); if (!envarp) { envarp = new AstVar{fl, VVarType::MODULETEMP, invarp->name() + "__en", invarp}; + envarp->setDfgTriLowered(); ifaceModp->addStmtsp(envarp); invarp->user1p(envarp); } diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.out b/test_regress/t/t_lint_multidriven_coverage_bad.out new file mode 100644 index 000000000..44f74c107 --- /dev/null +++ b/test_regress/t/t_lint_multidriven_coverage_bad.out @@ -0,0 +1,11 @@ +%Warning-MULTIDRIVEN: t/t_lint_multidriven_coverage_bad.v:13:16: Bit [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation. + : ... note: In instance 't' + t/t_lint_multidriven_coverage_bad.v:15:16: ... Location of offending driver + 15 | assign w[0] = a; + | ^ + t/t_lint_multidriven_coverage_bad.v:16:16: ... Location of offending driver + 16 | assign w[0] = b; + | ^ + ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest + ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.py b/test_regress/t/t_lint_multidriven_coverage_bad.py new file mode 100644 index 000000000..dceacd1b0 --- /dev/null +++ b/test_regress/t/t_lint_multidriven_coverage_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(verilator_flags2=["--coverage"], fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.v b/test_regress/t/t_lint_multidriven_coverage_bad.v new file mode 100644 index 000000000..a3b55d9f4 --- /dev/null +++ b/test_regress/t/t_lint_multidriven_coverage_bad.v @@ -0,0 +1,21 @@ +// 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 + +module t ( + input logic a, + input logic b, + output logic [1:0] y +); + + logic [1:0] w; + + assign w[0] = a; // <--- Warning + assign w[0] = b; // <--- Warning + assign w[1] = 1'b0; + + assign y = w; + +endmodule From 1b414fcc262a97980e42a27f5c848715eaae087c Mon Sep 17 00:00:00 2001 From: github action Date: Tue, 3 Mar 2026 00:08:46 +0000 Subject: [PATCH 5/9] Apply 'make format' --- src/V3DfgSynthesize.cpp | 5 ++--- test_regress/t/t_lint_multidriven_coverage_bad.py | 0 2 files changed, 2 insertions(+), 3 deletions(-) mode change 100644 => 100755 test_regress/t/t_lint_multidriven_coverage_bad.py diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index e86e03309..643bb7141 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -862,9 +862,8 @@ class AstToDfgSynthesize final { const bool iTri = containsTriLoweredVar(iD.m_vtxp); const bool jTri = containsTriLoweredVar(jD.m_vtxp); const bool triPair = iTri && jTri; - const bool triAndBridge - = (iTri && isDirectVarDriver(jD.m_vtxp)) - || (jTri && isDirectVarDriver(iD.m_vtxp)); + const bool triAndBridge = (iTri && isDirectVarDriver(jD.m_vtxp)) + || (jTri && isDirectVarDriver(iD.m_vtxp)); if (triPair || triAndBridge) continue; } diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.py b/test_regress/t/t_lint_multidriven_coverage_bad.py old mode 100644 new mode 100755 From 61f21f22fcfb9c15c8d720fc1f91e039beb8fe53 Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 2 Mar 2026 21:01:36 -0500 Subject: [PATCH 6/9] fix struct final, fix formatting --- src/V3Tristate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/V3Tristate.cpp b/src/V3Tristate.cpp index 981b5ccfd..4516beaa9 100644 --- a/src/V3Tristate.cpp +++ b/src/V3Tristate.cpp @@ -725,7 +725,7 @@ class TristateVisitor final : public TristateBaseVisitor { // (different VarXRef dotted paths) must be processed separately. // E.g. io_ifc.d and io_ifc_local.d both target the same AstVar d in // the ifc interface, but each instance needs its own contribution slot. - struct PartitionInfo { + struct PartitionInfo final { RefStrengthVec refs; string inlinedDots; }; @@ -745,7 +745,7 @@ class TristateVisitor final : public TristateBaseVisitor { findModportForDotted(nodep, kv.first)); } } else if (VN_IS(nodep, Iface)) { - // Local driver in an interface module — use contribution mechanism + // Local driver in an interface module - use contribution mechanism // so it can be combined with any external drivers later insertTristatesSignal(nodep, invarp, refsp, true, "", "", nullptr); } else { From 3675c9e50ec7a39c337434e7adee93a51e21e47c Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 2 Mar 2026 21:31:11 -0500 Subject: [PATCH 7/9] combine tests, fix formatting --- test_regress/t/t_interface_modport.v | 201 +++++----- test_regress/t/t_interface_tristate_hier.v | 374 +++++++++--------- .../t/t_lint_multidriven_coverage_bad.out | 14 +- .../t/t_lint_multidriven_coverage_bad.v | 16 +- test_regress/t/t_tri_hier_ref_unsup.v | 76 ++-- test_regress/t/t_tri_iface_eqcase.v | 158 -------- .../t/t_tri_iface_eqcase_modport_bad.v | 20 +- test_regress/t/t_tri_iface_mixed.py | 18 - test_regress/t/t_tri_iface_mixed.v | 136 ------- ...ace_eqcase.py => t_tri_iface_semantics.py} | 0 test_regress/t/t_tri_iface_semantics.v | 231 +++++++++++ test_regress/t/t_tri_root_ref.v | 48 +-- 12 files changed, 605 insertions(+), 687 deletions(-) delete mode 100644 test_regress/t/t_tri_iface_eqcase.v delete mode 100755 test_regress/t/t_tri_iface_mixed.py delete mode 100644 test_regress/t/t_tri_iface_mixed.v rename test_regress/t/{t_tri_iface_eqcase.py => t_tri_iface_semantics.py} (100%) create mode 100644 test_regress/t/t_tri_iface_semantics.v diff --git a/test_regress/t/t_interface_modport.v b/test_regress/t/t_interface_modport.v index a49d6a172..f9e480566 100644 --- a/test_regress/t/t_interface_modport.v +++ b/test_regress/t/t_interface_modport.v @@ -5,112 +5,111 @@ // SPDX-License-Identifier: CC0-1.0 interface counter_if; - logic [3:0] value; - logic reset; - modport counter_mp (input reset, output value); - modport core_mp (output reset, input value); + logic [3:0] value; + logic reset; + modport counter_mp (input reset, output value); + modport core_mp (output reset, input value); endinterface // Check can have inst module before top module -module counter_ansi - ( - input clkm, - counter_if c_data, - input logic [3:0] i_value - ); +module counter_ansi ( + input clkm, + counter_if c_data, + input logic [3:0] i_value +); - always @ (posedge clkm) begin - c_data.value <= c_data.reset ? i_value : c_data.value + 1; - end + always @ (posedge clkm) begin + c_data.value <= c_data.reset ? i_value : c_data.value + 1; + end endmodule : counter_ansi // Issue 3466: inout inside interface modport interface inout_if; - wire we; - wire d; - modport ctrl (output we, inout d); - modport prph (input we, inout d); + wire we; + wire d; + modport ctrl (output we, inout d); + modport prph (input we, inout d); endinterface module inout_mod(inout_if.prph io_if); - assign io_if.d = io_if.we ? 1'b1 : 1'bz; + assign io_if.d = io_if.we ? 1'b1 : 1'bz; endmodule module inout_mod_wrap(input we, inout d); - inout_if io_if(); - assign io_if.we = we; - assign io_if.d = d; - inout_mod prph (.*); + inout_if io_if(); + assign io_if.we = we; + assign io_if.d = d; + inout_mod prph (.*); endmodule module t (/*AUTOARG*/ - // Inputs - clk - ); + // Inputs + clk + ); - input clk; - integer cyc=1; + input clk; + integer cyc=1; - counter_if c1_data(); - counter_if c2_data(); - counter_if c3_data(); - counter_if c4_data(); + counter_if c1_data(); + counter_if c2_data(); + counter_if c3_data(); + counter_if c4_data(); - counter_ansi c1 (.clkm(clk), - .c_data(c1_data.counter_mp), - .i_value(4'h1)); + counter_ansi c1 (.clkm(clk), + .c_data(c1_data.counter_mp), + .i_value(4'h1)); `ifdef VERILATOR counter_ansi `else counter_nansi `endif - /**/ c2 (.clkm(clk), - .c_data(c2_data.counter_mp), - .i_value(4'h2)); - counter_ansi_m c3 (.clkm(clk), - .c_data(c3_data), - .i_value(4'h3)); + /**/ c2 (.clkm(clk), + .c_data(c2_data.counter_mp), + .i_value(4'h2)); + counter_ansi_m c3 (.clkm(clk), + .c_data(c3_data), + .i_value(4'h3)); `ifdef VERILATOR counter_ansi_m `else counter_nansi_m `endif - /**/ c4 (.clkm(clk), - .c_data(c4_data), - .i_value(4'h4)); + /**/ c4 (.clkm(clk), + .c_data(c4_data), + .i_value(4'h4)); - logic inout_we; - tri inout_d; - inout_mod_wrap inout_u (.we(inout_we), .d(inout_d)); + logic inout_we; + tri inout_d; + inout_mod_wrap inout_u (.we(inout_we), .d(inout_d)); - initial begin - c1_data.value = 4'h4; - c2_data.value = 4'h5; - c3_data.value = 4'h6; - c4_data.value = 4'h7; - inout_we = 1'b0; - end + initial begin + c1_data.value = 4'h4; + c2_data.value = 4'h5; + c3_data.value = 4'h6; + c4_data.value = 4'h7; + inout_we = 1'b0; + end - always @ (posedge clk) begin - cyc <= cyc + 1; - if (cyc<2) begin - c1_data.reset <= 1; - c2_data.reset <= 1; - c3_data.reset <= 1; - c4_data.reset <= 1; - end - if (cyc==2) begin - c1_data.reset <= 0; - c2_data.reset <= 0; - c3_data.reset <= 0; - c4_data.reset <= 0; - end - if (cyc==20) begin - $write("[%0t] cyc%0d: c1 %0x %0x c2 %0x %0x c3 %0x %0x c4 %0x %0x\n", $time, cyc, - c1_data.value, c1_data.reset, - c2_data.value, c2_data.reset, - c3_data.value, c3_data.reset, - c4_data.value, c4_data.reset); - if (c1_data.value != 2) $stop; - if (c2_data.value != 3) $stop; - if (c3_data.value != 4) $stop; - if (c4_data.value != 5) $stop; - $write("*-* All Finished *-*\n"); - $finish; - end - end + always @ (posedge clk) begin + cyc <= cyc + 1; + if (cyc<2) begin + c1_data.reset <= 1; + c2_data.reset <= 1; + c3_data.reset <= 1; + c4_data.reset <= 1; + end + if (cyc==2) begin + c1_data.reset <= 0; + c2_data.reset <= 0; + c3_data.reset <= 0; + c4_data.reset <= 0; + end + if (cyc==20) begin + $write("[%0t] cyc%0d: c1 %0x %0x c2 %0x %0x c3 %0x %0x c4 %0x %0x\n", $time, cyc, + c1_data.value, c1_data.reset, + c2_data.value, c2_data.reset, + c3_data.value, c3_data.reset, + c4_data.value, c4_data.reset); + if (c1_data.value != 2) $stop; + if (c2_data.value != 3) $stop; + if (c3_data.value != 4) $stop; + if (c4_data.value != 5) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end endmodule `ifndef VERILATOR @@ -118,26 +117,26 @@ endmodule module counter_nansi (clkm, c_data, i_value); - input clkm; - counter_if c_data; - input logic [3:0] i_value; + input clkm; + counter_if c_data; + input logic [3:0] i_value; - always @ (posedge clkm) begin - c_data.value <= c_data.reset ? i_value : c_data.value + 1; - end + always @ (posedge clkm) begin + c_data.value <= c_data.reset ? i_value : c_data.value + 1; + end endmodule : counter_nansi `endif module counter_ansi_m - ( - input clkm, - counter_if.counter_mp c_data, - input logic [3:0] i_value - ); + ( + input clkm, + counter_if.counter_mp c_data, + input logic [3:0] i_value + ); - always @ (posedge clkm) begin - c_data.value <= c_data.reset ? i_value : c_data.value + 1; - end + always @ (posedge clkm) begin + c_data.value <= c_data.reset ? i_value : c_data.value + 1; + end endmodule : counter_ansi_m `ifndef VERILATOR @@ -145,12 +144,12 @@ endmodule : counter_ansi_m module counter_nansi_m (clkm, c_data, i_value); - input clkm; - counter_if.counter_mp c_data; - input logic [3:0] i_value; + input clkm; + counter_if.counter_mp c_data; + input logic [3:0] i_value; - always @ (posedge clkm) begin - c_data.value <= c_data.reset ? i_value : c_data.value + 1; - end + always @ (posedge clkm) begin + c_data.value <= c_data.reset ? i_value : c_data.value + 1; + end endmodule : counter_nansi_m `endif diff --git a/test_regress/t/t_interface_tristate_hier.v b/test_regress/t/t_interface_tristate_hier.v index ba0c88ed8..f159b86d0 100644 --- a/test_regress/t/t_interface_tristate_hier.v +++ b/test_regress/t/t_interface_tristate_hier.v @@ -14,245 +14,245 @@ // verilator lint_off MULTIDRIVEN interface ifc; - wire we0, we2; - tri [15:0] d; + wire we0, we2; + tri [15:0] d; endinterface interface ifc_multi; - wire we0, wea, web; - tri [15:0] d; + wire we0, wea, web; + tri [15:0] d; endinterface module bot ( - ifc io_ifc + ifc io_ifc ); - assign io_ifc.d = io_ifc.we2 ? 16'hd2 : 16'hzzzz; + assign io_ifc.d = io_ifc.we2 ? 16'hd2 : 16'hzzzz; endmodule module passthru ( - ifc io_ifc + ifc io_ifc ); - bot u_bot (.*); + bot u_bot (.*); endmodule module bot_a ( - ifc_multi io_ifc + ifc_multi io_ifc ); - assign io_ifc.d = io_ifc.wea ? 16'hd2 : 16'hzzzz; + assign io_ifc.d = io_ifc.wea ? 16'hd2 : 16'hzzzz; endmodule module bot_b ( - ifc_multi io_ifc + ifc_multi io_ifc ); - assign io_ifc.d = io_ifc.web ? 16'hd2 : 16'hzzzz; + assign io_ifc.d = io_ifc.web ? 16'hd2 : 16'hzzzz; endmodule module passthru_multi_c ( - ifc_multi io_ifc + ifc_multi io_ifc ); - bot_a u_bot_a (.*); - bot_b u_bot_b (.*); + bot_a u_bot_a (.*); + bot_b u_bot_b (.*); endmodule module passthru_deep ( - ifc io_ifc + ifc io_ifc ); - passthru u_inner (.*); + passthru u_inner (.*); endmodule module t; - ifc io_ifc (); - ifc io_ifc_local (); - ifc io_ifc_b0 (); - ifc io_ifc_b1 (); - ifc_multi io_ifc_mc (); - ifc io_arr [1:0](); - ifc io_ifc_deep (); + ifc io_ifc (); + ifc io_ifc_local (); + ifc io_ifc_b0 (); + ifc io_ifc_b1 (); + ifc_multi io_ifc_mc (); + ifc io_arr [1:0](); + ifc io_ifc_deep (); - // Test top assignment - assign io_ifc.d = io_ifc.we0 ? 16'hd0 : 16'hzzzz; - assign io_ifc_local.d = io_ifc_local.we0 ? 16'hd0 : 16'hzzzz; - assign io_ifc_mc.d = io_ifc_mc.we0 ? 16'hd0 : 16'hzzzz; + // Test top assignment + assign io_ifc.d = io_ifc.we0 ? 16'hd0 : 16'hzzzz; + assign io_ifc_local.d = io_ifc_local.we0 ? 16'hd0 : 16'hzzzz; + assign io_ifc_mc.d = io_ifc_mc.we0 ? 16'hd0 : 16'hzzzz; - logic we0, we2; - logic we2_b0, we2_b1; - logic we0_mc, wea_mc, web_mc; - assign io_ifc.we0 = we0; - assign io_ifc.we2 = we2; - assign io_ifc_local.we0 = we0; - assign io_ifc_local.we2 = 1'b0; - assign io_ifc_b0.we0 = 1'b0; - assign io_ifc_b0.we2 = we2_b0; - assign io_ifc_b1.we0 = 1'b0; - assign io_ifc_b1.we2 = we2_b1; - assign io_ifc_mc.we0 = we0_mc; - assign io_ifc_mc.wea = wea_mc; - assign io_ifc_mc.web = web_mc; + logic we0, we2; + logic we2_b0, we2_b1; + logic we0_mc, wea_mc, web_mc; + assign io_ifc.we0 = we0; + assign io_ifc.we2 = we2; + assign io_ifc_local.we0 = we0; + assign io_ifc_local.we2 = 1'b0; + assign io_ifc_b0.we0 = 1'b0; + assign io_ifc_b0.we2 = we2_b0; + assign io_ifc_b1.we0 = 1'b0; + assign io_ifc_b1.we2 = we2_b1; + assign io_ifc_mc.we0 = we0_mc; + assign io_ifc_mc.wea = wea_mc; + assign io_ifc_mc.web = web_mc; - // Interface array signals - logic we2_arr0, we2_arr1; - assign io_arr[0].we0 = 1'b0; - assign io_arr[0].we2 = we2_arr0; - assign io_arr[1].we0 = 1'b0; - assign io_arr[1].we2 = we2_arr1; + // Interface array signals + logic we2_arr0, we2_arr1; + assign io_arr[0].we0 = 1'b0; + assign io_arr[0].we2 = we2_arr0; + assign io_arr[1].we0 = 1'b0; + assign io_arr[1].we2 = we2_arr1; - // Deep nesting signals - logic we0_deep, we2_deep; - assign io_ifc_deep.we0 = we0_deep; - assign io_ifc_deep.we2 = we2_deep; - assign io_ifc_deep.d = io_ifc_deep.we0 ? 16'hd0 : 16'hzzzz; + // Deep nesting signals + logic we0_deep, we2_deep; + assign io_ifc_deep.we0 = we0_deep; + assign io_ifc_deep.we2 = we2_deep; + assign io_ifc_deep.d = io_ifc_deep.we0 ? 16'hd0 : 16'hzzzz; - passthru u_passthru (.*); - passthru u_passthru_b0 (.io_ifc(io_ifc_b0)); - passthru u_passthru_b1 (.io_ifc(io_ifc_b1)); - passthru_multi_c u_passthru_mc (.io_ifc(io_ifc_mc)); - passthru u_passthru_arr0 (.io_ifc(io_arr[0])); - passthru u_passthru_arr1 (.io_ifc(io_arr[1])); - passthru_deep u_passthru_deep (.io_ifc(io_ifc_deep)); + passthru u_passthru (.*); + passthru u_passthru_b0 (.io_ifc(io_ifc_b0)); + passthru u_passthru_b1 (.io_ifc(io_ifc_b1)); + passthru_multi_c u_passthru_mc (.io_ifc(io_ifc_mc)); + passthru u_passthru_arr0 (.io_ifc(io_arr[0])); + passthru u_passthru_arr1 (.io_ifc(io_arr[1])); + passthru_deep u_passthru_deep (.io_ifc(io_ifc_deep)); - initial begin - #1; - we0 = 1'b0; - we2 = 1'b0; - we2_b0 = 1'b0; - we2_b1 = 1'b0; - we0_mc = 1'b0; - wea_mc = 1'b0; - web_mc = 1'b0; - we2_arr0 = 1'b0; - we2_arr1 = 1'b0; - we0_deep = 1'b0; - we2_deep = 1'b0; - #1; - `checkh(io_ifc.d, 16'hzzzz); - `checkh(io_ifc_local.d, 16'hzzzz); - `checkh(io_ifc_b0.d, 16'hzzzz); - `checkh(io_ifc_b1.d, 16'hzzzz); - `checkh(io_ifc_mc.d, 16'hzzzz); - `checkh(io_arr[0].d, 16'hzzzz); - `checkh(io_arr[1].d, 16'hzzzz); - `checkh(io_ifc_deep.d, 16'hzzzz); + initial begin + #1; + we0 = 1'b0; + we2 = 1'b0; + we2_b0 = 1'b0; + we2_b1 = 1'b0; + we0_mc = 1'b0; + wea_mc = 1'b0; + web_mc = 1'b0; + we2_arr0 = 1'b0; + we2_arr1 = 1'b0; + we0_deep = 1'b0; + we2_deep = 1'b0; + #1; + `checkh(io_ifc.d, 16'hzzzz); + `checkh(io_ifc_local.d, 16'hzzzz); + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hzzzz); + `checkh(io_ifc_mc.d, 16'hzzzz); + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hzzzz); + `checkh(io_ifc_deep.d, 16'hzzzz); - #1; - we0 = 1'b1; - we2 = 1'b0; - #1; - `checkh(io_ifc.d, 16'hd0); - `checkh(io_ifc_local.d, 16'hd0); + #1; + we0 = 1'b1; + we2 = 1'b0; + #1; + `checkh(io_ifc.d, 16'hd0); + `checkh(io_ifc_local.d, 16'hd0); - #1; - we0 = 1'b0; - we2 = 1'b0; - #1; - `checkh(io_ifc.d, 16'hzzzz); - `checkh(io_ifc_local.d, 16'hzzzz); + #1; + we0 = 1'b0; + we2 = 1'b0; + #1; + `checkh(io_ifc.d, 16'hzzzz); + `checkh(io_ifc_local.d, 16'hzzzz); - #1; - we0 = 1'b0; - we2 = 1'b1; - #1; - `checkh(io_ifc.d, 16'hd2); - `checkh(io_ifc_local.d, 16'hzzzz); + #1; + we0 = 1'b0; + we2 = 1'b1; + #1; + `checkh(io_ifc.d, 16'hd2); + `checkh(io_ifc_local.d, 16'hzzzz); - // Interface passed a->b, where b is instantiated multiple times (separate interfaces) - #1; - we2_b0 = 1'b1; - we2_b1 = 1'b0; - #1; - `checkh(io_ifc_b0.d, 16'hd2); - `checkh(io_ifc_b1.d, 16'hzzzz); + // Interface passed a->b, where b is instantiated multiple times (separate interfaces) + #1; + we2_b0 = 1'b1; + we2_b1 = 1'b0; + #1; + `checkh(io_ifc_b0.d, 16'hd2); + `checkh(io_ifc_b1.d, 16'hzzzz); - #1; - we2_b0 = 1'b0; - we2_b1 = 1'b1; - #1; - `checkh(io_ifc_b0.d, 16'hzzzz); - `checkh(io_ifc_b1.d, 16'hd2); + #1; + we2_b0 = 1'b0; + we2_b1 = 1'b1; + #1; + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hd2); - #1; - we2_b0 = 1'b0; - we2_b1 = 1'b0; - #1; - `checkh(io_ifc_b0.d, 16'hzzzz); - `checkh(io_ifc_b1.d, 16'hzzzz); + #1; + we2_b0 = 1'b0; + we2_b1 = 1'b0; + #1; + `checkh(io_ifc_b0.d, 16'hzzzz); + `checkh(io_ifc_b1.d, 16'hzzzz); - // Interface passed a->b, where c is instantiated multiple times (same interface) - #1; - we0_mc = 1'b1; - wea_mc = 1'b0; - web_mc = 1'b0; - #1; - `checkh(io_ifc_mc.d, 16'hd0); + // Interface passed a->b, where c is instantiated multiple times (same interface) + #1; + we0_mc = 1'b1; + wea_mc = 1'b0; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hd0); - #1; - we0_mc = 1'b0; - wea_mc = 1'b1; - web_mc = 1'b0; - #1; - `checkh(io_ifc_mc.d, 16'hd2); + #1; + we0_mc = 1'b0; + wea_mc = 1'b1; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hd2); - #1; - we0_mc = 1'b0; - wea_mc = 1'b0; - web_mc = 1'b1; - #1; - `checkh(io_ifc_mc.d, 16'hd2); + #1; + we0_mc = 1'b0; + wea_mc = 1'b0; + web_mc = 1'b1; + #1; + `checkh(io_ifc_mc.d, 16'hd2); - #1; - wea_mc = 1'b1; - web_mc = 1'b1; - #1; - `checkh(io_ifc_mc.d, 16'hd2); + #1; + wea_mc = 1'b1; + web_mc = 1'b1; + #1; + `checkh(io_ifc_mc.d, 16'hd2); - #1; - wea_mc = 1'b0; - web_mc = 1'b0; - #1; - `checkh(io_ifc_mc.d, 16'hzzzz); + #1; + wea_mc = 1'b0; + web_mc = 1'b0; + #1; + `checkh(io_ifc_mc.d, 16'hzzzz); - // Interface array: each element is independent - #1; - we2_arr0 = 1'b1; - we2_arr1 = 1'b0; - #1; - `checkh(io_arr[0].d, 16'hd2); - `checkh(io_arr[1].d, 16'hzzzz); + // Interface array: each element is independent + #1; + we2_arr0 = 1'b1; + we2_arr1 = 1'b0; + #1; + `checkh(io_arr[0].d, 16'hd2); + `checkh(io_arr[1].d, 16'hzzzz); - #1; - we2_arr0 = 1'b0; - we2_arr1 = 1'b1; - #1; - `checkh(io_arr[0].d, 16'hzzzz); - `checkh(io_arr[1].d, 16'hd2); + #1; + we2_arr0 = 1'b0; + we2_arr1 = 1'b1; + #1; + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hd2); - #1; - we2_arr0 = 1'b0; - we2_arr1 = 1'b0; - #1; - `checkh(io_arr[0].d, 16'hzzzz); - `checkh(io_arr[1].d, 16'hzzzz); + #1; + we2_arr0 = 1'b0; + we2_arr1 = 1'b0; + #1; + `checkh(io_arr[0].d, 16'hzzzz); + `checkh(io_arr[1].d, 16'hzzzz); - // Deep nesting: passthru_deep -> passthru -> bot (3 levels) - #1; - we0_deep = 1'b1; - we2_deep = 1'b0; - #1; - `checkh(io_ifc_deep.d, 16'hd0); + // Deep nesting: passthru_deep -> passthru -> bot (3 levels) + #1; + we0_deep = 1'b1; + we2_deep = 1'b0; + #1; + `checkh(io_ifc_deep.d, 16'hd0); - #1; - we0_deep = 1'b0; - we2_deep = 1'b1; - #1; - `checkh(io_ifc_deep.d, 16'hd2); + #1; + we0_deep = 1'b0; + we2_deep = 1'b1; + #1; + `checkh(io_ifc_deep.d, 16'hd2); - #1; - we0_deep = 1'b0; - we2_deep = 1'b0; - #1; - `checkh(io_ifc_deep.d, 16'hzzzz); + #1; + we0_deep = 1'b0; + we2_deep = 1'b0; + #1; + `checkh(io_ifc_deep.d, 16'hzzzz); - #1; - $write("*-* All Finished *-*\n"); - $finish; - end + #1; + $write("*-* All Finished *-*\n"); + $finish; + end endmodule diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.out b/test_regress/t/t_lint_multidriven_coverage_bad.out index 44f74c107..7e06c4ffb 100644 --- a/test_regress/t/t_lint_multidriven_coverage_bad.out +++ b/test_regress/t/t_lint_multidriven_coverage_bad.out @@ -1,11 +1,11 @@ -%Warning-MULTIDRIVEN: t/t_lint_multidriven_coverage_bad.v:13:16: Bit [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation. +%Warning-MULTIDRIVEN: t/t_lint_multidriven_coverage_bad.v:13:15: Bit [0] of signal 'w' have multiple combinational drivers. This can cause performance degradation. : ... note: In instance 't' - t/t_lint_multidriven_coverage_bad.v:15:16: ... Location of offending driver - 15 | assign w[0] = a; - | ^ - t/t_lint_multidriven_coverage_bad.v:16:16: ... Location of offending driver - 16 | assign w[0] = b; - | ^ + t/t_lint_multidriven_coverage_bad.v:15:15: ... Location of offending driver + 15 | assign w[0] = a; + | ^ + t/t_lint_multidriven_coverage_bad.v:16:15: ... Location of offending driver + 16 | assign w[0] = b; + | ^ ... For warning description see https://verilator.org/warn/MULTIDRIVEN?v=latest ... Use "/* verilator lint_off MULTIDRIVEN */" and lint_on around source to disable this message. %Error: Exiting due to diff --git a/test_regress/t/t_lint_multidriven_coverage_bad.v b/test_regress/t/t_lint_multidriven_coverage_bad.v index a3b55d9f4..4ea05186e 100644 --- a/test_regress/t/t_lint_multidriven_coverage_bad.v +++ b/test_regress/t/t_lint_multidriven_coverage_bad.v @@ -5,17 +5,17 @@ // SPDX-License-Identifier: CC0-1.0 module t ( - input logic a, - input logic b, - output logic [1:0] y + input logic a, + input logic b, + output logic [1:0] y ); - logic [1:0] w; + logic [1:0] w; - assign w[0] = a; // <--- Warning - assign w[0] = b; // <--- Warning - assign w[1] = 1'b0; + assign w[0] = a; // <--- Warning + assign w[0] = b; // <--- Warning + assign w[1] = 1'b0; - assign y = w; + assign y = w; endmodule diff --git a/test_regress/t/t_tri_hier_ref_unsup.v b/test_regress/t/t_tri_hier_ref_unsup.v index 3ad2edec6..e8cd1165a 100644 --- a/test_regress/t/t_tri_hier_ref_unsup.v +++ b/test_regress/t/t_tri_hier_ref_unsup.v @@ -18,52 +18,52 @@ // verilator lint_off MULTIDRIVEN module sub_with_tri ( - input we_internal + input we_internal ); - tri [7:0] bus; - // Internal driver: drives 8'hAA when we_internal is high - assign bus = we_internal ? 8'hAA : 8'hzz; + tri [7:0] bus; + // Internal driver: drives 8'hAA when we_internal is high + assign bus = we_internal ? 8'hAA : 8'hzz; endmodule module t; - logic sub_we_internal; - sub_with_tri u_sub(.we_internal(sub_we_internal)); + logic sub_we_internal; + sub_with_tri u_sub(.we_internal(sub_we_internal)); - logic hier_we; - // Drive u_sub.bus hierarchically from this module - assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; + logic hier_we; + // Drive u_sub.bus hierarchically from this module + assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; - initial begin - // All drivers off -> high-Z - #1; - hier_we = 1'b0; - sub_we_internal = 1'b0; - #1; - `checkh(u_sub.bus, 8'hzz); + initial begin + // All drivers off -> high-Z + #1; + hier_we = 1'b0; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hzz); - // External hierarchical driver on - #1; - hier_we = 1'b1; - sub_we_internal = 1'b0; - #1; - `checkh(u_sub.bus, 8'hBB); + // External hierarchical driver on + #1; + hier_we = 1'b1; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hBB); - // Internal driver on, external off - #1; - hier_we = 1'b0; - sub_we_internal = 1'b1; - #1; - `checkh(u_sub.bus, 8'hAA); + // Internal driver on, external off + #1; + hier_we = 1'b0; + sub_we_internal = 1'b1; + #1; + `checkh(u_sub.bus, 8'hAA); - // Both off again - #1; - hier_we = 1'b0; - sub_we_internal = 1'b0; - #1; - `checkh(u_sub.bus, 8'hzz); + // Both off again + #1; + hier_we = 1'b0; + sub_we_internal = 1'b0; + #1; + `checkh(u_sub.bus, 8'hzz); - #1; - $write("*-* All Finished *-*\n"); - $finish; - end + #1; + $write("*-* All Finished *-*\n"); + $finish; + end endmodule diff --git a/test_regress/t/t_tri_iface_eqcase.v b/test_regress/t/t_tri_iface_eqcase.v deleted file mode 100644 index 64dd23486..000000000 --- a/test_regress/t/t_tri_iface_eqcase.v +++ /dev/null @@ -1,158 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// Test === and !== on interface tristate signals accessed via VarXRef. -// Exercises the getEnExprBasedOnOriginalp() VarXRef path added for #3466. -// -// 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 - -// 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 - -// verilator lint_off MULTIDRIVEN - -// Simple interface with one external driver -interface ifc; - logic we; - tri [7:0] d; -endinterface - -module drv ( - ifc io_ifc -); - assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; -endmodule - -// Module that compares interface tri signal via VarXRef (cross-module === / !==) -module chk ( - ifc io_ifc, - output logic is_z, - output logic is_5a, - output logic not_z, - output logic not_5a -); - assign is_z = (io_ifc.d === 8'hzz); - assign is_5a = (io_ifc.d === 8'h5A); - assign not_z = (io_ifc.d !== 8'hzz); - assign not_5a = (io_ifc.d !== 8'h5A); -endmodule - -// Interface with modport -interface ifc_mp; - logic we; - tri [7:0] d; - modport drv_mp (input we, inout d); - modport chk_mp (input we, inout d); -endinterface - -module drv_mp ( - ifc_mp.drv_mp io_ifc -); - assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; -endmodule - -module chk_mp ( - ifc_mp.chk_mp io_ifc, - output logic is_z, - output logic is_5a, - output logic not_z, - output logic not_5a -); - assign is_z = (io_ifc.d === 8'hzz); - assign is_5a = (io_ifc.d === 8'h5A); - assign not_z = (io_ifc.d !== 8'hzz); - assign not_5a = (io_ifc.d !== 8'h5A); -endmodule - -// Deep hierarchy: passthru -> drv -module passthru ( - ifc io_ifc -); - drv u_drv (.*); -endmodule - -module t; - // ---- Basic (no modport) ---- - ifc i (); - logic is_z, is_5a, not_z, not_5a; - drv u_drv (.io_ifc(i)); - chk u_chk (.io_ifc(i), .is_z(is_z), .is_5a(is_5a), - .not_z(not_z), .not_5a(not_5a)); - - // ---- Modport ---- - ifc_mp i_mp (); - logic mp_is_z, mp_is_5a, mp_not_z, mp_not_5a; - drv_mp u_drv_mp (.io_ifc(i_mp)); - chk_mp u_chk_mp (.io_ifc(i_mp), .is_z(mp_is_z), .is_5a(mp_is_5a), - .not_z(mp_not_z), .not_5a(mp_not_5a)); - - // ---- Deep hierarchy ---- - ifc i_deep (); - logic deep_is_z, deep_is_5a, deep_not_z, deep_not_5a; - passthru u_deep (.io_ifc(i_deep)); - chk u_chk_deep (.io_ifc(i_deep), .is_z(deep_is_z), .is_5a(deep_is_5a), - .not_z(deep_not_z), .not_5a(deep_not_5a)); - - initial begin - // Driver off => high-Z - i.we = 1'b0; - i_mp.we = 1'b0; - i_deep.we = 1'b0; - #1; - - `checkh(is_z, 1'b1); - `checkh(is_5a, 1'b0); - `checkh(not_z, 1'b0); - `checkh(not_5a, 1'b1); - `checkh(mp_is_z, 1'b1); - `checkh(mp_is_5a, 1'b0); - `checkh(mp_not_z, 1'b0); - `checkh(mp_not_5a, 1'b1); - `checkh(deep_is_z, 1'b1); - `checkh(deep_is_5a, 1'b0); - - // Driver on => 8'h5A - #1; - i.we = 1'b1; - i_mp.we = 1'b1; - i_deep.we = 1'b1; - #1; - - `checkh(is_z, 1'b0); - `checkh(is_5a, 1'b1); - `checkh(not_z, 1'b1); - `checkh(not_5a, 1'b0); - `checkh(mp_is_z, 1'b0); - `checkh(mp_is_5a, 1'b1); - `checkh(mp_not_z, 1'b1); - `checkh(mp_not_5a, 1'b0); - `checkh(deep_is_z, 1'b0); - `checkh(deep_is_5a, 1'b1); - - // Driver off again => high-Z - #1; - i.we = 1'b0; - i_mp.we = 1'b0; - i_deep.we = 1'b0; - #1; - - `checkh(is_z, 1'b1); - `checkh(is_5a, 1'b0); - `checkh(not_z, 1'b0); - `checkh(not_5a, 1'b1); - `checkh(mp_is_z, 1'b1); - `checkh(mp_is_5a, 1'b0); - `checkh(mp_not_z, 1'b0); - `checkh(mp_not_5a, 1'b1); - `checkh(deep_is_z, 1'b1); - `checkh(deep_is_5a, 1'b0); - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_tri_iface_eqcase_modport_bad.v b/test_regress/t/t_tri_iface_eqcase_modport_bad.v index 6f4a13fe4..cf95b5427 100644 --- a/test_regress/t/t_tri_iface_eqcase_modport_bad.v +++ b/test_regress/t/t_tri_iface_eqcase_modport_bad.v @@ -11,21 +11,21 @@ // SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 interface ifc; - logic we; - tri [7:0] d; - modport no_d_mp (input we); // d is NOT exposed + logic we; + tri [7:0] d; + modport no_d_mp (input we); // d is NOT exposed endinterface module chk_bad ( - ifc.no_d_mp io_ifc, - output logic is_z + ifc.no_d_mp io_ifc, + output logic is_z ); - assign is_z = (io_ifc.d === 8'hzz); + assign is_z = (io_ifc.d === 8'hzz); endmodule module t; - ifc i (); - logic is_z; - chk_bad u (.io_ifc(i), .is_z(is_z)); - initial $finish; + ifc i (); + logic is_z; + chk_bad u (.io_ifc(i), .is_z(is_z)); + initial $finish; endmodule diff --git a/test_regress/t/t_tri_iface_mixed.py b/test_regress/t/t_tri_iface_mixed.py deleted file mode 100755 index 1ddad07d5..000000000 --- a/test_regress/t/t_tri_iface_mixed.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/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(timing_loop=True, verilator_flags2=['--timing']) - -test.execute() - -test.passes() diff --git a/test_regress/t/t_tri_iface_mixed.v b/test_regress/t/t_tri_iface_mixed.v deleted file mode 100644 index aad5c5194..000000000 --- a/test_regress/t/t_tri_iface_mixed.v +++ /dev/null @@ -1,136 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// Test mixed local + external tristate drivers on the same interface signal. -// The interface contains a local driver, and an external module also drives -// the same tri signal through a port. -// -// 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 - -// 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 - -// verilator lint_off MULTIDRIVEN - -// Basic interface with local driver (no modport) -interface ifc; - logic we_local; - logic we_ext; - tri [7:0] d; - // Local driver inside the interface - assign d = we_local ? 8'hA5 : 8'hzz; -endinterface - -module drv ( - ifc io_ifc -); - // External driver from a different module - assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; -endmodule - -// Interface with modport and local driver -interface ifc_mp; - logic we_local; - logic we_ext; - tri [7:0] d; - modport drv_mp (input we_ext, inout d); - // Local driver inside the interface - assign d = we_local ? 8'hA5 : 8'hzz; -endinterface - -module drv_mp ( - ifc_mp.drv_mp io_ifc -); - // External driver through modport - assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; -endmodule - -module t; - ifc i (); - drv u (.io_ifc(i)); - - ifc_mp i_mp (); - drv_mp u_mp (.io_ifc(i_mp)); - - initial begin - i.we_local = 1'b0; - i.we_ext = 1'b0; - i_mp.we_local = 1'b0; - i_mp.we_ext = 1'b0; - #1; - - // ---- Basic (no modport) ---- - - // Both drivers off => high-Z - `checkh(i.d, 8'hzz); - - // Local driver only - #1; - i.we_local = 1'b1; - i.we_ext = 1'b0; - #1; - `checkh(i.d, 8'hA5); - - // External driver only - #1; - i.we_local = 1'b0; - i.we_ext = 1'b1; - #1; - `checkh(i.d, 8'h3C); - - // Both drivers on (OR of 8'hA5 and 8'h3C in Verilator's tristate model) - #1; - i.we_local = 1'b1; - i.we_ext = 1'b1; - #1; - `checkh(i.d, 8'hBD); - - // Both drivers off again - #1; - i.we_local = 1'b0; - i.we_ext = 1'b0; - #1; - `checkh(i.d, 8'hzz); - - // ---- Modport-based external access ---- - - // Both drivers off => high-Z - `checkh(i_mp.d, 8'hzz); - - // Local driver only - #1; - i_mp.we_local = 1'b1; - i_mp.we_ext = 1'b0; - #1; - `checkh(i_mp.d, 8'hA5); - - // External driver only (through modport) - #1; - i_mp.we_local = 1'b0; - i_mp.we_ext = 1'b1; - #1; - `checkh(i_mp.d, 8'h3C); - - // Both drivers on (OR of 8'hA5 and 8'h3C in Verilator's tristate model) - #1; - i_mp.we_local = 1'b1; - i_mp.we_ext = 1'b1; - #1; - `checkh(i_mp.d, 8'hBD); - - // Both drivers off again - #1; - i_mp.we_local = 1'b0; - i_mp.we_ext = 1'b0; - #1; - `checkh(i_mp.d, 8'hzz); - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_tri_iface_eqcase.py b/test_regress/t/t_tri_iface_semantics.py similarity index 100% rename from test_regress/t/t_tri_iface_eqcase.py rename to test_regress/t/t_tri_iface_semantics.py diff --git a/test_regress/t/t_tri_iface_semantics.v b/test_regress/t/t_tri_iface_semantics.v new file mode 100644 index 000000000..629edc12f --- /dev/null +++ b/test_regress/t/t_tri_iface_semantics.v @@ -0,0 +1,231 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +// 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 + +// verilator lint_off MULTIDRIVEN + +// ---------------- Compare semantics (=== / !==) ---------------- +interface ifc_cmp; + logic we; + tri [7:0] d; +endinterface + +module drv_cmp ( + ifc_cmp io_ifc +); + assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; +endmodule + +module chk_cmp ( + ifc_cmp io_ifc, + output logic is_z, + output logic is_5a, + output logic not_z, + output logic not_5a +); + assign is_z = (io_ifc.d === 8'hzz); + assign is_5a = (io_ifc.d === 8'h5A); + assign not_z = (io_ifc.d !== 8'hzz); + assign not_5a = (io_ifc.d !== 8'h5A); +endmodule + +interface ifc_cmp_mp; + logic we; + tri [7:0] d; + modport drv_mp (input we, inout d); + modport chk_mp (input we, inout d); +endinterface + +module drv_cmp_mp ( + ifc_cmp_mp.drv_mp io_ifc +); + assign io_ifc.d = io_ifc.we ? 8'h5A : 8'hzz; +endmodule + +module chk_cmp_mp ( + ifc_cmp_mp.chk_mp io_ifc, + output logic is_z, + output logic is_5a, + output logic not_z, + output logic not_5a +); + assign is_z = (io_ifc.d === 8'hzz); + assign is_5a = (io_ifc.d === 8'h5A); + assign not_z = (io_ifc.d !== 8'hzz); + assign not_5a = (io_ifc.d !== 8'h5A); +endmodule + +module passthru_cmp ( + ifc_cmp io_ifc +); + drv_cmp u_drv (.*); +endmodule + +// ---------------- Mixed local + external contributors ---------------- +interface ifc_mix; + logic we_local; + logic we_ext; + tri [7:0] d; + assign d = we_local ? 8'hA5 : 8'hzz; +endinterface + +module drv_mix ( + ifc_mix io_ifc +); + assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; +endmodule + +interface ifc_mix_mp; + logic we_local; + logic we_ext; + tri [7:0] d; + modport drv_mp (input we_ext, inout d); + assign d = we_local ? 8'hA5 : 8'hzz; +endinterface + +module drv_mix_mp ( + ifc_mix_mp.drv_mp io_ifc +); + assign io_ifc.d = io_ifc.we_ext ? 8'h3C : 8'hzz; +endmodule + +module t; + // ---- Compare semantics: basic ---- + ifc_cmp i_cmp (); + logic is_z, is_5a, not_z, not_5a; + drv_cmp u_drv_cmp (.io_ifc(i_cmp)); + chk_cmp u_chk_cmp (.io_ifc(i_cmp), .is_z(is_z), .is_5a(is_5a), + .not_z(not_z), .not_5a(not_5a)); + + // ---- Compare semantics: modport ---- + ifc_cmp_mp i_cmp_mp (); + logic mp_is_z, mp_is_5a, mp_not_z, mp_not_5a; + drv_cmp_mp u_drv_cmp_mp (.io_ifc(i_cmp_mp)); + chk_cmp_mp u_chk_cmp_mp (.io_ifc(i_cmp_mp), .is_z(mp_is_z), .is_5a(mp_is_5a), + .not_z(mp_not_z), .not_5a(mp_not_5a)); + + // ---- Compare semantics: deep hierarchy ---- + ifc_cmp i_cmp_deep (); + logic deep_is_z, deep_is_5a, deep_not_z, deep_not_5a; + passthru_cmp u_cmp_deep (.io_ifc(i_cmp_deep)); + chk_cmp u_chk_cmp_deep (.io_ifc(i_cmp_deep), .is_z(deep_is_z), .is_5a(deep_is_5a), + .not_z(deep_not_z), .not_5a(deep_not_5a)); + + // ---- Mixed contributors ---- + ifc_mix i_mix (); + drv_mix u_mix (.io_ifc(i_mix)); + + ifc_mix_mp i_mix_mp (); + drv_mix_mp u_mix_mp (.io_ifc(i_mix_mp)); + + initial begin + // ---- Compare semantics ---- + i_cmp.we = 1'b0; + i_cmp_mp.we = 1'b0; + i_cmp_deep.we = 1'b0; + #1; + + `checkh(is_z, 1'b1); + `checkh(is_5a, 1'b0); + `checkh(not_z, 1'b0); + `checkh(not_5a, 1'b1); + `checkh(mp_is_z, 1'b1); + `checkh(mp_is_5a, 1'b0); + `checkh(mp_not_z, 1'b0); + `checkh(mp_not_5a, 1'b1); + `checkh(deep_is_z, 1'b1); + `checkh(deep_is_5a, 1'b0); + + #1; + i_cmp.we = 1'b1; + i_cmp_mp.we = 1'b1; + i_cmp_deep.we = 1'b1; + #1; + + `checkh(is_z, 1'b0); + `checkh(is_5a, 1'b1); + `checkh(not_z, 1'b1); + `checkh(not_5a, 1'b0); + `checkh(mp_is_z, 1'b0); + `checkh(mp_is_5a, 1'b1); + `checkh(mp_not_z, 1'b1); + `checkh(mp_not_5a, 1'b0); + `checkh(deep_is_z, 1'b0); + `checkh(deep_is_5a, 1'b1); + + #1; + i_cmp.we = 1'b0; + i_cmp_mp.we = 1'b0; + i_cmp_deep.we = 1'b0; + #1; + + `checkh(is_z, 1'b1); + `checkh(is_5a, 1'b0); + `checkh(not_z, 1'b0); + `checkh(not_5a, 1'b1); + `checkh(mp_is_z, 1'b1); + `checkh(mp_is_5a, 1'b0); + `checkh(mp_not_z, 1'b0); + `checkh(mp_not_5a, 1'b1); + `checkh(deep_is_z, 1'b1); + `checkh(deep_is_5a, 1'b0); + + // ---- Mixed contributors ---- + i_mix.we_local = 1'b0; + i_mix.we_ext = 1'b0; + i_mix_mp.we_local = 1'b0; + i_mix_mp.we_ext = 1'b0; + #1; + + `checkh(i_mix.d, 8'hzz); + `checkh(i_mix_mp.d, 8'hzz); + + #1; + i_mix.we_local = 1'b1; + i_mix.we_ext = 1'b0; + i_mix_mp.we_local = 1'b1; + i_mix_mp.we_ext = 1'b0; + #1; + `checkh(i_mix.d, 8'hA5); + `checkh(i_mix_mp.d, 8'hA5); + + #1; + i_mix.we_local = 1'b0; + i_mix.we_ext = 1'b1; + i_mix_mp.we_local = 1'b0; + i_mix_mp.we_ext = 1'b1; + #1; + `checkh(i_mix.d, 8'h3C); + `checkh(i_mix_mp.d, 8'h3C); + + #1; + i_mix.we_local = 1'b1; + i_mix.we_ext = 1'b1; + i_mix_mp.we_local = 1'b1; + i_mix_mp.we_ext = 1'b1; + #1; + `checkh(i_mix.d, 8'hBD); + `checkh(i_mix_mp.d, 8'hBD); + + #1; + i_mix.we_local = 1'b0; + i_mix.we_ext = 1'b0; + i_mix_mp.we_local = 1'b0; + i_mix_mp.we_ext = 1'b0; + #1; + `checkh(i_mix.d, 8'hzz); + `checkh(i_mix_mp.d, 8'hzz); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_tri_root_ref.v b/test_regress/t/t_tri_root_ref.v index 12794c581..44dc14d29 100644 --- a/test_regress/t/t_tri_root_ref.v +++ b/test_regress/t/t_tri_root_ref.v @@ -15,37 +15,37 @@ // verilog_format: on module sub_root_reader ( - output logic [7:0] val + output logic [7:0] val ); - assign val = $root.t.root_bus; + assign val = $root.t.root_bus; endmodule module t; - tri [7:0] root_bus; - logic root_we; - assign root_bus = root_we ? 8'hCC : 8'hzz; + tri [7:0] root_bus; + logic root_we; + assign root_bus = root_we ? 8'hCC : 8'hzz; - logic [7:0] root_readback; - sub_root_reader u_reader(.val(root_readback)); + logic [7:0] root_readback; + sub_root_reader u_reader(.val(root_readback)); - initial begin - #1; - root_we = 1'b0; - #1; - `checkh(root_readback, 8'hzz); + initial begin + #1; + root_we = 1'b0; + #1; + `checkh(root_readback, 8'hzz); - #1; - root_we = 1'b1; - #1; - `checkh(root_readback, 8'hCC); + #1; + root_we = 1'b1; + #1; + `checkh(root_readback, 8'hCC); - #1; - root_we = 1'b0; - #1; - `checkh(root_readback, 8'hzz); + #1; + root_we = 1'b0; + #1; + `checkh(root_readback, 8'hzz); - #1; - $write("*-* All Finished *-*\n"); - $finish; - end + #1; + $write("*-* All Finished *-*\n"); + $finish; + end endmodule From 97fd113536028e87d24edfd1142c12fc072d03ce Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 2 Mar 2026 23:56:17 -0500 Subject: [PATCH 8/9] Fix incorrect golden reference --- test_regress/t/t_tri_iface_eqcase_modport_bad.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_regress/t/t_tri_iface_eqcase_modport_bad.out b/test_regress/t/t_tri_iface_eqcase_modport_bad.out index 486d1e507..7f2cc5c89 100644 --- a/test_regress/t/t_tri_iface_eqcase_modport_bad.out +++ b/test_regress/t/t_tri_iface_eqcase_modport_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_tri_iface_eqcase_modport_bad.v:23:26: Can't find definition of 'd' in dotted variable/method: 'io_ifc.d' - 23 | assign is_z = (io_ifc.d === 8'hzz); - | ^ +%Error: t/t_tri_iface_eqcase_modport_bad.v:23:25: Can't find definition of 'd' in dotted variable/method: 'io_ifc.d' + 23 | assign is_z = (io_ifc.d === 8'hzz); + | ^ ... Known scopes under 'io_ifc': ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. %Error: Exiting due to From be7827488ff738fc4a9eee161379b515398d3d29 Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Tue, 3 Mar 2026 08:17:00 -0500 Subject: [PATCH 9/9] Fix t_tri_hier_ref_unsup test output. --- test_regress/t/t_tri_hier_ref_unsup.out | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test_regress/t/t_tri_hier_ref_unsup.out b/test_regress/t/t_tri_hier_ref_unsup.out index 17b04a233..5cc7a8bdc 100644 --- a/test_regress/t/t_tri_hier_ref_unsup.out +++ b/test_regress/t/t_tri_hier_ref_unsup.out @@ -1,10 +1,12 @@ -%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:34:17: Unsupported tristate construct: hierarchical driver of non-interface tri signal: 'bus' +%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:34:16: Unsupported LHS tristate construct: VARXREF 'bus' : ... note: In instance 't' - 34 | assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; - | ^~~ + 34 | assign u_sub.bus = hier_we ? 8'hBB : 8'hzz; + | ^~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:23:14: Unsupported tristate construct (in graph; not converted): VAR 'bus' - : ... note: In instance 't.u_sub' - 23 | tri [7:0] bus; - | ^~~ -%Error: Exiting due to +%Error-UNSUPPORTED: t/t_tri_hier_ref_unsup.v:42:19: Unsupported tristate construct: VARXREF 'bus' in function getEnExprBasedOnOriginalp + 42 | do if ((u_sub.bus) !== (8'hzz)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_tri_hier_ref_unsup.v",42, (u_sub.bus), (8'hzz)); $stop; end while(0);; + | ^~~ +%Error: Internal Error: t/t_tri_hier_ref_unsup.v:42:24: ../V3Ast.cpp:#: Null item passed to setOp2p + 42 | do if ((u_sub.bus) !== (8'hzz)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_tri_hier_ref_unsup.v",42, (u_sub.bus), (8'hzz)); $stop; end while(0);; + | ^~~ + ... This fatal error may be caused by the earlier error(s); resolve those first.