Flow A: Signoff and Results

This page documents everything Flow A does after the basic place-and-route steps in Running Flow A on Your Own SV Design: how timing is constrained and reported with real wires, how the netlist is repaired, how the layout is checked (DRC, LVS, antenna, multi-corner timing), and how runs are isolated so results are reproducible. All numbers on this page come from the reference run sign_p28_hm35 of examples/custom_myfir (Sky130 HD, 28 ns clock) unless stated otherwise.

What a run produces

make check sim synth ppa gls pnr sdf power     # functional + PPA
make drc lvs                                    # signoff (after pnr)

make pnr runs scripts/pnr.tcl and then scripts/post_route.tcl (the $(ROUTE_SDF) dependency). make drc / make lvs depend on <design>.gds, which make builds through the gds target (KLayout stream-out, needs the klayout venv from Getting Started).

One source of truth for timing constraints

constraints/<design>.sdc is the only place the clock period, uncertainty, input/output delays, driving cell and output load are defined. scripts/load_constraints.tcl reads it and is sourced by ppa_report.tcl, pnr.tcl, post_route.tcl, power_activity.tcl and power_activity_postsynth.tcl, so the pre-route, post-route and power numbers always use the same constraints. For a purely combinational design (clk_port ""), load_constraints.tcl falls back to the flat budget_ns delay budget instead.

Rules that make the numbers trustworthy:

  • set_input_delay / set_output_delay carry no -max, so they apply to min and max and input hold is checked and repaired.

  • The testbench launches DIN/VIN at +IN_DLY after the clock edge, with IN_DLY equal to the SDC input delay. A mismatch gives false SDF-simulation failures or hold races. make check verifies that the SDC period equals the testbench TS.

  • scripts/run_flow.sh --period N rewrites both consistently (even integers only, because the testbench uses TS/2).

Post-route timing with real wires

OpenSTA ignores extracted parasitics unless read_spef is called, so reports and write_sdf produced inside pnr.tcl would show zero wire delay. scripts/post_route.tcl therefore reloads the routed netlist (<design>_route.v), reads <design>_route.spef, sets propagated clocks, and only then writes timing_postroute.rpt, timing_postroute_hold.rpt, power_postroute.rpt and <design>_route.sdf. It runs in seconds and can be repeated freely (make postroute).

Netlist repair in pnr.tcl

Step

Why

tech_wire_rc + estimate_parasitics -placement

Wire RC for repair and CTS (signal on met2, clock on met5, defined in the tech file).

set_dont_use $dont_use_cells

Keeps the resizer away from cells the flow excludes (see DRC). Yosys gets the same list.

repair_design

Fixes slew/cap/fanout and buffers high-fanout nets (RST_n, VIN) before CTS.

CTS, then set_propagated_clock

Timing after CTS uses the real clock tree.

repair_timing -setup -hold -hold_margin

See Margins below.

global_route, repair_antennas (diodes), detailed_route

Antenna repair runs on the global-route estimate; detailed route follows.

filler_placement, global_connect

Fillers are needed for clean DRC. global_connect gives cells inserted after the PDN (buffers, CTS cells, diodes, fillers) their power pins, or LVS sees dangling rails.

extract_parasitics / write_spef / write_verilog / write_cdl

Inputs for post-route STA and for LVS.

Margins (environment variables)

Variable

Default

Effect

HOLD_MARGIN

0.35

repair_timing -hold_margin (ns). Chosen from the multi-corner result: input-to-flop hold paths lose ~0.29 ns between tt and ss because the clock tree slows down while the external input delay is fixed.

ANT_MARGIN

40

repair_antennas -ratio_margin (percent). Final detailed-route wires are longer than the global-route estimate.

ABC_D

auto

none, auto or a target in ps. auto is the period minus abc_margin_ps (2500) and needs set_driving_cell in the SDC; it is the custom_myfir default and used by the reference runs. Combinational designs without an SDC keep none.

Example: HOLD_MARGIN=0 ANT_MARGIN=0 scripts/run_flow.sh --tag ablate ... reproduces the behaviour without either margin.

DRC

make drc runs scripts/drc.sh: the PDK’s KLayout deck (sky130A_mr.drc, variable klayout_drc in the tech file) on <design>.gds with feol, beol and offgrid enabled, and summarises the result with scripts/rdb_summary.py into signoff/<design>_drc_summary.txt (the .lyrdb opens in KLayout).

Reference result: 3 violations, all rule li.5, on three library cells (clkbuf_4, nor3b_1, o221a_2). Each was confirmed on the bare library cell by running the same deck on the library GDS, so they are intrinsic to the PDK cell layouts, not introduced by this flow:

lib=$PDK_ROOT/$PDK/libs.ref/sky130_fd_sc_hd/gds/sky130_fd_sc_hd.gds
c=o221a_2
klayout -b -r $PDK_ROOT/$PDK/libs.tech/klayout/drc/${PDK}_mr.drc \
  -rd input=$lib -rd top_cell=sky130_fd_sc_hd__$c -rd report=/tmp/lib_$c.lyrdb \
  -rd feol=true -rd beol=true -rd offgrid=true -rd thr=4
python3 scripts/rdb_summary.py /tmp/lib_$c.lyrdb

Two cell families are excluded from synthesis and resizing (dont_use_cells): probe_p_8 / probec_p_8 (large probe buffers whose met5/li shapes violate DRC next to the PDN) and lpflow_* (low-power isolation cells with special power connections). None of them appeared in the final netlist of the signoff run.

LVS

make lvs runs scripts/lvs.sh (KLayout sky130.lvs, run_mode=deep) of the GDS against a schematic built from the routed design:

  1. write_cdl -masters writes the design netlist.

  2. scripts/lvs_prep.py adds the cell subcircuits from the PDK SPICE (the CDL does not match the deck, which expects X-style transistors with scale and convert_subckts) and rewrites the tie cell.

  3. scripts/drop_cells.py removes the antenna diode cells.

  4. The deck runs with scale=true, convert_subckts=true and schematic_simplify=true (merges multi-finger devices).

Reference result: LVS_RESULT MATCH.

What LVS does not verify

The layout extraction cannot see two kinds of cell: tie cells (conb_1: no recognised device, outputs are just rail connections) and antenna diodes (diode_2: no recognised device). They are rewritten to rail connections / removed from the schematic, so their connections are correct by construction, not checked by LVS. State this in any LVS claim. The same applies to fillers, which are excluded from the CDL.

Antenna

Antenna repair runs on the global-route estimate: 11 violating nets before repair, 0 after (custom_myfir_antenna_groute*.rpt). After detailed route, check_antennas re-checks the real wires (custom_myfir_antenna.rpt): 7 nets remain over the limit in the reference run. Every one is the met1 side-area limit (400); the ratios in the first signoff run ranged from 432 to 681.

The count depends on exactly how each run routes: with ANT_MARGIN=40 the earlier run had 4 violating nets, the reference run 7, and the nets themselves differ between runs. A larger margin trades extra diodes for a different set of violators, not for zero. Treat the count as a rough indicator, report it as residual antenna violations, and note that zero would need routing changes (jumpers, layer constraints) that are outside this flow’s scope.

Multi-corner timing

All flow numbers are at the typical corner (tt_025C_1v80) with OpenRCX parasitics from the single nominal rules file. The routed design can be re-timed at other Liberty corners without re-routing:

cd runs/<tag>
for c in ss_100C_1v60 ff_n40C_1v95; do
  CORNER=$c openroad -no_init -exit scripts/multicorner.tcl
done

Corner

Worst setup slack (ns)

Worst hold slack (ns)

tt_025C_1v80

+13.29

+0.39

ss_100C_1v60

−2.26 (TNS −4.32)

+0.36

ff_n40C_1v95

+19.28

+0.36

At 28 ns the design closes at tt and ff but not at ss: it would need about 30.5 ns there. Hold at ss was −0.27 ns before HOLD_MARGIN (all failing paths were input-port-to-flop). Keep the PPA comparison at one corner, and report ss/ff as a separate signoff check. Parasitics are the nominal-corner SPEF in every case.

Reproducible runs and sweeps

scripts/run_flow.sh --tag NAME --period NS --abc auto|none|PS --steps "..." copies the design into runs/NAME/ and runs the steps there, so a run never disturbs another one. It refuses to reuse an existing tag. Each run writes logs/steps.tsv (step, seconds, exit code) and metrics/NAME.json (from scripts/collect_metrics.py).

# reference run, including signoff
nohup scripts/run_flow.sh --tag sign_p28_hm35 --period 28 --abc auto \
  --steps "check sim synth ppa gls pnr sdf power drc lvs" > sign_p28_hm35.log 2>&1 &

# period sweep, two runs at a time
nohup scripts/sweep.sh 2 16 18 20 24 > sweep_fin.log 2>&1 &
python3 scripts/sweep_table.py "metrics/fin_p*.json" metrics/sign_p28_hm35.json

Detailed route takes about 30 to 45 minutes per run on the reference machine (8 cores, 7 GB memory); two parallel runs fit in memory.

Reference results (custom_myfir, --abc auto)

Period (ns)

Setup (ns)

Hold (ns)

Area (µm²)

Power (mW)

SDF sim

28

+13.29

+0.39

76596

2.75

PASS

24

+9.29

+0.39

76596

3.20

PASS

20

+5.29

+0.39

76596

3.84

PASS

18

+3.29

+0.39

76596

4.27

PASS

16

+1.35

+0.37

76578

4.79

PASS

Post-route values, power from the real-activity VCD flow. The netlist is identical for 18 to 28 ns (10402 cells), so area is flat and power scales with frequency (about 0.077 mW/MHz). The estimated fmax is about 68 MHz (14.7 ns). Utilisation after CTS is 42 %, global-route usage 22 %, detailed route finishes with 0 violations.

Limitations

  • PPA numbers are at the typical corner only; ss setup fails at 28 ns (above).

  • Parasitics come from one nominal OpenRCX rules file for every corner.

  • LVS does not verify tie cells or antenna diodes (see above).

  • 7 residual met1 side-area antenna violations (4 to 7 between runs).

  • 3 DRC findings (li.5), all inherited from library cells.

  • Power is activity-based (VCD) for the testbench vectors of this design only; the vectorless report_power figures are not for reporting (the metrics files label them that way).

  • examples/r4_mbe_mul (combinational) predates these scripts and has not been re-run with them.