Skip to content

rsz: Add a relative leakage cost to PARALLEL_EVAL cell swaps - #12153

Open
jhkim-pii wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:rsz-pe-relative-cell-cost
Open

jhkim-pii wants to merge 6 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:rsz-pe-relative-cell-cost

Conversation

@jhkim-pii

@jhkim-pii jhkim-pii commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Apply #11916 changes in PARALLEL_EVAL policy.

  • PARALLEL_EVAL SizeUpMt and VtSwapMt divide their scores by a leakage cost relative to the cheapest costlier option of the same move.
  • The PARALLEL_EVAL max-cap screen skips scan-in and scan-enable pins as LEGACY does.

Problem

Issue Symptom
Cell swaps ignore leakage A target takes the largest size or the leakiest VT even when a cheaper option gains about as much, while LEGACY upsizeCell ranks by gain per cost (#11916).
The max-cap screen differs from LEGACY A scan flop that LEGACY sizes up keeps its size, because inputCapBudgets() checks the scan pins that replacementPreservesMaxCap() skips.

Changes

Item Before After
SizeUpMt score gain x 0.75 gain x 0.75 / divisor
VtSwapMt score gain x 1.0 gain x 1.0 / divisor
Cost divisor None max(1, r / r_min) per target and move
Max-cap screen Checks scan pins Skips them through the shared Resizer::skipsFaninMaxCap()
flowchart LR
  T["Target cell<br/>r = new leakage / old leakage"] --> S["SizeUpMt options<br/>r = 0.9, 1.3, 1.8<br/>r_min = 1.3"]
  T --> V["VtSwapMt options<br/>r = 0.8, 4, 8<br/>r_min = 4"]
  S --> SD["divisor = max(1, r / r_min)<br/>→ 1, 1, 1.38"]
  V --> VD["divisor = max(1, r / r_min)<br/>→ 1, 1, 2"]
  SD --> P["score = weight x net gain / divisor<br/>weight: SizeUp 0.75, VtSwap 1.0"]
  VD --> P
Loading

Related

Details
  • r is the new/old leakage of an option (else LEF area, else Liberty area), and r_min is the smallest r above 1 among the target's options of the same move.
  • Dividing by r_min gives the option with the smallest leakage increase no penalty (divisor 1).
  • Only positive scores are divided, and no divisor is below 1, so the cost never raises a score.
  • A raw leakage ratio cannot be shared by the two moves, because the median ASAP7 ratio is 9.6x for a VT swap and 1.29x for a size-up, so a raw-ratio cost removes most VT swaps.
  • The move weights move from the candidates into OptimizationPolicyConfig::scoreScale() and are applied in the policy, while each move applies its own divisor.
  • An earlier revision retuned the weights to SizeUpMt 0.60 and VtSwapMt 1.25, and this revision restores the existing weights.
  • The existing weights give lower leakage at about the same Fmax, and SizeUpMt 0.60 stalled setup repair of ihp-sg13g2 ibex at -165 ps WNS where master closes.

PARALLEL_EVAL ranked SizeUpMt and VtSwapMt candidates by timing gain
alone, while SizeUpGenerator::upsizeCell (LEGACY, The-OpenROAD-Project#11916) already picks
the size with the best gain per unit of cost.  Without a cost, a target
could take the largest size or jump over several VTs when a cheaper
option gave a similar gain.

A raw leakage ratio cannot be shared between the two moves: in ASAP7
one VT step multiplies a cell's leakage by about 10x and one size step
by about 1.3x, so dividing the scores by the raw ratio drops VT swaps
from the shared pool.

What and how:
- MoveGenerator::cellCostRatio() returns the new/old cell cost:
  leakage, else LEF area, else Liberty area, as upsizeCell compares
  them.  It runs on the main thread because cellLeakage() fills a
  cache.
- relativeCellCosts() turns the ratios of one move's options for a
  target into score divisors max(1, r / r_min), where r_min is the
  smallest ratio above 1.  The cheapest costlier option keeps its full
  score, a costlier one pays only its extra cost, and no divisor drops
  below 1, so the cost never raises a score.
- SizeUpMtGenerator and VtSwapMtGenerator find the ratios in
  prepareInputs(), cache them per library cell, and pass the divisors
  of the options that fit the max-cap budgets to the candidates, which
  divide positive scores by them after the side costs.
- As the cost only orders the options of a move, the move weights set
  the balance between moves: VtSwapMt gets its own weight
  (vt_swap_mt_score_scale = 1.25) and SizeUpMt goes from 0.75 to 0.60,
  tuned on post-placement setup repair benchmarks.
- Resizer::inputCapBudgets() now skips ideal register clock pins and
  scan pins the same way as replacementPreservesMaxCap(), through the
  new skipsFaninMaxCap(), so the generation-time max-cap screen matches
  the apply-time check.
- Add unit tests for the move weights, the divisors and
  relativeCellCosts(), and update the PARALLEL_EVAL goldens.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
The SizeUpMt and VtSwapMt generators screen each replacement with
Resizer::inputCapBudgets().  Before skipsFaninMaxCap() was shared, that
screen did not skip the scan-enable and scan-in pins as
replacementPreservesMaxCap() does, so PARALLEL_EVAL kept a scan flop at
its size when its scan pin drivers had no max-cap headroom, while
LEGACY sized it up.  No test covered the PARALLEL_EVAL side.

repair_setup_parallel_eval_fanin_max_cap sizes up three sky130hd
sdfxbp_1 flops with -sequence sizeup.  sdfxbp_2 has larger CLK, SCE and
SCD pins, and output port loads push the driver of each tested pin past
its max cap:
- ff_scan, on its SCE and SCD drivers, sizes up as in LEGACY.  Without
  the scan pin skip it keeps sdfxbp_1.
- ff_ideal, on its ideal clock driver, sizes up: STA does not check the
  max cap of an ideal clock net.
- ff_prop, on its propagated clock driver, keeps sdfxbp_1.  With
  headroom on that driver it sizes up.

Every flop input has a gate driver because the flop estimator replays
a pin cap change only through a gate model, and a hold false path keeps
the late propagated clock from blocking ff_prop for another reason.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
Five Mt candidates took their move weight (*_mt_score_scale) through
the constructor and multiplied their own score by it, so the
candidates, their generators and chargeCellSwapSideCosts() all carried
the same plumbing.  The weights only balance the moves in the policy's
shared score pool, so the policy now owns them.

- OptimizationPolicyConfig::scoreScale() maps a MoveType to its weight,
  1 for SwapPinsMt, UnbufferMt and the moves without one.  The switch
  has no default, so a new move type must pick a weight.
- SetupParallelEvalPolicy::estimateTarget() multiplies each estimate by
  the weight of its move right after estimate().  It is static and runs
  on the workers, so it takes the policy config as an argument.
- The candidates score their own gain and cost.  SizeUpMt and VtSwapMt
  still divide a positive score by their relative cell cost inside the
  move; SplitLoadMt, SizeDownFanoutMt and RebufferMt drop the weight
  member and constructor parameter, and the generators stop passing it.
  The RebufferMt debug message prints the unweighted score.
- The candidate unit test checks only the cell cost.  New tests check
  the weight of each move type and that estimateTargets() applies it.

The weight now follows the cost divisor, (gain / divisor) x weight
instead of (gain x weight) / divisor.  The rsz goldens and the QoR and
move counts of 34 post-placement benchmark designs are unchanged.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
@jhkim-pii jhkim-pii self-assigned this Nov 6, 2026
@github-actions github-actions Bot added the size/L label Nov 6, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the parallel evaluation setup policy in the Resizer to scale candidate scores dynamically based on relative cell costs (leakage, LEF area, or Liberty area) rather than static move-specific scales. It also centralizes the fanin max-cap exemption logic and applies move weights at the policy level. Feedback on the changes suggests optimizing MoveGenerator::cellCostRatio by caching the costs of the source cell to avoid redundant computations inside generator loops.

Comment thread src/rsz/src/move/MoveGenerator.cc Resolved
buildSizeUpOptions() and buildVtSwapOptions() called cellCostRatio()
for every candidate cell, and each call looked up the costs of the
current cell again, although that cell is the same for the whole loop.

MoveGenerator::cellCosts() now returns a cell's costs (leakage, LEF
area, Liberty area), and cellCostRatio() takes the current cell's costs
with the candidate cell.  Each builder finds the current cell's costs
once before its loop, as SizeUpGenerator::upsizeCell already does.  The
ratios do not change.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
The relative leakage cost came with retuned move weights, VtSwapMt 1.25
and SizeUpMt 0.60.  On 29 public designs at the nominal clock period and
at 98% and 95% of it, the existing weights (VtSwapMt 1.0, SizeUpMt 0.75)
with the relative cost give lower leakage at every period (-2.34%,
-1.33% and -1.45% against master, versus -1.33%, -1.23% and -1.17%) and
about the same Fmax.  They also keep ihp-sg13g2 ibex closing as master
does: with SizeUpMt at 0.60, other moves win the targets that size-ups
fix on master, and setup repair stalls at -165 ps WNS.

Restore the existing weights in OptimizationPolicyConfig, note why a
VtSwapMt weight above 1 is not wanted, and update the PARALLEL_EVAL
goldens.  The max_util golden matches master again.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
Resolve the VtSwapMtGenerator conflict with the VT candidate cache of
master: the cost ratios join the cached candidate cells, which
isApplicable() fills on the main thread, and the separate cache that
prepareInputs() filled is dropped.

Signed-off-by: Jaehyun Kim <jhkim@precisioninno.com>
@jhkim-pii
jhkim-pii marked this pull request as ready for review November 8, 2026 21:53
@jhkim-pii
jhkim-pii requested a review from a team as a code owner November 8, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant