From fb617e49dd064c808cac15242a2241a3701c0cb7 Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Thu, 14 May 2026 22:46:57 +0200 Subject: [PATCH] Optimize read selects with no overlapping forces with regular reads (#7594) --- src/V3Force.cpp | 71 +++++++++++++++++++++--------- test_regress/t/t_force_wide_sel.py | 4 +- test_regress/t/t_force_wide_sel.v | 14 ++++++ 3 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/V3Force.cpp b/src/V3Force.cpp index 5731f53d4..f3dc9ede6 100644 --- a/src/V3Force.cpp +++ b/src/V3Force.cpp @@ -37,6 +37,7 @@ #include "V3Force.h" #include "V3AstUserAllocator.h" +#include "V3Stats.h" #include "V3UniqueNames.h" VL_DEFINE_DEBUG_FUNCTIONS; @@ -611,6 +612,13 @@ public: return it2->second; } + static bool selOverlapsAnyForce(const VarForceInfo& varInfo, int selLsb, int selMsb) { + for (const auto& pair : varInfo.m_forces) { + if (pair.second.m_rangeLsb <= selMsb && pair.second.m_rangeMsb >= selLsb) return true; + } + return false; + } + AstNodeExpr* createForceReadExpression(const VarForceInfo& varInfo, AstVarRef* originalRefp) const { FileLine* const flp = originalRefp->fileline(); @@ -1044,6 +1052,7 @@ public: class ForceReplaceVisitor final : public VNVisitor { const ForceState& m_state; + VDouble0 m_nonOverlappingForceSels; // Statistic tracking AstNodeStmt* m_stmtp = nullptr; bool m_inLogic = false; @@ -1090,30 +1099,45 @@ class ForceReplaceVisitor final : public VNVisitor { void visit(AstSenItem* nodep) override { iterateLogic(nodep); } void visit(AstSel* nodep) override { // Replace Sel on a wide with readSelI/Q/W to avoid materializing the full value - if (AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef)) { - if (!ForceState::isNotReplaceable(refp) && refp->access().isReadOnly()) { - AstVar* const varp = refp->varp(); - const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); - if (varInfo && !varInfo->m_forceRdVscp && !varInfo->m_forces.empty() - && ForceState::isBitwiseDType(varp) && varp->dtypep()->isWide()) { - FileLine* const flp = nodep->fileline(); - ForceState::markNonReplaceable(refp); - AstVarRef* const refClonep = refp->cloneTreePure(false); - ForceState::markNonReplaceable(refClonep); - AstCMethodHard* const callp = new AstCMethodHard{ - flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::READ}, - VCMethod::FORCE_READ_SEL, ForceState::makeConst32(flp, varp->width())}; - callp->addPinsp(refClonep); - callp->addPinsp(nodep->lsbp()->cloneTreePure(false)); - callp->addPinsp(ForceState::makeConst32(flp, nodep->width())); - callp->dtypeFrom(nodep); - nodep->replaceWith(callp); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - return; - } + AstVarRef* const refp = VN_CAST(nodep->fromp(), VarRef); + if (!refp || ForceState::isNotReplaceable(refp) || !refp->access().isReadOnly()) { + visit(static_cast(nodep)); + return; + } + + AstVar* const varp = refp->varp(); + const ForceState::VarForceInfo* const varInfo = m_state.getVarInfo(varp); + if (!varInfo || varInfo->m_forceRdVscp || varInfo->m_forces.empty() + || !ForceState::isBitwiseDType(varp) || !varp->dtypep()->isWide()) { + visit(static_cast(nodep)); + return; + } + + if (const AstConst* const lsbConstp = VN_CAST(nodep->lsbp(), Const)) { + const int selLsb = lsbConstp->toSInt(); + const int selMsb = selLsb + nodep->width() - 1; + if (!varp->isSigPublic() + && !ForceState::selOverlapsAnyForce(*varInfo, selLsb, selMsb)) { + m_nonOverlappingForceSels++; + ForceState::markNonReplaceable(refp); + visit(static_cast(nodep)); + return; } } - visit(static_cast(nodep)); + + FileLine* const flp = nodep->fileline(); + ForceState::markNonReplaceable(refp); + AstVarRef* const refClonep = refp->cloneTreePure(false); + ForceState::markNonReplaceable(refClonep); + AstCMethodHard* const callp = new AstCMethodHard{ + flp, new AstVarRef{flp, varInfo->m_forceVecVscp, VAccess::READ}, + VCMethod::FORCE_READ_SEL, ForceState::makeConst32(flp, varp->width())}; + callp->addPinsp(refClonep); + callp->addPinsp(nodep->lsbp()->cloneTreePure(false)); + callp->addPinsp(ForceState::makeConst32(flp, nodep->width())); + callp->dtypeFrom(nodep); + nodep->replaceWith(callp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstArraySel* nodep) override { if (nodep->backp() && VN_IS(nodep->backp(), ArraySel)) { @@ -1236,6 +1260,9 @@ public: : m_state{state} { iterateAndNextNull(nodep->modulesp()); } + ~ForceReplaceVisitor() override { + V3Stats::addStat("Non-overlapping force sels", m_nonOverlappingForceSels); + } }; //###################################################################### // diff --git a/test_regress/t/t_force_wide_sel.py b/test_regress/t/t_force_wide_sel.py index 8a938befd..19ed22916 100755 --- a/test_regress/t/t_force_wide_sel.py +++ b/test_regress/t/t_force_wide_sel.py @@ -11,7 +11,9 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=["--stats"]) + +test.file_grep(test.stats, r'Non-overlapping force sels\s+(\d+)', 2) test.execute() diff --git a/test_regress/t/t_force_wide_sel.v b/test_regress/t/t_force_wide_sel.v index 2b88c7a14..b83ef8c29 100644 --- a/test_regress/t/t_force_wide_sel.v +++ b/test_regress/t/t_force_wide_sel.v @@ -14,16 +14,20 @@ module t ( ); integer cyc = 0; logic [127:0] sig; + logic [127:0] publicSig /*verilator public_flat_rw*/; always @(posedge clk) begin cyc <= cyc + 1; sig <= '0; + publicSig <= '0; sig[127:64] <= '0; // write path if (cyc == 1) begin force sig[31] = 1'b1; force sig[32] = 1'b1; + force publicSig[31] = 1'b1; + force publicSig[32] = 1'b1; end else if (cyc == 3) begin `checkh(sig[33:26], 8'h60); // width <= 8 @@ -35,6 +39,16 @@ module t ( `checkh(sig[73:10], 64'h600000); `checkh(sig[100:5], (96'h1 << 26) | (96'h1 << 27)); // width > 64 `checkh(sig[70:6], (65'h1 << 25) | (65'h1 << 26)); + + `checkh(publicSig[33:26], 8'h60); // width <= 8 + `checkh(publicSig[39:24], 16'h180); // 8 < width <= 16 + `checkh(publicSig[40:20], 21'h1800); // 16 < width <= 32 + `checkh(publicSig[51:20], 32'h1800); + `checkh(publicSig[29:0], 30'h0); + `checkh(publicSig[50:10], 41'h600000); // 32 < width <= 64 + `checkh(publicSig[73:10], 64'h600000); + `checkh(publicSig[100:5], (96'h1 << 26) | (96'h1 << 27)); // width > 64 + `checkh(publicSig[70:6], (65'h1 << 25) | (65'h1 << 26)); $write("*-* All Finished *-*\n"); $finish; end