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) andsynth(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.tclandpower_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), andgen_gds.sh(DEF → GDS stream-out via KLayout — a bash/Python script, not Tcl, since it runs through KLayout’spyabindings rather thanopenroad), plus the signoff scriptsdrc.shandlvs.sh(withlvs_prep.pyanddrop_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
alwaysblocks.Stripping ``signed`` from the mapped netlist. Yosys still emits the
signedqualifier 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 afterglobal_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
-densityclose to the actual target utilization forces a uniform spread.``pnr.tcl`` must run headless.
detailed_routeis known to segfault inside the OpenROAD GUI process (confirmed via a null-pointer dereference indmesg, 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.tclruns it and writes<design>_route.spef(plus_route_min.spefand_route_max.speffor the other RC corners). But OpenSTA ignores parasitics unlessread_spefis called, so reports andwrite_sdfproduced insidepnr.tclwould show zero wire delay (setup slack looked about 1.7 ns too good, every SDF INTERCONNECT was 0).post_route.tclreloads the routed netlist, reads the SPEF and only then reports — in seconds, repeatably.Constraints live in one SDC. Every OpenROAD script reads
constraints/<design>.sdcthroughload_constraints.tcl, so pre-route, post-route and power numbers use identical constraints.Repair steps in ``pnr.tcl``.
repair_designbefore CTS,repair_timing -setup -holdafter CTS (with setup and hold margins, optionally against thesslibrary viamake pnr-ss), andrepair_antennasafter global route. Cells inserted after the PDN needglobal_connector 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_powerwith 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 toreport_powerviaread_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.shdoes this with KLayout’sstream_out.py, run through KLayout’s own Python bindings (pya) rather thanopenroad— which is also why it needs a separate one-time venv distinct from theapt-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.