============================== Flow A: Signoff and Results ============================== This page documents everything Flow A does **after** the basic place-and-route steps in :doc:`running-your-own-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. .. contents:: :local: :depth: 1 What a run produces ===================== .. code-block:: text 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 ``.gds``, which ``make`` builds through the ``gds`` target (KLayout stream-out, needs the ``klayout`` venv from :doc:`getting-started`). One source of truth for timing constraints ============================================== ``constraints/.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 (``_route.v``), reads ``_route.spef``, sets propagated clocks, and only then writes ``timing_postroute.rpt``, ``timing_postroute_hold.rpt``, ``power_postroute.rpt`` and ``_route.sdf``. It runs in seconds and can be repeated freely (``make postroute``). Netlist repair in ``pnr.tcl`` ================================= .. list-table:: :header-rows: 1 :widths: 30 70 * - 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) ----------------------------------- .. list-table:: :header-rows: 1 :widths: 22 12 66 * - 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 ``.gds`` with ``feol``, ``beol`` and ``offgrid`` enabled, and summarises the result with ``scripts/rdb_summary.py`` into ``signoff/_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: .. code-block:: sh 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: .. code-block:: sh cd runs/ for c in ss_100C_1v60 ff_n40C_1v95; do CORNER=$c openroad -no_init -exit scripts/multicorner.tcl done .. list-table:: :header-rows: 1 * - 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``). .. code-block:: sh # 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``) ========================================================= .. list-table:: :header-rows: 1 * - 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.