From d9234501e0aa1f1a937df11364719ff0628d2df5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 31 Jan 2026 17:28:14 +0000 Subject: [PATCH] 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. --- src/V3DfgPeephole.cpp | 95 +++++++++++++++++++++++++-------- src/V3DfgPeepholePatterns.h | 2 +- src/V3DfgVertices.h | 8 +++ test_regress/t/t_dfg_peephole.v | 6 +++ 4 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 55228a471..6ca05983a 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -30,6 +30,7 @@ #include "V3Stats.h" #include +#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -800,29 +801,6 @@ class V3DfgPeephole final : public DfgVisitor { DfgSel* const replacementp = make(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(flp, DfgDataType::packed(lSelWidth), lhsp, 0U); - - // The new Rhs vertex - DfgSel* const newRhsp - = make(flp, DfgDataType::packed(rSelWidth), rhsp, lsb); - - // The replacement Concat vertex - DfgConcat* const newConcat - = make(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 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()) { + 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()) { + 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(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(flp, DfgDataType::packed(lWidth), lhsp, 0); + + // Create the new concatenation + DfgConcat* const newConcat = make( + flp, DfgDataType::packed(msb - lsb + 1), newLhsp, newRhsp); + + // Replace Sel sinks + for (DfgVertex* const sinkp : sinkps) { + if (DfgSel* const selp = sinkp->cast()) { + replace(selp, make(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 { diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d4f04607f..a0d5ee059 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -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) \ diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index 068e881d9..940afb2d6 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -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'. diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index fa380ac33..c757612e9 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -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;