Skip to content

Fix SCHEMATIC nested-loop energy accounting and estimator FRAM costs - #85

Merged
byeongjee merged 29 commits into
mainfrom
byeongjee/schematic-nested-loop-cost
Aug 24, 2026
Merged

Fix SCHEMATIC nested-loop energy accounting and estimator FRAM costs#85
byeongjee merged 29 commits into
mainfrom
byeongjee/schematic-nested-loop-cost

Conversation

@byeongjee

@byeongjee byeongjee commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

Energy-model corrections for SCHEMATIC's nested-loop accounting and the
assembly estimator's FRAM stack costs, plus a new pass that repairs trip-count
metadata the compiler duplicates when it splits an annotated loop.

SCHEMATIC loop energy

  • Charge nested loop iterations in the shared energy propagation, and charge the
    restore cost in the loop iteration budget.
  • Replace a hand-inlined second copy of both propagation walkers in
    recomputeLoopBodyEnergyOnCFG (~230 lines) with calls to the shared ones. The
    inline copy had already drifted: it skipped only the analyzed loop's own back
    edge, while the shared walker skips every back edge it reaches.
  • Convert estimator block costs to SCHEMATIC's all-NVM convention.

Assembly estimator / FRAM stack

  • Charge FRAM stack accesses (--stack-access-penalty) for the pipelines whose
    linker script places the stack in FRAM (SCHEMATIC, RockClimb), counting only
    memory-accessing stack operands.
  • Charge the same in RockClimb's machine-level energy pass.

Trip-count metadata

  • UnifyAnnotatedLoopPass merges a loop pair that LoopSimplify split apart.
    Loop::setLoopID stamps the annotation on every latch of a multi-latch loop,
    so after separateNestedLoop both halves claim the full trip count. Verified
    on rsa: the latches of while.cond.i and while.cond.i.outer both carry
    !llvm.loop !21 = tripcount.upper 96, so a consumer that multiplies sees
    9216 instead of 96.

MILP liveness

  • Treat promoted allocas as dead at entry, scoped to main — the only function
    AllocaToGlobalPass transforms.

Robustness fixes from review

  • SCHEMATIC now fails loudly when a loop body still does not fit after being
    split: splitLoopBodyAtFixedBoundary reports whether every body path got a
    boundary (a path with no fixed boundary is infeasible, not ignorable), the
    budget is re-checked after the split-and-recompute, and the decision.final*
    diagnostics are recorded after the budget settles rather than before.
  • push/pop/call now also charge their operand's access, so
    push 4(r1) counts two FRAM accesses rather than one.
  • UnifyAnnotatedLoopPass initializes logging like every other pass; without it
    its only diagnostic was dropped whenever it ran first.
  • The RockClimb preprocess test configs declare nvm_access_penalty. The
    compile path genuinely requires it, so the loader still fails loudly on a real
    config that omits it.

Test plan

  • uv run pytest tests/ — 257 passed.
  • Results are being re-measured; results/milp_debug.csv had mixed provenance
    (a full run at 8c8c013 with only the three rsa rows overwritten from a later
    rsa-only rerun) and is being regenerated in full.

The shared propagation walkers costed a path crossing an analyzed inner loop
as a single iteration; only the private copy inside the convergence loop
applied the reference's (n-1)*E_loop charge, so the initial E_loop measurement
never saw it. Fold the charge into the shared walkers (with the reference's
seed-loop exclusion) and drop the private copy. SCHEMATIC O0 rsa at 50uF was
under-costed ~4x; schematicO3 rsa at 5uF is now reported infeasible.
The paper's Algorithm 1 and the reference implementation size the
back-edge checkpoint interval without the restore at the checkpoint that
starts it, so a loop whose single iteration fits only without that cost
is left with an oversized region. Subtract the restore, and when the
corrected budget admits no full iteration, split the body at its first
fixed inner-loop boundary and recompute the body energies.
The run measured sha256.c (sha256_fixed.c was already deleted in 1d2fc69), but
an agent relabeled the output as sha256_fixed to match the stale benchmark list
in summarize.py. Region boundaries and compile times of the CSVs match a fresh
sha256 compile at the board capacitor, not the old sha256_fixed results.
@byeongjee
byeongjee merged commit 1a3913c into main Aug 24, 2026
7 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