From 44ccdbf669bbb4fc017a3ff4d8f292fd200722a8 Mon Sep 17 00:00:00 2001 From: Nick Brereton Date: Mon, 2 Mar 2026 19:07:30 -0500 Subject: [PATCH] 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