V3Dfg: suppress MULTIDRIVEN for intentional interface tristate lowering while retaining coverage multidrive warnings

This commit is contained in:
Nick Brereton 2026-03-02 19:07:30 -05:00
parent fb8452c563
commit 44ccdbf669
6 changed files with 111 additions and 0 deletions

View File

@ -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; }

View File

@ -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<const DfgVertex*> stack;
std::vector<const DfgVertex*> 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<DfgVertexVar>()) {
AstVar* const astVarp = [&]() -> AstVar* {
if VL_CONSTEXPR_CXX17 (T_Scoped) {
return reinterpret_cast<AstVarScope*>(varp->nodep())->varp();
} else {
return reinterpret_cast<AstVar*>(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<DfgVertexVar>(); }
// Gather all synthesized drivers of an unresolved variable
static std::vector<Driver> gatherDriversUnresolved(DfgUnresolved* vtxp) {
std::vector<Driver> 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);

View File

@ -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<int>(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);
}

View File

@ -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

View File

@ -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()

View File

@ -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