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 {
|
struct ArraySelInfo final {
|
||||||
std::vector<AstArraySel*> m_sels;
|
std::vector<AstArraySel*> m_sels;
|
||||||
bool m_hasBitSel = false;
|
bool m_hasBitSel = false;
|
||||||
|
|
@ -138,12 +144,15 @@ private:
|
||||||
// NODE STATE
|
// NODE STATE
|
||||||
// AstVarRef::user1 -> Flag indicating not to replace reference
|
// AstVarRef::user1 -> Flag indicating not to replace reference
|
||||||
// AstAssignForce::user2 -> true if force is synthetic (externally forceable)
|
// AstAssignForce::user2 -> true if force is synthetic (externally forceable)
|
||||||
// AstVar::user3p() -> AstVarScope*: Generated <name>__VforceRd helper
|
// AstVar::user3 -> ForceHelperVars via m_forceHelperVarsByVar
|
||||||
// AstVar::user4p() -> AstVarScope*: Generated <name>__VforceEn helper
|
|
||||||
// AstVarScope::user3p() -> AstVarScope*: Generated <name>__VforceVal helper
|
|
||||||
const VNUser1InUse m_user1InUse;
|
const VNUser1InUse m_user1InUse;
|
||||||
const VNUser2InUse m_user2InUse;
|
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::vector<VarForceInfo> m_varInfos; // Indexed by stable variable ID
|
||||||
std::unordered_map<AstVarScope*, int> m_varToId;
|
std::unordered_map<AstVarScope*, int> m_varToId;
|
||||||
std::unordered_set<AstVar*> m_clockedWrites;
|
std::unordered_set<AstVar*> m_clockedWrites;
|
||||||
|
|
@ -153,8 +162,9 @@ private:
|
||||||
// statements instead of force statements
|
// statements instead of force statements
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ForceState(bool doingAssign)
|
ForceState(bool doingAssign, ForceHelperVarsByVar& forceHelperVarsByVar)
|
||||||
: m_doingAssign{doingAssign} {}
|
: m_forceHelperVarsByVar{forceHelperVarsByVar}
|
||||||
|
, m_doingAssign{doingAssign} {}
|
||||||
VL_UNCOPYABLE(ForceState);
|
VL_UNCOPYABLE(ForceState);
|
||||||
|
|
||||||
// STATIC METHODS
|
// STATIC METHODS
|
||||||
|
|
@ -439,6 +449,49 @@ public:
|
||||||
info.m_varVscp = vscp;
|
info.m_varVscp = vscp;
|
||||||
info.m_varp = vscp->varp();
|
info.m_varp = vscp->varp();
|
||||||
info.m_scopep = vscp->scopep();
|
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;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -822,33 +875,9 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
|
||||||
FileLine* const flp = varp->fileline();
|
FileLine* const flp = varp->fileline();
|
||||||
const int innerWidth = leafDtypep->width();
|
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);
|
ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep);
|
||||||
info.m_forceRdVscp = rdVscp;
|
AstVarScope* const enVscp = info.m_forceEnVscp;
|
||||||
info.m_forceEnVscp = enVscp;
|
AstVarScope* const valVscp = info.m_forceValVscp;
|
||||||
info.m_forceValVscp = valVscp;
|
|
||||||
varp->user3p(rdVscp);
|
|
||||||
varp->user4p(enVscp);
|
|
||||||
nodep->user3p(valVscp);
|
|
||||||
|
|
||||||
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
|
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
|
||||||
new AstVarRef{flp, enVscp, VAccess::READ}};
|
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
|
// ForceState. Reuse already-created public helper vars instead of regenerating
|
||||||
// duplicate __Vforce* members for every forceable signal.
|
// duplicate __Vforce* members for every forceable signal.
|
||||||
if (m_state.doingAssign()) {
|
if (m_state.doingAssign()) {
|
||||||
AstVar* const varp = nodep->varp();
|
m_state.getOrCreateVarInfo(nodep);
|
||||||
AstVarScope* const rdVscp = VN_CAST(varp->user3p(), VarScope);
|
iterateChildrenConst(nodep);
|
||||||
AstVarScope* const enVscp = VN_CAST(varp->user4p(), VarScope);
|
return;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AstUnpackArrayDType* const arrDtypep
|
if (AstUnpackArrayDType* const arrDtypep
|
||||||
|
|
@ -957,39 +975,12 @@ class ForceDiscoveryVisitor final : public VNVisitorConst {
|
||||||
"Forcing strings is not permitted: " << nodep->varp()->name());
|
"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();
|
AstVar* const varp = nodep->varp();
|
||||||
FileLine* const flp = varp->fileline();
|
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);
|
ForceState::VarForceInfo& info = m_state.getOrCreateVarInfo(nodep);
|
||||||
info.m_forceRdVscp = rdVscp;
|
AstVarScope* const enVscp = info.m_forceEnVscp;
|
||||||
info.m_forceEnVscp = enVscp;
|
AstVarScope* const valVscp = info.m_forceValVscp;
|
||||||
info.m_forceValVscp = valVscp;
|
|
||||||
|
|
||||||
// Build an update block triggered by force-enable changes.
|
// Build an update block triggered by force-enable changes.
|
||||||
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
|
AstSenItem* const itemsp = new AstSenItem{flp, VEdgeType::ET_CHANGED,
|
||||||
|
|
@ -1521,17 +1512,10 @@ public:
|
||||||
//######################################################################
|
//######################################################################
|
||||||
// V3Force - Main entry point
|
// V3Force - Main entry point
|
||||||
|
|
||||||
namespace {
|
static void forceAllImpl(AstNetlist* nodep, ForceState::ForceHelperVarsByVar& helperVars) {
|
||||||
class ForceUserSlots final {
|
|
||||||
const VNUser3InUse m_user3InUse;
|
|
||||||
const VNUser4InUse m_user4InUse;
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
static void forceAllImpl(AstNetlist* nodep) {
|
|
||||||
UINFO(2, __FUNCTION__ << ":\n");
|
UINFO(2, __FUNCTION__ << ":\n");
|
||||||
if (!v3Global.hasForceableSignals()) return;
|
if (!v3Global.hasForceableSignals()) return;
|
||||||
ForceState state{false};
|
ForceState state{false, helperVars};
|
||||||
{ ForceDiscoveryVisitor{nodep, state}; }
|
{ ForceDiscoveryVisitor{nodep, state}; }
|
||||||
state.finalizeRhsVars();
|
state.finalizeRhsVars();
|
||||||
{ ForceConvertVisitor{nodep, state}; }
|
{ ForceConvertVisitor{nodep, state}; }
|
||||||
|
|
@ -1539,7 +1523,7 @@ static void forceAllImpl(AstNetlist* nodep) {
|
||||||
V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3);
|
V3Global::dumpCheckGlobalTree("force", 0, dumpTreeEitherLevel() >= 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void assignAllImpl(AstNetlist* nodep) {
|
static void assignAllImpl(AstNetlist* nodep, ForceState::ForceHelperVarsByVar& helperVars) {
|
||||||
UINFO(2, __FUNCTION__ << ":\n");
|
UINFO(2, __FUNCTION__ << ":\n");
|
||||||
if (!v3Global.hasAssignDeassign()) return;
|
if (!v3Global.hasAssignDeassign()) return;
|
||||||
|
|
||||||
|
|
@ -1568,7 +1552,7 @@ static void assignAllImpl(AstNetlist* nodep) {
|
||||||
new AstRelease{deassignp->fileline(), deassignp->lhsp()->cloneTreePure(true)});
|
new AstRelease{deassignp->fileline(), deassignp->lhsp()->cloneTreePure(true)});
|
||||||
deassignp->deleteTree();
|
deassignp->deleteTree();
|
||||||
}
|
}
|
||||||
ForceState state{true};
|
ForceState state{true, helperVars};
|
||||||
{ ForceDiscoveryVisitor{nodep, state}; }
|
{ ForceDiscoveryVisitor{nodep, state}; }
|
||||||
state.finalizeRhsVars();
|
state.finalizeRhsVars();
|
||||||
{ ForceConvertVisitor{nodep, state}; }
|
{ ForceConvertVisitor{nodep, state}; }
|
||||||
|
|
@ -1577,7 +1561,8 @@ static void assignAllImpl(AstNetlist* nodep) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void V3Force::forceAndAssignAll(AstNetlist* nodep) {
|
void V3Force::forceAndAssignAll(AstNetlist* nodep) {
|
||||||
ForceUserSlots userSlots;
|
const VNUser3InUse user3InUse;
|
||||||
forceAllImpl(nodep);
|
ForceState::ForceHelperVarsByVar helperVars;
|
||||||
assignAllImpl(nodep);
|
forceAllImpl(nodep, helperVars);
|
||||||
|
assignAllImpl(nodep, helperVars);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ module child (
|
||||||
input wire i
|
input wire i
|
||||||
);
|
);
|
||||||
/*verilator no_inline_module*/
|
/*verilator no_inline_module*/
|
||||||
|
|
||||||
|
logic [2:0] unpacked[2] = '{3'b001, 3'b010};
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
module t;
|
module t;
|
||||||
|
|
@ -16,10 +18,13 @@ module t;
|
||||||
|
|
||||||
initial begin
|
initial begin
|
||||||
force a.i = 1'b1;
|
force a.i = 1'b1;
|
||||||
|
force a.unpacked[0] = 3'b111;
|
||||||
|
|
||||||
if (a.i !== 1'b1) $stop;
|
if (a.i !== 1'b1) $stop;
|
||||||
if (b.i !== 1'b0) $stop;
|
if (b.i !== 1'b0) $stop;
|
||||||
if (a.i === b.i) $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");
|
$write("*-* All Finished *-*\n");
|
||||||
$finish;
|
$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