diff --git a/src/V3Force.cpp b/src/V3Force.cpp index a64cdaaf7..e832e2a74 100644 --- a/src/V3Force.cpp +++ b/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 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 __VforceRd helper - // AstVar::user4p() -> AstVarScope*: Generated __VforceEn helper - // AstVarScope::user3p() -> AstVarScope*: Generated __VforceVal helper + // AstVar::user3 -> ForceHelperVars via m_forceHelperVarsByVar const VNUser1InUse m_user1InUse; const VNUser2InUse m_user2InUse; +public: + using ForceHelperVarsByVar = AstUser3Allocator; + +private: + ForceHelperVarsByVar& m_forceHelperVarsByVar; std::vector m_varInfos; // Indexed by stable variable ID std::unordered_map m_varToId; std::unordered_set 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); } diff --git a/test_regress/t/t_force_scope.v b/test_regress/t/t_force_scope.v index 7b24ad78e..7f872c1c0 100644 --- a/test_regress/t/t_force_scope.v +++ b/test_regress/t/t_force_scope.v @@ -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; diff --git a/test_regress/t/t_force_scope_public.py b/test_regress/t/t_force_scope_public.py new file mode 100755 index 000000000..6b416c476 --- /dev/null +++ b/test_regress/t/t_force_scope_public.py @@ -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()