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)
  -> post-route SDF-back-annotated functional simulation (Icarus, manual)
  -> post-route PPA analysis (OpenROAD/OpenSTA, real OpenRCX parasitics)

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 describes two targets: sim (RTL functional simulation) and synth (Yosys synthesis) — see Running Flow A on Your Own SV Design;

  • three standalone Tcl scripts driven directly by openroad: ppa_report.tcl (post-synthesis, pre-route PPA), pnr.tcl (floorplan → placement → CTS → routing → post-route PPA), and optionally a power-with-real-activity variant of each;

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

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>/
├── 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
│   └── pnr.tcl                    # place & route + post-route PPA
├── 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
├── 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 PPA reporting stays inside the same live ``openroad`` session as place & route. Reloading a written .def in a fresh session does not restore the linked-network/global-route state that extract_parasitics/estimate_parasitics need — a standalone reload-and-report script fails with [ERROR EST-0005] Run global_route before estimating parasitics. This is why pnr.tcl keeps floorplanning through PPA reporting in one uninterrupted script.

  • 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.

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.