Flow A: Overview

Flow A is the fully open-source EDA/PDK quadrant of the thesis (Yosys + OpenROAD + KLayout, Sky130). This page explains the pipeline architecture; Running Flow A on Your Own SV Design shows how to point it at a design of your own.

Pipeline stages

RTL (design.v)
  -> functional simulation (Icarus, via FuseSoC)
  -> synthesis (Yosys, via FuseSoC, target library from the PDK)
  -> post-synthesis functional simulation (Icarus, manual)
  -> post-synthesis PPA analysis (OpenROAD/OpenSTA, manual)
  -> place & route (OpenROAD, manual — standalone script: repair,
     CTS, hold repair, antenna repair, routing, fillers, extraction)
  -> post-route PPA analysis (OpenROAD/OpenSTA, SPEF read back in)
  -> post-route SDF-back-annotated functional simulation (Icarus, manual)
  -> real-activity power analysis (OpenROAD/OpenSTA, VCD-based, manual)
  -> GDSII generation (KLayout stream-out, manual — standalone script)
  -> DRC and LVS (KLayout, PDK decks) and multi-corner re-timing
     (see :doc:`flow-a-signoff`)

Why FuseSoC only covers two stages

FuseSoC/Edalize is used only for the RTL simulation and synthesis stages, via Edalize’s Icarus and Yosys Tool-API backends. Place & route is run as a standalone OpenROAD Tcl script, not through Edalize’s OpenROAD backend, because that backend is built specifically to drive the separate OpenROAD-flow-scripts (ORFS) project via its own Makefile/config.mk format — a different, heavier toolchain than the hand-written Tcl flow used here. Everything from place-and-route onward (PPA analysis, gate-level simulation, SDF back-annotation) is likewise run by invoking openroad / iverilog directly, not through FuseSoC.

This means for every design the flow needs:

  • a FuseSoC core file (<design>.core) that lists the sources (the only source list) and describes two targets: sim (RTL functional simulation) and synth (Yosys synthesis) — see Running Flow A on Your Own SV Design;

  • standalone scripts driven directly by openroad / python3, not FuseSoC: ppa_report.tcl (post-synthesis, pre-route PPA), pnr.tcl (floorplan → placement → CTS → routing → post-route PPA), power_activity_postsynth.tcl and power_activity.tcl (real-activity, VCD-based power at each stage), post_route.tcl (timing/power/SDF with the extracted parasitics), multicorner.tcl (re-time the routed design at the ss/tt/ff Liberty corners, each with its matching min/nom/max SPEF), and gen_gds.sh (DEF → GDS stream-out via KLayout — a bash/Python script, not Tcl, since it runs through KLayout’s pya bindings rather than openroad), plus the signoff scripts drc.sh and lvs.sh (with lvs_prep.py and drop_cells.py);

  • a handful of manual Icarus invocations for gate-level simulation (post-synthesis and post-route/SDF), since neither of those stages goes through FuseSoC.

In practice, none of this is invoked by hand stage-by-stage: each design’s Makefile (copied from flow-a/templates/Makefile.example, with only its top config block edited) wraps the FuseSoC targets, the standalone scripts, and the manual Icarus invocations into single make sim / synth / gls / ppa / pnr / sdf / power / gds targets — see Running Flow A on Your Own SV Design for setup and GDS Generation for the GDS stage specifically.

Output layout

FuseSoC-driven stages land under build/<core-name>_<version>/<stage>/. Everything driven directly by the standalone OpenROAD script (pnr.tcl) writes to the project root instead, since it isn’t run through FuseSoC/Edalize and has no build-directory convention to follow:

<design>/
├── Makefile                       # wraps every stage into `make <target>`
├── constraints/
│   └── <design>.sdc
├── golden/                        # expected-output reference data
├── <design>.core                  # FuseSoC core file
├── rtl/
│   └── <design>.v
├── scripts/
│   ├── synth.tcl                  # Yosys synthesis (custom template)
│   ├── ppa_report.tcl             # post-synthesis PPA
│   ├── load_constraints.tcl       # reads constraints/<design>.sdc (one place)
│   ├── pnr.tcl                    # place & route, repair, extraction
│   ├── post_route.tcl             # post-route timing/power/SDF (reads SPEF)
│   ├── multicorner.tcl            # re-time at ss/tt/ff (min/nom/max SPEF)
│   ├── drc.sh, lvs.sh             # KLayout signoff (+ lvs_prep.py, drop_cells.py)
│   ├── run_flow.sh, sweep.sh      # isolated runs / period sweeps
│   ├── collect_metrics.py         # metrics/<tag>.json
│   ├── power_activity_postsynth.tcl  # real-activity power, post-synth
│   ├── power_activity.tcl         # real-activity power, post-route
│   ├── gen_gds.sh                 # DEF -> GDS stream-out (KLayout)
│   ├── tech.tcl, tech_query.tcl   # technology (PDK) selector / query
│   └── tech/<TECH>.tcl            # all PDK-specific names (sky130hd)
├── tb/
│   └── tb_<design>.sv
├── build/
│   └── <core-name>_<version>/
│       ├── sim-icarus/            # RTL sim output
│       ├── synth-yosys/           # synthesis output (netlist, reports)
│       └── gls-icarus/            # gate-level sims (created manually)
├── <design>_placed.def            # from pnr.tcl, project root
├── <design>_route.v               # post-route netlist
├── <design>_route.sdf             # post-route SDF (real delays)
├── <design>_route.spef            # extracted parasitics
├── <design>_route.cdl             # netlist for LVS
├── <design>_antenna*.rpt          # antenna reports (global / final)
├── signoff/                       # DRC / LVS results (make drc, make lvs)
├── runs/<tag>/                    # isolated copies (run_flow.sh)
├── metrics/<tag>.json             # per-run metrics
├── <design>.gds                   # from gen_gds.sh, project root
├── timing_postroute.rpt
├── timing_postroute_hold.rpt
└── power_postroute.rpt

Why the flow works this way

A few design choices recur throughout the scripts and are worth knowing up front, since they explain “why is this step here” the first time you read synth.tcl or pnr.tcl:

  • ``dfflibmap`` before ``abc``. Generic/behavioral flip-flops must be mapped to real library cells before ABC’s technology mapping runs, since OpenSTA/OpenROAD’s Verilog reader only understands gate instantiations, not behavioral always blocks.

  • Stripping ``signed`` from the mapped netlist. Yosys still emits the signed qualifier on port/wire declarations in the gate-level netlist; OpenSTA/OpenROAD’s Verilog reader does not support it, and it carries no structural meaning once the netlist is fully mapped, so it is safe (and required) to strip.

  • Two ``place_pins`` calls in ``pnr.tcl``. The first is required before global_placement (which hard-errors if a port has no location yet); the second is required after global_placement, because that step does not move already-placed pins along with the cells it re-positions.

  • ``-density`` below the default in ``global_placement``. OpenROAD’s default target density (0.70) lets the placer satisfy the density constraint by compacting the netlist into a wirelength-optimal blob covering only part of the die, instead of spreading uniformly the way a commercial placer does by default. Setting -density close to the actual target utilization forces a uniform spread.

  • ``pnr.tcl`` must run headless. detailed_route is known to segfault inside the OpenROAD GUI process (confirmed via a null-pointer dereference in dmesg, not memory exhaustion). Every command up to (not including) routing can be typed into the GUI’s Scripting console for visual inspection if needed.

  • Post-route reports come from a separate session that reads the SPEF. Extraction needs the live global-route state, so pnr.tcl runs it and writes <design>_route.spef (plus _route_min.spef and _route_max.spef for the other RC corners). But OpenSTA ignores parasitics unless read_spef is called, so reports and write_sdf produced inside pnr.tcl would show zero wire delay (setup slack looked about 1.7 ns too good, every SDF INTERCONNECT was 0). post_route.tcl reloads the routed netlist, reads the SPEF and only then reports — in seconds, repeatably.

  • Constraints live in one SDC. Every OpenROAD script reads constraints/<design>.sdc through load_constraints.tcl, so pre-route, post-route and power numbers use identical constraints.

  • Repair steps in ``pnr.tcl``. repair_design before CTS, repair_timing -setup -hold after CTS (with setup and hold margins, optionally against the ss library via make pnr-ss), and repair_antennas after global route. Cells inserted after the PDN need global_connect or LVS sees dangling power pins. Details and the reasoning for the margins: Flow A: Signoff and Results. Every stage with its settings and design choices: Place and Route: Stage by Stage.

  • Vectorless vs. real-activity power. report_power with no VCD loaded uses OpenSTA’s default/vectorless switching-activity assumption, which is not tied to the clock period in a physically meaningful way and can be off by an order of magnitude. For a trustworthy figure, capture a VCD from an already-verified-passing gate-level simulation and feed it to report_power via read_vcd — see Running Flow A on Your Own SV Design for the pattern.

  • GDS generation needs the PDK’s real cell shapes, not just DEF/LEF. A routed DEF only carries abstract cell outlines from the LEF; the actual Sky130 standard-cell polygons have to be merged in from the PDK’s own GDS library via a LEF/DEF → GDS layer map. gen_gds.sh does this with KLayout’s stream_out.py, run through KLayout’s own Python bindings (pya) rather than openroad — which is also why it needs a separate one-time venv distinct from the apt-installed KLayout GUI package (see Getting Started).

See Troubleshooting for the full list of gotchas encountered running this flow, including the gate-level simulation flags (-gspecify, -ginterconnect) and a testbench-timing pitfall that only shows up once real SDF delays are annotated.