Optimize concatenations that produce unused bits in DFG (#6971)

Concatenations that are only used by Sel expressions that do not consume
some bits on the edges can be narrowed to not compute the unused bits.

E.g.: `{a[4:0], b[4:0]}[5:4]` -> `{a[0], b[4]}[1:0]`

This is a superset or the PUSH_SEL_THROUGH_CONCAT DFG pattern, which is
removed.
This commit is contained in:
Geza Lore 2026-01-31 17:28:14 +00:00 committed by GitHub
parent 0915ae6ba8
commit d9234501e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 87 additions and 24 deletions

View File

@ -30,6 +30,7 @@
#include "V3Stats.h"
#include <cctype>
#include <vector>
VL_DEFINE_DEBUG_FUNCTIONS;
@ -800,29 +801,6 @@ class V3DfgPeephole final : public DfgVisitor {
DfgSel* const replacementp = make<DfgSel>(vtxp, lhsp, lsb - rhsp->width());
replace(vtxp, replacementp);
}
} else if (!concatp->hasMultipleSinks()) {
// If the select straddles both sides, the Concat has no other use,
// then push the Sel past the Concat
APPLYING(PUSH_SEL_THROUGH_CONCAT) {
const uint32_t rSelWidth = rhsp->width() - lsb;
const uint32_t lSelWidth = width - rSelWidth;
// The new Lhs vertex
DfgSel* const newLhsp
= make<DfgSel>(flp, DfgDataType::packed(lSelWidth), lhsp, 0U);
// The new Rhs vertex
DfgSel* const newRhsp
= make<DfgSel>(flp, DfgDataType::packed(rSelWidth), rhsp, lsb);
// The replacement Concat vertex
DfgConcat* const newConcat
= make<DfgConcat>(concatp->fileline(), vtxp->dtype(), newLhsp, newRhsp);
// Replace this vertex
replace(vtxp, newConcat);
return;
}
}
}
@ -1327,6 +1305,77 @@ class V3DfgPeephole final : public DfgVisitor {
}
}
}
// Attempt to narrow a concatenation that produces unused bits on the edges
{
const uint32_t vMsb = vtxp->width() - 1; // MSB of the concatenation
const uint32_t lLsb = vtxp->rhsp()->width(); // LSB of the LHS
const uint32_t rMsb = lLsb - 1; // MSB of the RHS
// Check each sink, and record the range of bits used by them
uint32_t lsb = vMsb; // LSB used by a sink
uint32_t msb = 0; // MSB used by a sink
std::vector<DfgVertex*> sinkps;
bool hasCrossSink = false; // True if some sinks use bits from both sides
vtxp->foreachSink([&](DfgVertex& sink) {
sinkps.emplace_back(&sink);
// Record bits used by DfgSel sinks
if (const DfgSel* const selp = sink.cast<DfgSel>()) {
const uint32_t selLsb = selp->lsb();
const uint32_t selMsb = selLsb + selp->width() - 1;
lsb = std::min(lsb, selLsb);
msb = std::max(msb, selMsb);
hasCrossSink |= selMsb >= lLsb && rMsb >= selLsb;
return false;
}
// Ignore non observable variable sinks. These will be eliminated.
if (const DfgVarPacked* const varp = sink.cast<DfgVarPacked>()) {
if (!varp->hasSinks() && !varp->isObserved()) return false;
}
// Otherwise the whole value is used
lsb = 0;
msb = vMsb;
return true;
});
// If not all bits are used, narrow the concatenation, but only if at least
// one select straddles both sides (DfgSel paterns will handle the rest).
if ((vMsb > msb || lsb > 0) && hasCrossSink) {
APPLYING(NARROW_CONCAT) {
FileLine* const flp = vtxp->fileline();
// Compute new RHS
DfgVertex* const rhsp = vtxp->rhsp();
const uint32_t rWidth = rMsb - lsb + 1;
DfgVertex* const newRhsp
= rWidth == rhsp->width()
? rhsp
: make<DfgSel>(flp, DfgDataType::packed(rWidth), rhsp, lsb);
// Compute new LHS
DfgVertex* const lhsp = vtxp->lhsp();
const uint32_t lWidth = msb - lLsb + 1;
DfgVertex* const newLhsp
= lWidth == lhsp->width()
? lhsp
: make<DfgSel>(flp, DfgDataType::packed(lWidth), lhsp, 0);
// Create the new concatenation
DfgConcat* const newConcat = make<DfgConcat>(
flp, DfgDataType::packed(msb - lsb + 1), newLhsp, newRhsp);
// Replace Sel sinks
for (DfgVertex* const sinkp : sinkps) {
if (DfgSel* const selp = sinkp->cast<DfgSel>()) {
replace(selp, make<DfgSel>(selp, newConcat, selp->lsb() - lsb));
}
}
// Also need to replace the concatenation itself, otherwise this pattern
// will match again and iteration won't terminate. This vertex is now
// effectively unused, so replace with zero.
replace(vtxp, makeZero(flp, vtxp->width()));
return;
}
}
}
}
void visit(DfgDiv* vtxp) override {

View File

@ -35,6 +35,7 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_UNARY) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_SPLICE) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, INLINE_ARRAYSEL_UNIT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, NARROW_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PULL_NOTS_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_OP_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_BITWISE_THROUGH_REDUCTION) \
@ -43,7 +44,6 @@
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_NOT_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_REDUCTION_THROUGH_COND_WITH_CONST_BRANCH) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_CONCAT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_COND) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_NOT) \
_FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, PUSH_SEL_THROUGH_REPLICATE) \

View File

@ -140,6 +140,14 @@ public:
bool hasExtWrRefs() const { return hasExtWrRefs(nodep()); }
bool hasExtRefs() const { return hasExtRdRefs() || hasExtWrRefs(); }
// True iff the value of this variable is read outside this DfgGraph
bool isObserved() const {
// A DfgVarVertex is written in exactly one DfgGraph, and might be read in an arbitrary
// number of other DfgGraphs. If it's driven in this DfgGraph, it's read in others.
if (hasDfgRefs()) return srcp() || defaultp();
return hasExtRdRefs() || hasModRdRefs();
}
// The value of this vertex might differ from what is defined by its drivers
// 'srcp' and 'defaultp'. That is, it might be assigned, possibly partially,
// or abruptly outside the graph, hence it is not equivalent to its 'srcp'.

View File

@ -264,6 +264,12 @@ module t (
wire sel_from_not = sel_from_not_tmp[2];
always @(posedge randbit_a) if ($c(0)) $display(sel_from_not); // Do not remove signal
// Narrow concatenation
wire [9:0] narrow_concat = {5'd0, ~rand_a[44 +: 5]};
`signal(NARROW_CONCAT_A, narrow_concat[5:1]);
`signal(NARROW_CONCAT_B, narrow_concat[8:4]);
`signal(NARROW_CONCAT_C, narrow_concat[5:4]);
// Assigned at the end to avoid inlining by other passes
assign const_a = 64'h0123456789abcdef;
assign const_b = 64'h98badefc10325647;