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/V3AstNodeOther.h b/src/V3AstNodeOther.h index 4a2e03f65..eef1c05e5 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1946,6 +1946,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() { @@ -2001,6 +2003,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; } @@ -2174,6 +2178,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..643bb7141 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,17 @@ 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 667184be7..159aa514b 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} {} }; @@ -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,9 +471,32 @@ 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 (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. @@ -486,6 +511,16 @@ 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 +529,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 +566,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)) { @@ -561,7 +620,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); @@ -636,14 +695,62 @@ 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 final { + 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); } @@ -663,20 +770,35 @@ 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, varp->name() + "__out" + cvtToStr(m_unique), varp}; // 2-state ok; sep enable + newLhsp->setDfgTriLowered(); 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 = 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); @@ -708,7 +830,12 @@ 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 +848,31 @@ 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,25 +881,38 @@ 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, 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); @@ -796,11 +938,74 @@ 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) { + // 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()); + 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}; + 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); + + // 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) { @@ -1753,7 +1958,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()); @@ -1789,12 +1994,21 @@ 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}); + } } } } + 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); @@ -1897,12 +2111,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}; + envarp->setDfgTriLowered(); + 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_modport.v b/test_regress/t/t_interface_modport.v index c4a6580b5..f9e480566 100644 --- a/test_regress/t/t_interface_modport.v +++ b/test_regress/t/t_interface_modport.v @@ -5,88 +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); +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 - ); + // 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)); - initial begin - c1_data.value = 4'h4; - c2_data.value = 4'h5; - c3_data.value = 4'h6; - c4_data.value = 4'h7; - end + logic inout_we; + tri inout_d; + inout_mod_wrap inout_u (.we(inout_we), .d(inout_d)); - 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 + 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 endmodule `ifndef VERILATOR @@ -94,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 @@ -121,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.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 100644 index 000000000..f159b86d0 --- /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_lint_multidriven_coverage_bad.out b/test_regress/t/t_lint_multidriven_coverage_bad.out new file mode 100644 index 000000000..7e06c4ffb --- /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: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: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.py b/test_regress/t/t_lint_multidriven_coverage_bad.py new file mode 100755 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..4ea05186e --- /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 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..5c1b41793 --- /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:16: 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:13: 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 100755 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..e8cd1165a --- /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_modport_bad.out b/test_regress/t/t_tri_iface_eqcase_modport_bad.out new file mode 100644 index 000000000..7f2cc5c89 --- /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: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 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 100755 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..cf95b5427 --- /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_semantics.py b/test_regress/t/t_tri_iface_semantics.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_tri_iface_semantics.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_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.py b/test_regress/t/t_tri_root_ref.py new file mode 100755 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..44dc14d29 --- /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