Fix force helpers in multiply instantiated modules (#8047)

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
Artur Bieniek 2026-08-05 22:47:45 +02:00 committed by GitHub
parent b1ec180a3b
commit e413e67ab3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 98 additions and 89 deletions

View File

@ -122,6 +122,12 @@ public:
}
};
struct ForceHelperVars final {
AstVar* m_rdVarp = nullptr;
AstVar* m_enVarp = nullptr;
AstVar* m_valVarp = nullptr;
};
struct ArraySelInfo final {
std::vector<AstArraySel*> m_sels;
bool m_hasBitSel = false;
@ -138,12 +144,15 @@ private:
// NODE STATE
// AstVarRef::user1 -> Flag indicating not to replace reference
// AstAssignForce::user2 -> true if force is synthetic (externally forceable)
// AstVar::user3p() -> AstVarScope*: Generated <name>__VforceRd helper
// AstVar::user4p() -> AstVarScope*: Generated <name>__VforceEn helper
// AstVarScope::user3p() -> AstVarScope*: Generated <name>__VforceVal helper
// AstVar::user3 -> ForceHelperVars via m_forceHelperVarsByVar
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
public:
using ForceHelperVarsByVar = AstUser3Allocator<AstVar, ForceHelperVars>;
private:
ForceHelperVarsByVar& m_forceHelperVarsByVar;
std::vector<VarForceInfo> m_varInfos; // Indexed by stable variable ID
std::unordered_map<AstVarScope*, int> m_varToId;
std::unordered_set<AstVar*> m_clockedWrites;
@ -153,8 +162,9 @@ private:
// statements instead of force statements
public:
ForceState(bool doingAssign)
: m_doingAssign{doingAssign} {}
ForceState(bool doingAssign, ForceHelperVarsByVar& forceHelperVarsByVar)
: m_forceHelperVarsByVar{forceHelperVarsByVar}
, m_doingAssign{doingAssign} {}
VL_UNCOPYABLE(ForceState);
// STATIC METHODS
@ -439,6 +449,49 @@ public:
info.m_varVscp = vscp;
info.m_varp = vscp->varp();
info.m_scopep = vscp->scopep();
AstVar* const varp = info.m_varp;
if (!varp->isForceable()) return info;
FileLine* const flp = varp->fileline();
ForceHelperVars& helperVars = m_forceHelperVarsByVar(varp);
const bool helperVarsBuilt = helperVars.m_rdVarp != nullptr;
UASSERT_OBJ(helperVarsBuilt == (helperVars.m_enVarp != nullptr)
&& helperVarsBuilt == (helperVars.m_valVarp != nullptr),
varp, "Incomplete force helper set");
if (!helperVarsBuilt) {
const bool unpacked = isUnpackedArrayDType(varp->dtypep());
const VVarType enValType = unpacked ? VVarType::WIRE : VVarType::VAR;
AstNodeDType* const enDtypep
= unpacked || isBitwiseDType(varp) ? varp->dtypep() : varp->findBitDType();
helperVars.m_rdVarp
= new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()};
helperVars.m_rdVarp->sigPublic(true);
helperVars.m_enVarp
= new AstVar{flp, enValType, varp->name() + "__VforceEn", enDtypep};
helperVars.m_enVarp->sigUserRWPublic(true);
helperVars.m_valVarp
= new AstVar{flp, enValType, varp->name() + "__VforceVal", varp->dtypep()};
helperVars.m_valVarp->sigUserRWPublic(true);
varp->addNextHere(helperVars.m_rdVarp);
varp->addNextHere(helperVars.m_enVarp);
varp->addNextHere(helperVars.m_valVarp);
}
info.m_forceRdVscp = findScopeVar(info.m_scopep, helperVars.m_rdVarp);
info.m_forceEnVscp = findScopeVar(info.m_scopep, helperVars.m_enVarp);
info.m_forceValVscp = findScopeVar(info.m_scopep, helperVars.m_valVarp);
if (info.m_forceRdVscp || info.m_forceEnVscp || info.m_forceValVscp) {
UASSERT_OBJ(info.m_forceRdVscp && info.m_forceEnVscp && info.m_forceValVscp, vscp,
"Incomplete pre-existing force helper set");
} else {
info.m_forceRdVscp = new AstVarScope{flp, info.m_scopep, helperVars.m_rdVarp};
info.m_forceEnVscp = new AstVarScope{flp, info.m_scopep, helperVars.m_enVarp};
info.m_forceValVscp = new AstVarScope{flp, info.m_scopep, helperVars.m_valVarp};
info.m_scopep->addVarsp(info.m_forceRdVscp);
info.m_scopep->addVarsp(info.m_forceEnVscp);
info.m_scopep->addVarsp(info.m_forceValVscp);
}
return info;
}
@ -822,33 +875,9 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
FileLine* const flp = varp->fileline();
const int innerWidth = leafDtypep->width();
AstVar* const rdVarp
= new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()};
rdVarp->noSubst(true);
rdVarp->sigPublic(true);
AstVar* const enVarp
= new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceEn", varp->dtypep()};
enVarp->sigUserRWPublic(true);
AstVar* const valVarp
= new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceVal", varp->dtypep()};
valVarp->sigUserRWPublic(true);
varp->addNextHere(rdVarp);
varp->addNextHere(enVarp);
varp->addNextHere(valVarp);
AstVarScope* const rdVscp = new AstVarScope{flp, nodep->scopep(), rdVarp};
AstVarScope* const enVscp = new AstVarScope{flp, nodep->scopep(), enVarp};
AstVarScope* const valVscp = new AstVarScope{flp, nodep->scopep(), valVarp};
nodep->scopep()->addVarsp(rdVscp);
nodep->scopep()->addVarsp(enVscp);
nodep->scopep()->addVarsp(valVscp);
ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep);
info.m_forceRdVscp = rdVscp;
info.m_forceEnVscp = enVscp;
info.m_forceValVscp = valVscp;
varp->user3p(rdVscp);
varp->user4p(enVscp);
nodep->user3p(valVscp);
AstVarScope* const enVscp = info.m_forceEnVscp;
AstVarScope* const valVscp = info.m_forceValVscp;
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
new AstVarRef{flp, enVscp, VAccess::READ}};
@ -928,20 +957,9 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
// ForceState. Reuse already-created public helper vars instead of regenerating
// duplicate __Vforce* members for every forceable signal.
if (m_state.doingAssign()) {
AstVar* const varp = nodep->varp();
AstVarScope* const rdVscp = VN_CAST(varp->user3p(), VarScope);
AstVarScope* const enVscp = VN_CAST(varp->user4p(), VarScope);
AstVarScope* const valVscp = VN_CAST(nodep->user3p(), VarScope);
if (rdVscp || enVscp || valVscp) {
UASSERT_OBJ(rdVscp && enVscp && valVscp, nodep,
"Incomplete pre-existing force helper set");
ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep);
info.m_forceRdVscp = rdVscp;
info.m_forceEnVscp = enVscp;
info.m_forceValVscp = valVscp;
iterateChildrenConst(nodep);
return;
}
m_state.getOrCreateVarInfo(nodep);
iterateChildrenConst(nodep);
return;
}
if (AstUnpackArrayDType* const arrDtypep
@ -957,39 +975,12 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
"Forcing strings is not permitted: " << nodep->varp()->name());
}
// Create per-signal storage for force enable/value state.
// Build the per-signal force update logic.
AstVar* const varp = nodep->varp();
FileLine* const flp = varp->fileline();
AstVar* const rdVarp
= new AstVar{flp, VVarType::WIRE, varp->name() + "__VforceRd", varp->dtypep()};
rdVarp->noSubst(true);
rdVarp->sigPublic(true);
AstNodeDType* const enDtypep
= ForceState::isBitwiseDType(varp) ? varp->dtypep() : varp->findBitDType();
AstVar* const enVarp
= new AstVar{flp, VVarType::VAR, varp->name() + "__VforceEn", enDtypep};
enVarp->sigUserRWPublic(true);
AstVar* const valVarp
= new AstVar{flp, VVarType::VAR, varp->name() + "__VforceVal", varp->dtypep()};
valVarp->sigUserRWPublic(true);
varp->addNextHere(rdVarp);
varp->addNextHere(enVarp);
varp->addNextHere(valVarp);
AstVarScope* const rdVscp = new AstVarScope{flp, nodep->scopep(), rdVarp};
AstVarScope* const enVscp = new AstVarScope{flp, nodep->scopep(), enVarp};
AstVarScope* const valVscp = new AstVarScope{flp, nodep->scopep(), valVarp};
nodep->scopep()->addVarsp(rdVscp);
nodep->scopep()->addVarsp(enVscp);
nodep->scopep()->addVarsp(valVscp);
varp->user3p(rdVscp);
varp->user4p(enVscp);
nodep->user3p(valVscp);
// Register force metadata so later transforms can find these helper vars.
ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep);
info.m_forceRdVscp = rdVscp;
info.m_forceEnVscp = enVscp;
info.m_forceValVscp = valVscp;
AstVarScope* const enVscp = info.m_forceEnVscp;
AstVarScope* const valVscp = info.m_forceValVscp;
// Build an update block triggered by force-enable changes.
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
@ -1521,17 +1512,10 @@ public:
//######################################################################
// V3Force - Main entry point
namespace {
class ForceUserSlots final {
const VNUser3InUse m_user3InUse;
const VNUser4InUse m_user4InUse;
};
} // namespace
static void forceAllImpl(AstNetlist* nodep) {
static void forceAllImpl(AstNetlist* nodep, ForceState::ForceHelperVarsByVar& helperVars) {
UINFO(2, __FUNCTION__ << ":\n");
if (!v3Global.hasForceableSignals()) return;
ForceState state{false};
ForceState state{false, helperVars};
{ ForceDiscoveryVisitor{nodep, state}; }
state.finalizeRhsVars();
{ ForceConvertVisitor{nodep, state}; }
@ -1539,7 +1523,7 @@ static void forceAllImpl(AstNetlist* nodep) {
V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3);
}
static void assignAllImpl(AstNetlist* nodep) {
static void assignAllImpl(AstNetlist* nodep, ForceState::ForceHelperVarsByVar& helperVars) {
UINFO(2, __FUNCTION__ << ":\n");
if (!v3Global.hasAssignDeassign()) return;
@ -1568,7 +1552,7 @@ static void assignAllImpl(AstNetlist* nodep) {
new AstRelease{deassignp->fileline(), deassignp->lhsp()->cloneTreePure(true)});
deassignp->deleteTree();
}
ForceState state{true};
ForceState state{true, helperVars};
{ ForceDiscoveryVisitor{nodep, state}; }
state.finalizeRhsVars();
{ ForceConvertVisitor{nodep, state}; }
@ -1577,7 +1561,8 @@ static void assignAllImpl(AstNetlist* nodep) {
}
void V3Force::forceAndAssignAll(AstNetlist* nodep) {
ForceUserSlots userSlots;
forceAllImpl(nodep);
assignAllImpl(nodep);
const VNUser3InUse user3InUse;
ForceState::ForceHelperVarsByVar helperVars;
forceAllImpl(nodep, helperVars);
assignAllImpl(nodep, helperVars);
}

View File

@ -8,6 +8,8 @@ module child (
input wire i
);
/*verilator no_inline_module*/
logic [2:0] unpacked[2] = '{3'b001, 3'b010};
endmodule
module t;
@ -16,10 +18,13 @@ module t;
initial begin
force a.i = 1'b1;
force a.unpacked[0] = 3'b111;
if (a.i !== 1'b1) $stop;
if (b.i !== 1'b0) $stop;
if (a.i === b.i) $stop;
if (a.unpacked[0] !== 3'b111) $stop;
if (b.unpacked[0] !== 3'b001) $stop;
$write("*-* All Finished *-*\n");
$finish;

View File

@ -0,0 +1,19 @@
#!/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.top_filename = "t/t_force_scope.v"
test.compile(verilator_flags2=["--public-flat-rw", "--vpi"])
test.execute()
test.passes()