Optimize read selects with no overlapping forces with regular reads (#7594)

This commit is contained in:
Artur Bieniek 2026-05-14 22:46:57 +02:00 committed by GitHub
parent 4a1f17e75f
commit fb617e49dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 66 additions and 23 deletions

View File

@ -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<AstNode*>(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<AstNode*>(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<AstNode*>(nodep));
return;
}
}
visit(static_cast<AstNode*>(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);
}
};
//######################################################################
//

View File

@ -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()

View File

@ -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