Fix force helpers in multiply instantiated modules (#8047)
Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
parent
b1ec180a3b
commit
e413e67ab3
163
src/V3Force.cpp
163
src/V3Force.cpp
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue