Skip to content

opt_accum_enable: fold a zero-gated register update into the enable - #297

Merged
akashlevy merged 2 commits into
mainfrom
qor/accum-enable
Aug 28, 2026
Merged

akashlevy merged 2 commits into
mainfrom
qor/accum-enable

Conversation

@akashlevy

Copy link
Copy Markdown

What

New pass opt_accum_enable. When a register's next value is q OP z ($add,
$sub, $or, $xor) and some condition holds z at zero, the register keeps
its value while that condition is off. That makes the condition a hold, not
arithmetic, so it belongs on the register's enable rather than in front of the
adder. The pass drops the zeroing gate from the datapath and ANDs its condition
onto the enable.

The win is that the condition's whole cone -- typically a wide compare -- leaves
the path into the update, and only a one-bit term lands on the enable. It also
exposes the register to clock gating.

Why it is sound

  • The gate must be a $mux with one constant-zero arm, or an $and whose mask
    is a full-width replication of one bit (a narrower mask also clears high bits
    that a bypass would not reproduce).
  • Every net the gate feeds must be read only by that one accumulate, so the
    values that change once the gate is gone are not observable anywhere else.
  • The zero is propagated forward through the network between gate and operator
    (one-hot spray, or/xor merge, select, shift), so the gate does not have to sit
    next to the operator.
  • Registers whose enable already gates a sync reset, and async-load or clockless
    registers, are refused.

Tests

tests/opt/opt_accum_enable.ys. The update the gate used to zero becomes an
observability don't-care once the register holds, so gold and gate deliberately
differ on internal nodes and only the module outputs agree; positive groups
therefore prove equivalence with a sequential miter (sat -tempinduct) rather
than equiv_opt. Seven proofs, six negatives:

  • A: gate next to the operator, gate behind a zero-preserving network, $and
    mask instead of a mux, and $sub/$or/$xor.
  • B: gated value read outside the accumulate, non-zero off arm, update that is
    not q OP z, sum observed combinationally, partial-width mask, gate shared by
    two accumulators.
  • C: existing enable (ANDed, not replaced), sync reset, zero on the true arm.

Landing

Nothing in Yosys invokes the pass, so this is a no-op on its own and safe to
merge first. The Preqorsor half (Silimate/preqorsor) adds the invocation and a
regression; its CI will fail on Unknown command until this merges and a wheel
ships.

Measured there on a 64-bit counter whose increment is zeroed by a compare
against the counter's own high half: lol 34.75 -> 26.5, clk 486 -> 377, area
100 -> 97. An A/B over 13 other qor designs (barrel shifters, compressor trees,
carry-save, compare trees, prefix trees, accumulate loops) is bit-identical on
lol/clk/area -- the pass never fires on them.

Made with Cursor

When a register's next value is `q OP z` for an identity operator and some
condition holds `z` at zero, the register keeps its value: the condition is a
hold, not arithmetic, so it belongs on the enable rather than in front of the
adder. Folding it takes the condition's whole cone -- typically a wide compare
-- off the path into the update and leaves a one-bit term on the enable.

The rewrite only fires when every net the gate feeds is read by that one
accumulate, so dropping the gate cannot be observed anywhere else, and the
zero is traced forward through the network in between rather than only across
the cell next to the operator.

Tests prove the positive cases with a sequential miter (the zeroed update is
an observability don't-care once the register holds, so gold and gate differ
on internal nodes and only the outputs agree) and pin down the guards: a value
read elsewhere, a non-zero off arm, an update that is not `q OP z`, a sum
observed combinationally, a partial-width mask, and a gate shared by two
accumulators.

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

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown

Greptile Summary

Adds the opt_accum_enable optimization pass, which replaces zero-gated accumulator updates with register-enable conditions while preserving externally observable behavior.

  • Registers the new pass in the CMake component build.
  • Analyzes complete modules for safe, exclusive accumulator cones while considering only selected registers as rewrite candidates.
  • Adds sequential equivalence proofs and negative coverage for unsupported or observable circuit shapes.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
passes/opt/opt_accum_enable.cc Implements gated-accumulator detection, safety analysis, candidate selection, and enable rewriting; the prior selection-scope defect is resolved at the candidate-register boundary.
tests/opt/opt_accum_enable.ys Covers supported accumulator forms, observability guards, shared gates, existing enables, synchronous resets, and inverted gating through structural assertions and sequential equivalence proofs.
passes/opt/CMakeLists.txt Registers the new optimization pass as a build component.

Reviews (2): Last reviewed commit: "opt_accum_enable: honor the cell selecti..." | Re-trigger Greptile

Comment thread passes/opt/opt_accum_enable.cc Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
@akashlevy

Copy link
Copy Markdown
Author

@greptileai review

@akashlevy
akashlevy merged commit 61253e2 into main Aug 28, 2026
11 checks passed
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