Skip to content

opt_balance_tree: -sliced-profit keeps a nest the tree would deepen - #300

Closed
akashlevy wants to merge 1 commit into
mainfrom
qor/sliced-add-profit
Closed

opt_balance_tree: -sliced-profit keeps a nest the tree would deepen#300
akashlevy wants to merge 1 commit into
mainfrom
qor/sliced-add-profit

Conversation

@akashlevy

Copy link
Copy Markdown

Summary

The sliced-$add matcher in opt_balance_tree flattens a nest of adders into a
flat summand list and rebuilds it as a balanced tree. That pays when the
summands were already crossing adders, but the extraction also splits out leaf
slices that the nest merely concatenated into one operand. Those slices cost
nothing where they are and a full-width adder each in the tree, so flattening
them adds carry-propagate levels instead of removing them.

Concretely, x + (c ? {x[14:0], b} : 0) is one 18-bit CPA in the nest. The 1-bit
b is the output of a narrow $add, so the matcher reads it as a fourth summand
and emits a two-level tree: two 17-bit CPAs in series.

-sliced-profit compares the worst-case carry depth of both shapes -- the summed
widths of the adders a summand crosses -- and keeps the nest when the tree is
deeper. The estimate mirrors create_balanced_tree()'s own split, so it scores
the shape that would actually be emitted.

The option is default-off: without it the pass behaves exactly as before.

Test plan

  • tests/opt/opt_balance_tree.ys test 35 (new): the concatenated-carry nest
    flattens into three adds by default and is left alone under
    -sliced-profit, with equiv_opt -assert on the guarded run.
  • tests/opt/opt_balance_tree.ys test 36 (new): the existing sliced shifted
    ADD chain (test 31) still flattens to add3_tree_3 under -sliced-profit.
  • Measured in Preqorsor on the design this came from: lol 27.75 -> 23.0 and
    area 55.0 -> 53.7, with the flag off bit-identical to a pristine build.
  • qor_vmw_add_tree{,_v1..v5}, which exist to exercise this matcher, are
    unchanged in lol; the guard only declines the redundant second flatten of
    an already-built tree.

Made with Cursor

The sliced-$add matcher flattens a nest of adders into summands and
rebuilds it as a balanced tree, which pays when those summands were
already crossing adders. It also splits out leaf slices that the nest
merely concatenated into one operand: those cost nothing where they are
and a full-width adder each in the tree.

A conditional {x[14:0], c} added to x is one 18-bit CPA in the nest and
two 17-bit CPAs after flattening, because the 1-bit c is read as a fourth
summand. Under -sliced-profit the matcher compares the worst-case carry
depth (summed adder widths) of both shapes and keeps the nest when the
tree is deeper.

Default-off, so the pass is unchanged without the option.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds an optional profitability guard that compares carry depths before flattening sliced add nests.

  • Threads accumulated nest costs through sliced-add extraction.
  • Models the exact balanced-tree split and rejects transformations predicted to deepen the carry path.
  • Adds regression tests for rejected and accepted sliced-add shapes, although the accepted-shape test contradicts the implemented cost calculation.

Confidence Score: 4/5

The PR should not merge until Test 36 is corrected or its input is changed to a genuinely profitable sliced tree.

The new guard computes Test 36's balanced tree as deeper than its nest and therefore cannot produce the structure asserted by the added regression test.

Files Needing Attention: tests/opt/opt_balance_tree.ys

Important Files Changed

Filename Overview
passes/opt/opt_balance_tree.cc Adds the default-off carry-depth profitability guard; its recurrence consistently models the emitted balanced tree.
tests/opt/opt_balance_tree.ys Adds two regression cases, but Test 36 expects flattening even though its tree computes deeper than its nest.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Extract sliced add summands] --> B[Compute maximum nested carry depth]
    A --> C[Compute balanced-tree carry depth]
    B --> D{Tree depth <= nest depth?}
    C --> D
    D -->|Yes| E[Flatten into balanced tree]
    D -->|No| F[Keep sliced nest]
    F --> G[Test 36 structural assertion fails]
Loading

Reviews (1): Last reviewed commit: "opt_balance_tree: -sliced-profit keeps a..." | Re-trigger Greptile

Comment thread tests/opt/opt_balance_tree.ys
@akashlevy

Copy link
Copy Markdown
Author

Heads up that the design cited in the test plan no longer justifies this option,
but a different one does -- so this should not be closed, it should be re-based
on new evidence.

qor_case_shift_path5 gets its whole win from #305 instead. The 1-bit
hic ^ xbase[0] there is an $add that discards its own carry, which is exactly
what #305's guard refuses, so that design reaches lol 23.0 / area 53.7 with
-sliced-profit nowhere in the flow. (Silimate/preqorsor#2286 has been
simplified accordingly: it is now just the regression, with no synthesis.py
change.)

The two guards are close to disjoint, though. #305 refuses a child that drops its
carry; this one refuses a nest that is sound to flatten but deeper as a tree. A
child that keeps its carry passes #305 untouched and lands squarely in the
shape this option exists for:

module test (
  input  wire clk,
  input  wire  [7:0] a_in, b_in, input wire [3:0] lo_in, input wire [17:0] c_in,
  output reg  [17:0] y
);
  reg [7:0] a, b; reg [3:0] lo; reg [17:0] c;
  always @(posedge clk) begin a <= a_in; b <= b_in; lo <= lo_in; c <= c_in; end
  wire [8:0] s = a + b;            // 9 bits over 8-bit operands: carry kept
  always @(posedge clk) y <= {s, lo} + c;
endmodule

Built #305 and this PR separately on current origin/main and measured in
Preqorsor (asap7, speed 1):

build lol area sliced nest
pristine 16.50 35.55 flattened
#305 only 16.50 35.55 flattened
this PR, -sliced-profit 13.50 29.10 kept

#305 is bit-identical to pristine, as expected, while -sliced-profit takes
3.0 lol (18%) and 18% of the area. At the pass level: #305 rewrites the
2-adder nest into a 3-adder tree, this PR leaves the 2 adders alone.

Something along those lines would give the option a regression that #305 cannot
absorb. Happy to hand over the design if useful.

@akashlevy

Copy link
Copy Markdown
Author

Completing the picture from my last comment: I censused which of our regressions
actually reach this matcher, and the answer sharpens the ask. 32 designs run
through a #305 build, counting sliced-nest flattens:

case flattens, #305 flattens, -sliced-profit lol area
qor_vmw_add_tree (+_v1.._v5) 2 1 48.00 -> 48.00 678.12 -> 678.61
qor_addr_almost_barrel 15 0 11.00 -> 11.00 100.59 -> 100.59
qor_case_shift_path5 0 (carry guard refuses) -- -- --

The other 24 never flatten a sliced nest, so neither PR can reach them.

Two things worth knowing before merge:

  1. The option is lol-neutral on every design we have that exercises it, and
    slightly area-negative on the add_tree family (+0.49, +0.07%). So the test
    plan's "unchanged in lol" holds, but there is currently no case in the repo
    that would fail if the option were dropped -- the only win I can measure is
    the synthetic carry-complete nest from my previous comment (16.50 -> 13.50
    lol, 35.55 -> 29.10 area).

  2. qor_addr_almost_barrel flattens 15 sliced nests for exactly zero effect,
    lol and area byte-identical whether they happen or not. That is independent
    of this PR, but it does suggest the matcher fires much more often than it
    pays, which is the premise this option is built on and the best argument for
    it.

None of that argues for closing -- the mechanism is sound and the residual set is
real. It argues that the PR wants a regression built on a carry-complete child,
since the design it currently cites gets its win from #305 instead. Offer stands
to hand over the testcase.

@akashlevy

Copy link
Copy Markdown
Author

Closing for now. The mechanism is sound and the residual set is real, but it does
not currently buy us anything measurable:

  • The design this was justified with, qor_case_shift_path5, gets its whole win
    from opt_balance_tree: do not reassociate across a discarded carry #305 instead (lol 27.75 -> 23.0, area 55.0 -> 53.7 with no
    -sliced-profit anywhere). Silimate/preqorsor#2286 has been re-based on opt_balance_tree: do not reassociate across a discarded carry #305
    and is now just the regression.
  • Across 32 regressions, the seven that reach this matcher --
    qor_vmw_add_tree{,_v1..v5} and qor_addr_almost_barrel -- are all lol-neutral
    under the option, and the add_tree family is marginally area-negative
    (+0.07%). Nothing in the repo would fail if the option did not exist.
  • The only win I could measure is a synthetic carry-complete nest (lol
    16.50 -> 13.50), which is not enough on its own to carry a new pass option.

Worth reopening if a real design turns up that flattens a carry-complete nest and
pays for it. The observation that qor_addr_almost_barrel flattens 15 sliced
nests for byte-identical lol and area stands on its own and may be the better
thread to pull -- the matcher firing that often for no gain is a separate
question from how to decline it.

@akashlevy akashlevy closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant