From b211af9d249dec15dc32cfd206c4b3ac6588b19a Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Fri, 10 Jul 2026 12:31:08 +0200 Subject: [PATCH] Fix independent force of multiply instantiated signals (#7905) Signed-off-by: Artur Bieniek --- src/V3Force.cpp | 68 +++++++++++++------------- test_regress/t/t_force_scope.py | 18 +++++++ test_regress/t/t_force_scope.v | 25 ++++++++++ test_regress/t/t_force_scope_assign.py | 18 +++++++ test_regress/t/t_force_scope_assign.v | 37 ++++++++++++++ 5 files changed, 133 insertions(+), 33 deletions(-) create mode 100755 test_regress/t/t_force_scope.py create mode 100644 test_regress/t/t_force_scope.v create mode 100755 test_regress/t/t_force_scope_assign.py create mode 100644 test_regress/t/t_force_scope_assign.v diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 3a7b51ce9..3a97ddf53 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -145,7 +145,7 @@ private: const VNUser2InUse m_user2InUse; std::vector m_varInfos; // Indexed by stable variable ID - std::unordered_map m_varToId; + std::unordered_map m_varToId; std::unordered_set m_clockedWrites; std::unordered_map> m_rhsDepToForces; std::unordered_map m_scopeVarCaches; @@ -329,7 +329,7 @@ public: // Non-bitwise member/struct paths cannot use a real bit range, so map each distinct // source path onto a synthetic index in VlForceVec and use that index consistently // for force, release, and readback. - VarForceInfo& varInfo = getOrCreateVarInfo(varp); + VarForceInfo& varInfo = getOrCreateVarInfo(getOneVarRef(lhsp)->varScopep()); const int index = varInfo.getOrCreateForcePathIndex(lhsp); info.m_rangeLsb = index; info.m_rangeMsb = index; @@ -426,14 +426,16 @@ public: }); } - VarForceInfo& getOrCreateVarInfo(AstVar* varp) { - const auto it = m_varToId.find(varp); + VarForceInfo& getOrCreateVarInfo(AstVarScope* vscp) { + const auto it = m_varToId.find(vscp); if (it != m_varToId.end()) return m_varInfos[it->second]; - m_varToId.emplace(varp, m_varInfos.size()); + m_varToId.emplace(vscp, m_varInfos.size()); m_varInfos.emplace_back(); VarForceInfo& info = m_varInfos.back(); - info.m_varp = varp; + info.m_varVscp = vscp; + info.m_varp = vscp->varp(); + info.m_scopep = vscp->scopep(); return info; } @@ -442,8 +444,8 @@ public: bool doingAssign() const { return m_doingAssign; } - const VarForceInfo* getVarInfo(AstVar* varp) const { - const auto it = m_varToId.find(varp); + const VarForceInfo* getVarInfo(AstVarScope* vscp) const { + const auto it = m_varToId.find(vscp); return it != m_varToId.end() ? &m_varInfos[it->second] : nullptr; } @@ -464,8 +466,7 @@ public: v3Global.setUsesForce(); varp->setForcedByCode(); - VarForceInfo& info = getOrCreateVarInfo(varp); - if (!info.m_scopep) info.m_scopep = vscp->scopep(); + VarForceInfo& info = getOrCreateVarInfo(vscp); const int forceId = info.m_forces.size(); FileLine* const flp = varp->fileline(); AstScope* const scopep = vscp->scopep(); @@ -475,9 +476,11 @@ public: AstCDType* const forceVecDtypep = new AstCDType{flp, "VlForceVec"}; v3Global.rootp()->typeTablep()->addTypesp(forceVecDtypep); - AstVar* const forceVecVarp = new AstVar{ - flp, VVarType::MEMBER, - varp->name() + (m_doingAssign ? "_VassignVec" : "__VforceVec"), forceVecDtypep}; + AstVar* const forceVecVarp + = new AstVar{flp, VVarType::MEMBER, + varp->name() + (m_doingAssign ? "_VassignVec" : "__VforceVec") + "__" + + scopep->nameDotless(), + forceVecDtypep}; forceVecVarp->funcLocal(false); forceVecVarp->isInternal(true); varp->addNextHere(forceVecVarp); @@ -578,11 +581,11 @@ public: UASSERT_OBJ(finfo.m_rhsExprp, varp, "Missing RHS expression for ForceInfo"); // Create per-force temporary storage for the captured RHS value. - AstVar* const rhsVarp - = new AstVar{flp, VVarType::VAR, - varp->name() + (doingAssign() ? "_VassignRHS" : "__VforceRHS") - + std::to_string(finfo.m_forceId), - finfo.m_rhsExprp->dtypep()}; + AstVar* const rhsVarp = new AstVar{ + flp, VVarType::VAR, + varp->name() + (doingAssign() ? "_VassignRHS" : "__VforceRHS") + + std::to_string(finfo.m_forceId) + "__" + scopep->nameDotless(), + finfo.m_rhsExprp->dtypep()}; rhsVarp->noSubst(true); rhsVarp->sigPublic(true); rhsVarp->setForcedByCode(); @@ -704,8 +707,8 @@ public: } const ForceInfo& getForceInfo(AstAssignForce* forceStmtp) const { - AstVar* varp = getOneVarRef(forceStmtp->lhsp())->varp(); - const VarForceInfo* const varInfo = getVarInfo(varp); + AstVarScope* const vscp = getOneVarRef(forceStmtp->lhsp())->varScopep(); + const VarForceInfo* const varInfo = getVarInfo(vscp); UASSERT(varInfo, "Force info not found for variable"); auto it2 = varInfo->m_forces.find(forceStmtp); UASSERT(it2 != varInfo->m_forces.end(), "Force statement not found"); @@ -812,11 +815,10 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { nodep->scopep()->addVarsp(enVscp); nodep->scopep()->addVarsp(valVscp); - ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep); info.m_forceRdVscp = rdVscp; info.m_forceEnVscp = enVscp; info.m_forceValVscp = valVscp; - info.m_varVscp = nodep; varp->user3p(rdVscp); varp->user4p(enVscp); nodep->user3p(valVscp); @@ -906,11 +908,10 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { if (rdVscp || enVscp || valVscp) { UASSERT_OBJ(rdVscp && enVscp && valVscp, nodep, "Incomplete pre-existing force helper set"); - ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep); info.m_forceRdVscp = rdVscp; info.m_forceEnVscp = enVscp; info.m_forceValVscp = valVscp; - info.m_varVscp = nodep; iterateChildrenConst(nodep); return; } @@ -958,11 +959,10 @@ class ForceDiscoveryVisitor final : public VNVisitorConst { nodep->user3p(valVscp); // Register force metadata so later transforms can find these helper vars. - ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(varp); + ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep); info.m_forceRdVscp = rdVscp; info.m_forceEnVscp = enVscp; info.m_forceValVscp = valVscp; - info.m_varVscp = nodep; // Build an update block triggered by force-enable changes. AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED, @@ -1033,7 +1033,8 @@ class ForceConvertVisitor final : public VNVisitor { AstVar* const forcedVarp = lhsVarRefp->varp(); const ForceState::ForceInfo& info = m_state.getForceInfo(nodep); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(forcedVarp); + const ForceState::VarForceInfo* const varInfo + = m_state.getVarInfo(lhsVarRefp->varScopep()); UASSERT_OBJ(varInfo && varInfo->m_forceVecVscp, nodep, "Force info not set up"); FileLine* const flp = nodep->fileline(); @@ -1141,7 +1142,8 @@ class ForceConvertVisitor final : public VNVisitor { AstVarRef* const lhsVarRefp = m_state.getOneVarRef(lhsp); AstVar* const releasedVarp = lhsVarRefp->varp(); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(releasedVarp); + const ForceState::VarForceInfo* const varInfo + = m_state.getVarInfo(lhsVarRefp->varScopep()); if (!varInfo) { VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); return; @@ -1314,7 +1316,7 @@ class ForceReplaceVisitor final : public VNVisitor { } AstVar* const varp = refp->varp(); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(refp->varScopep()); if (!varInfo || varInfo->m_forceRdVscp || varInfo->m_forces.empty() || !ForceState::isBitwiseDType(varp) || !varp->dtypep()->isWide()) { visit(static_cast(nodep)); @@ -1362,7 +1364,7 @@ class ForceReplaceVisitor final : public VNVisitor { return; } AstVar* const varp = baseRefp->varp(); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(baseRefp->varScopep()); // Skip non-forceable reads, reads we intentionally protected earlier, and intermediate // selections that still evaluate to an unpacked array rather than a scalar element. if (ForceState::isNotReplaceable(baseRefp) || !varInfo @@ -1389,8 +1391,7 @@ class ForceReplaceVisitor final : public VNVisitor { if (ForceState::isNotReplaceable(nodep)) return; if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) return; - AstVar* const varp = nodep->varp(); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(nodep->varScopep()); if (!varInfo) return; if (varInfo->m_forceRdVscp) { @@ -1444,7 +1445,8 @@ class ForceReplaceVisitor final : public VNVisitor { AstVar* const varp = baseRefp->varp(); if (!ForceState::isBitwiseDType(varp) && !ForceState::isUnpackedArrayDType(varp->dtypep())) { - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + const ForceState::VarForceInfo* const varInfo + = m_state.getVarInfo(baseRefp->varScopep()); if (!ForceState::isNotReplaceable(baseRefp) && varInfo) { const int forcePathIndex = varInfo->findForcePathIndex(exprp); if (forcePathIndex >= 0) { diff --git a/test_regress/t/t_force_scope.py b/test_regress/t/t_force_scope.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_force_scope.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_scope.v b/test_regress/t/t_force_scope.v new file mode 100644 index 000000000..72779b372 --- /dev/null +++ b/test_regress/t/t_force_scope.v @@ -0,0 +1,25 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module child(input wire i); + /*verilator no_inline_module*/ +endmodule + +module t; + child a(.i(1'b0)); + child b(.i(1'b0)); + + initial begin + force a.i = 1'b1; + + if (a.i !== 1'b1) $stop; + if (b.i !== 1'b0) $stop; + if (a.i === b.i) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_force_scope_assign.py b/test_regress/t/t_force_scope_assign.py new file mode 100755 index 000000000..10f1e0290 --- /dev/null +++ b/test_regress/t/t_force_scope_assign.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["-Wno-IEEEMAYDEPRECATE"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_force_scope_assign.v b/test_regress/t/t_force_scope_assign.v new file mode 100644 index 000000000..96b389029 --- /dev/null +++ b/test_regress/t/t_force_scope_assign.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain +// SPDX-FileCopyrightText: 2026 Antmicro +// SPDX-License-Identifier: CC0-1.0 + +module child(input wire drive, output wire observed); + /*verilator no_inline_module*/ + + logic value; + + assign observed = value; + + initial begin + value = 1'b0; + if (drive) assign value = 1'b1; + end +endmodule + +module t; + wire a_observed; + wire b_observed; + bit done; + + child a(.drive(1'b1), .observed(a_observed)); + child b(.drive(1'b0), .observed(b_observed)); + + always @(a_observed or b_observed) begin + if (!done && a_observed === 1'b1) begin + done = 1'b1; + if (b_observed !== 1'b0) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule