Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

librelane-ppa

Timing closure and PPA exploration for LibreLane designs.

You have a design that hardens. You do not know how fast it actually runs, which synthesis options are worth using, or which module is eating the critical path. Finding out by hand means editing config.yaml, launching a run, waiting, opening metrics.json, writing the number down, and repeating — for hours.

ppa does that loop for you and shows you the answer.

$ ppa fmax --stage pre
==> closing timing on heichips26_e2spike (stage pre, start 12.0 ns)
  PASS  fmax_pre_00_12.00ns   12.00 ns  85.3 MHz  +0.283 ns ws  104478 um2  15s
  PASS  fmax_pre_01_11.77ns   11.77 ns  85.7 MHz  +0.097 ns ws  104478 um2  15s

  tag                  stage  period  fmax_mhz  setup_ws  setup_vio  area_um2  ok
  -------------------  -----  ------  --------  --------  ---------  --------  ---
  fmax_pre_00_12.00ns  pre    12.00   85.3      +0.283    0          104478    yes
* fmax_pre_01_11.77ns  pre    11.77   85.7      +0.097    0          104478    yes

Fmax 85.7 MHz at 11.77 ns (setup ws +0.097 ns, tag fmax_pre_01_11.77ns)
  re-run with --apply to write CLOCK_PERIOD: 11.767 into config.yaml

Two runs, thirty seconds, done.


The bug this tool exists to avoid

OpenROAD clamps timing__setup__wns at zero.

The moment a design passes, WNS reads 0.000 no matter whether there are 3 ns of headroom left or 3 ps. Every naive Fmax search steers on WNS, sees zero, decides it has converged, and stops at whatever period you happened to start from. You then ship a chip clocked 30 % below what the silicon can do and never find out.

timing__setup__ws is the raw worst slack — positive with headroom, negative without. This tool reads ws everywhere, which is what makes the search work at all, and it is why ppa finds Fmax in two runs instead of a dozen bisection steps.

The same clamp is why ppa reports an Fmax for failing runs too. A run with −1.3 ns of slack still tells you exactly what the design can do; throwing that information away is what makes manual searches slow.

Install

pip install git+https://github.com/OutBlade/librelane-ppa

Or, from a checkout:

pip install -e ".[dev]"

Pure Python, one dependency (PyYAML), works on 3.9+. LibreLane itself is not a Python dependency — ppa shells out to the librelane on your PATH, so run it from inside whatever nix-shell / container your flow already uses.

First thirty seconds

cd your-design/          # anywhere at or below the design directory
ppa info                 # what did it find, and can it run?
ppa fmax --stage pre     # how fast is this design, really?

ppa info finds the config on its own (flow/librelane/config.yaml, openlane/config.json, or macros/*/flow/librelane/config.*), picks up PDK and PDK_ROOT from the environment or from a PDK checkout in the repo, and tells you what is missing before you burn an hour on a run that was never going to start:

$ ppa info
design heichips26_e2spike
  config    .../macros/heichips26_e2spike/flow/librelane/config.yaml
  pdk       ihp-sg13cmos5l
  pdk root  .../IHP-Open-PDK
  clock     10.0 ns
  runs      .../flow/librelane/runs
  database  .../ppa_results.json (12 runs)

  ok everything needed for a run is in place

Commands

command what it does
ppa info show the detected design and check the environment
ppa fmax drive CLOCK_PERIOD to the fastest value that still closes
ppa sweep run a grid of config variants and rank them
ppa run one run with explicit overrides
ppa paths where the critical path actually spends its time
ppa report every recorded run, as a table, Markdown, CSV or JSON
ppa compare the delta between two runs
ppa pareto the speed/area trade-off front
ppa clean remove generated config files

Every command takes --stage, which decides how much of the flow to run:

stage runs to typical cost use it for
synth Yosys seconds area only
pre OpenROAD.STAPrePNR ~15 s ranking options, bracketing Fmax
cts / route the matching OpenROAD step minutes debugging a specific stage
post OpenROAD.STAPostPNR ~1 h the number you actually quote
full the whole Classic flow hours signoff, DRC and LVS included

The intended workflow is explore with pre, confirm with post. Pre-PnR STA on this design lands within 2 % of the post-PnR answer at 1/300th of the cost.

ppa fmax — how fast is it?

ppa fmax --stage pre               # start from the config's CLOCK_PERIOD
ppa fmax --stage post --start 12   # the number you quote in the datasheet
ppa fmax --stage pre --guard 0.3   # keep 300 ps of margin
ppa fmax --stage pre --apply       # write the winning period back into config.yaml

The search is not a bisection. Each run reports how much slack it has, and

next_period = period - slack + guard

is the arrival time of the critical path, i.e. the shortest period that can possibly work. Aiming straight at it converges in two or three runs from either direction — whether the design started out passing or failing. --guard adds a margin you keep on purpose; --tolerance (default 50 ps) decides when squeezing further stops being worth another run.

--apply edits CLOCK_PERIOD in the real config in place and leaves a comment saying where the number came from.

ppa sweep — which options are worth using?

ppa sweep --set "SYNTH_STRATEGY=AREA 0,AREA 2,DELAY 0,DELAY 2" --jobs 2

Repeat --set for a full grid (--set A=1,2 --set B=x,y runs four points). Values containing spaces are fine as long as you quote them. --jobs overlaps runs, which is close to free because LibreLane is mostly single-threaded.

Real output from the e2spike neuromorphic accelerator at 11.77 ns, IHP ihp-sg13cmos5l, slow corner:

SYNTH_STRATEGY setup ws setup vio area µm² verdict
AREA 0 (default) +0.099 0 104 478 best
AREA 2 −3.262 69 103 807 66 MHz, 0.6 % smaller
DELAY 0 −8.680 210 116 372 slower and 11 % bigger
DELAY 2 −8.688 262 113 656 slower and 9 % bigger

The "delay" strategies are 43 % slower and 11 % larger than the default. That is the kind of result worth having before someone spends a week tuning the wrong knob.

ppa paths — where does the time go?

This is the part a slack number cannot tell you. ppa paths parses OpenROAD's max.rpt, throws away the launch and capture clock trees, and maps what is left back onto the RTL hierarchy using the net names that survived synthesis.

$ ppa paths --run fmax_post_00_12.00ns --top 6
==> fmax_post_00_12.00ns, corner nom_slow_1p08V_125C, 885 reported paths

  #  slack   delay   depth  group  from     to
  -  ------  ------  -----  -----  -------  -------
  0  +0.146  11.509  24     clk    _13816_  _14219_
  1  +0.158  11.532  24     clk    _13816_  _14218_
  2  +0.266  11.427  23     clk    _13816_  _14226_

worst path: 11.509 ns through 24 cells
  launched in  U_MICRO_BRAIN_TOP.U_CORE_TOP.U_FC_CTRL
  captured in  U_MICRO_BRAIN_TOP.U_CORE_TOP.U_FC_CTRL

split by kind of cell
  ████████████████████████  6.507 ns  56.5%  repair / buffering inserted by OpenROAD
  ██████████████████░░░░░░  5.002 ns  43.5%  logic cells

split by RTL module
  ████████████████████████ 11.509 ns 100.0%  U_MICRO_BRAIN_TOP.U_CORE_TOP.U_FC_CTRL

slowest cells on the path
   1.978 ns  _09873_/Y      sg13cmos5l_nand4_1         fanout 5
   0.958 ns  _09928_/Y      sg13cmos5l_nor2_1          fanout 1
   0.785 ns  fanout148/X    sg13cmos5l_buf_1           fanout 8
   0.603 ns  hold2598/X     sg13cmos5l_dlygate4sd3_1   fanout 1

Two actionable facts fall out of that in one command: the entire critical path lives inside a single module, and 56 % of it is buffering the tool inserted, not logic anybody wrote. Both point at concrete fixes; neither is visible in metrics.json.

Only some net names survive synthesis — combinational cones come out as _00925_ or net669. A flat net is attributed to the module that last drove a named one, which is where that cone came from. It is a heuristic, and it is the only mapping back to RTL that survives flattening.

--hold does the same for min.rpt. With no --corner, the corner with the least slack is chosen for you.

ppa report, compare, pareto

Every run ever executed is appended to ppa_results.json next to the design, so the history outlives the terminal it was produced in.

ppa report                                    # everything
ppa report --stage post --passing             # only post-PnR runs that closed
ppa report --sort fmax_mhz --desc --format md # paste straight into a PR
ppa report --all --format csv > runs.csv      # all 20 metrics, for a spreadsheet
ppa compare fmax_pre_01_11.77ns fmax_post_00_12.00ns
ppa pareto --stage pre                        # undominated speed/area points
$ ppa compare fmax_pre_01_11.77ns fmax_post_00_12.00ns
fmax_pre_01_11.77ns  ->  fmax_post_00_12.00ns
  period            11.77         12.00  +0.233 (+2.0%)
  fmax_mhz           85.7          84.4  -1.326 (-1.5%)
  setup_ws         +0.097        +0.146  +0.050 (+51.2%)
  hold_ws          -0.102        +0.274  +0.375
  hold_vio            600             0  -600 (-100.0%)
  area_um2         104478        199771  +95293 (+91.2%)
  power_w          0.0104        0.0171  +0.007 (+64.6%)
  seconds            15.1        4994.2  +4979 (+32974.2%)

That single table is a good example of why pre-PnR numbers need a health warning: the Fmax estimate held to 1.5 %, the 600 pre-PnR hold violations were an artefact that CTS repaired, and the area nearly doubled once fill and repair cells landed.

What gets recorded

ppa_results.json is plain JSON — greppable, diffable, and safe to commit next to the design. Every run stores its tag, stage, overrides, effective period, runtime, exit status and 20 metrics:

setup/hold ws · wns · tns · violation counts · instance area · cell count · total and leakage power · max slew/cap/fanout violations · inferred latches · unmapped cells · Magic DRC errors · Netgen LVS errors · antenna violations.

--corner picks the per-corner variant of every metric that has one; metrics that are corner independent (area, cell count) are kept as they are.

In CI

Both useful modes are one line each:

- name: the design must still close timing at the configured period
  run: ppa run --stage pre && ppa report --last 1 --passing --format csv | grep -q .

- name: track Fmax over time
  run: ppa fmax --stage pre && ppa report --format md >> "$GITHUB_STEP_SUMMARY"

Output detects a non-TTY and drops colour and the self-overwriting progress line automatically, so CI logs stay readable. NO_COLOR and --no-color also work.

How it runs LibreLane

For each point of a search, ppa writes a _ppa_<tag>.yaml next to your real config — that location is not optional, because LibreLane resolves dir:: paths relative to the config file, so a variant written to /tmp would silently lose your RTL and SDC files. It then invokes

librelane _ppa_<tag>.yaml --pdk <pdk> --pdk-root <root> --manual-pdk \
          --run-tag <tag> --to <step>

streams the output to runs/<tag>/ppa.log, showing only the current step on one line, harvests runs/<tag>/final/metrics.json, and deletes the generated config. Your config.yaml is never touched unless you ask for --apply.

--dry-run prints the exact commands and runs nothing. --keep-configs leaves the generated files behind so you can run one by hand. ppa clean removes leftovers from an interrupted session.

Development

pip install -e ".[dev]"
pytest -q      # 72 tests, ~1 s, no EDA tools required
ruff check .

The suite ships a fake librelane on PATH that models a design with a fixed critical-path arrival time — and reproduces the WNS clamp, so a regression that starts steering on wns instead of ws fails the tests instead of quietly losing you 30 % of your clock.

Compatibility

Written against LibreLane 3.x with the IHP ihp-sg13cmos5l PDK, on a HeiChips / TinyTapeout style repository layout. Nothing is specific to that PDK or that layout: metric names come from LibreLane, report parsing comes from OpenSTA, and both the config location and the PDK are auto-detected or overridable with -C / --pdk / --pdk-root. It should work unchanged on sky130 and gf180 via OpenLane 2 configs.

License

Apache-2.0.

About

Timing closure and PPA exploration for LibreLane/OpenROAD designs: slack-driven Fmax search, config sweeps, and critical-path attribution back to RTL modules.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages