Running Flow A on Your Own SV Design¶
This page is a build-from-nothing walkthrough: environment setup through
place-and-route results, using the shared scripts and templates under
flow-a/ in this repository, unmodified, on any SystemVerilog design
of your own.
Important
Implemented and validated (on custom_myfir — a sequential,
clocked FIR filter, multi-file since it uses a custom Radix-4
Modified Booth signed multiplier internally rather than a plain
* operator; see examples/custom_myfir/): RTL simulation,
synthesis, post-synthesis gate-level simulation, pre-route PPA,
place & route, post-route PPA, post-route SDF-back-annotated
gate-level simulation, GDSII generation (gen_gds.sh, via
KLayout’s stream_out.py script), DRC, LVS, antenna checks and
multi-corner timing. The signoff steps, the timing-margin variables
and the reference results are documented in Flow A: Signoff and Results.
GDSII generation requires a separate one-time klayout
Python-bindings venv beyond the apt-installed KLayout GUI (see
Getting Started) — the GUI package alone does not expose the
pya module the stream-out script needs. Activity-driven
(VCD-based) power reporting is a separate step; see
Real-activity power (optional, recommended) below.
The core idea¶
Every script in flow-a/scripts/ (synth.tcl, load_constraints.tcl,
ppa_report.tcl, pnr.tcl, post_route.tcl, multicorner.tcl,
the two power_activity*.tcl, gen_gds.sh, drc.sh, lvs.sh with
its helpers, and the run_flow.sh/sweep.sh/metrics scripts) is
identical across every design and should be copied in unmodified. The
only script that changes per design is scripts/yosys_common.tcl
(plus the per-design SDC and Makefile config block). If you find
yourself editing one of the shared scripts to onboard a design, the
change almost certainly belongs in yosys_common.tcl instead.
File |
Changes per design? |
Source |
|---|---|---|
|
Yes — this is the file you write |
|
|
Only the top config block ( |
|
|
Yes — the single source of truth for sources (no separate manifest or file lists) |
|
|
Only if the design has a clock |
|
|
No — copy verbatim (all PDK-specific values are in
|
|
Makefile¶
Makefile wraps every stage below into a single make <target>
(sim, synth, gls, ppa, pnr, postroute, sdf,
power, gds, drc, lvs, signoff, metrics, plus
check/tech-check/clean/help/all) so you don’t need
to remember or copy-paste the raw fusesoc/openroad/
iverilog/./scripts/gen_gds.sh invocations documented in each
numbered section below — those sections remain the authoritative
reference for what each make target actually runs under the hood,
and for running a stage manually if you need to.
Copy flow-a/templates/Makefile.example to the project root as
Makefile and fill in its six-line config block to match
yosys_common.tcl — run make check to verify the two stay
consistent (along with tool/PDK/KLayout-bindings availability).
make gds needs the klayout Python venv active in the same
shell (see GDS Generation), so it’s deliberately excluded from make
all; make drc and make lvs build the GDS first and are
excluded for the same reason. Run them separately once the rest of the
flow has passed (see Flow A: Signoff and Results).
PDK selection and checking¶
The Makefile reads every PDK-specific value from the technology file
scripts/tech/$(TECH).tcl (TECH defaults to sky130hd;
override per call with make TECH=<name> <target> or with
export TECH=<name>). It queries that file through
scripts/tech_query.tcl, so tclsh must be installed
(sudo apt install tcl).
make tech-check verifies the active technology on its own: every
required variable is set, every referenced PDK file exists, and the
tech_tapcell/tech_pdn procs are defined. It prints one
MISS line per problem and fails if any are found. Run it after
installing the PDK or after writing a new tech file (see
PDK Portability (e.g. Retargeting to 55 nm)); make check runs the same check as part of
its wider tool and PDK verification.
make tech-check # default technology (sky130hd)
make TECH=<name> tech-check # a new technology file
Project layout¶
<design_name>/
├── Makefile
├── <design_name>.core
├── rtl/*.sv
├── tb/*.sv
├── scripts/
│ ├── yosys_common.tcl (the file you write)
│ ├── synth.tcl (copied from flow-a/scripts/)
│ ├── load_constraints.tcl (reads the SDC)
│ ├── ppa_report.tcl
│ ├── pnr.tcl
│ ├── post_route.tcl (post-route timing/power/SDF)
│ ├── multicorner.tcl
│ ├── power_activity_postsynth.tcl
│ ├── power_activity.tcl
│ ├── gen_gds.sh
│ ├── drc.sh, lvs.sh, lvs_prep.py, drop_cells.py, rdb_summary.py
│ ├── run_flow.sh, sweep.sh, sweep_table.py, collect_metrics.py
│ ├── tech.tcl, tech_query.tcl
│ └── tech/sky130hd.tcl (all PDK-specific names)
└── constraints/<design_name>.sdc (clocked designs only)
mkdir -p <design_name>/{rtl,tb,scripts,constraints}
cd <design_name>
cp -r <repo>/flow-a/scripts/* scripts/
cp <repo>/flow-a/templates/yosys_common.tcl.example scripts/yosys_common.tcl
cp <repo>/flow-a/templates/Makefile.example Makefile
cp <repo>/flow-a/templates/design.core.example <design_name>.core
chmod +x scripts/*.sh
1. <design_name>.core — declare your sources¶
The FuseSoC .core file is the only place the source list lives.
Simulation and synthesis both build their file lists from its filesets,
so there is nothing to generate and nothing to keep in sync:
CAPI=2:
name: thesis:flowA:<design_name>:0.1.0
description: <one-line description>
filesets:
rtl:
files:
- rtl/leaf_module.sv # leaf modules first, by convention;
- rtl/top_module.sv # read_slang doesn't strictly require it
file_type: systemVerilogSource
tb:
files:
- tb/testbench.sv
file_type: systemVerilogSource
targets:
sim:
default_tool: icarus
filesets: [rtl, tb]
toplevel: <testbench_module_name>
tools:
icarus:
iverilog_options: [-g2012]
synth:
default_tool: yosys
filesets: [rtl] # the testbench never reaches synthesis
toplevel: <top_design>
tools:
yosys:
arch: generic # unused: synth.tcl is a full custom template
output_format: verilog
output_name: <proj_name>_mapped.v
yosys_template: ../../../scripts/synth.tcl
Adding or removing a source file is a one-line edit to the rtl
fileset; FuseSoC tracks the individual files, so edits are picked up on
the next fusesoc run.
2. scripts/yosys_common.tcl — the file you actually write¶
Find your own values first:
grep -n "^module" rtl/*.sv
# identifies every module; the one instantiating the others is
# top_design — NOT necessarily the .core file's name
grep -n "input\|output" rtl/<top_module_file>.sv
# sort into in_ports (every input except a clock), out_ports, and
# clk_port (leave "" if the design is purely combinational)
set common_dir [file dirname [file normalize [info script]]]
set project_root [file normalize $common_dir/..]
set top_design <top_module_name>
set proj_name <design_name>
set build_name thesis_flowA_<design_name>_<version>
;# deterministic from the .core file's CAPI2 name field:
;# "thesis:flowA:<design_name>:0.1.0" -> "thesis_flowA_<design_name>_0.1.0"
set synth_dir $project_root/build/${build_name}/synth-yosys
set mapped_netlist $synth_dir/${proj_name}_mapped.v
set clk_port "" ;# leave "" for a purely combinational design
set budget_ns 5.0 ;# combinational: flat max-delay budget;
;# clocked: starting guess only - the
;# period lives in constraints/<design>.sdc
set clk_uncertainty 0.07
set in_ports {a b ...}
set out_ports {p ...}
set io_delay 0.5
set out_load 0.05
set fp_utilization 40
set fp_aspect_ratio 1
set fp_core_space 2
;# the standard-cell site (fp_site) is PDK-specific: it is set in
;# scripts/tech/<TECH>.tcl, not here
3. RTL simulation¶
The sim target defined in the core file (step 1) compiles the rtl
and tb filesets together with Icarus:
conda activate x-heep
source ~/thesis/tools/oss-cad-suite/environment
fusesoc --cores-root . run --target=sim thesis:flowA:<design_name>
Confirm a clean pass before moving on.
4. Synthesis¶
The synth target (step 1) is already in the core file. Its
yosys_template is scripts/synth.tcl (flow-a/scripts/synth.tcl —
copy verbatim; summarised here for reference). Edalize writes the
target’s fileset into edalize_yosys_procs.tcl as a read_files
proc; synth.tcl reuses that list but passes it to Yosys’s
read_slang — a real SystemVerilog elaborator (also known as
yosys-slang / sv-elab) that compiles the whole design together, correctly
handling multi-file, multi-package designs in one call, rather than the
fragile per-file-in-order read_verilog -sv approach:
source ../../../scripts/yosys_common.tcl ;# per-design values
source ../../../scripts/tech.tcl ;# $liberty etc. for $TECH
source edalize_yosys_procs.tcl ;# sources from the .core file
# ... collect the file list from read_files into $slang_files ...
yosys plugin -i slang
yosys read_slang {*}$slang_files --top $top --allow-use-before-declare
yosys hierarchy -top $top
yosys proc
yosys opt
yosys memory
yosys techmap
yosys opt
yosys dfflibmap -liberty $liberty
yosys abc -liberty $liberty
yosys opt_clean
yosys write_verilog -noattr ${proj_name}_mapped.v
exec sed -i {s/ signed / /g} ${proj_name}_mapped.v
The final sed line strips the signed qualifier Yosys still
emits on some ports — required before OpenSTA/OpenROAD can read this
netlist, and already handled by the script, not a separate manual step.
fusesoc --cores-root . run --target=synth thesis:flowA:<design_name>
Sanity checks before trusting the result:
grep -A 60 "ABC RESULTS" build/${build_name}/synth-yosys/yosys.log
# expect real, varied Sky130 cell counts, not empty/trivial
grep -c "VPWR" build/${build_name}/synth-yosys/<design_name>_mapped.v
# expect 0 unless you deliberately synthesized with power pins
See Troubleshooting if plugin -i slang fails to load, or if
read_slang rejects --ignore-unknown-modules/--compat-mode
on a newer build.
Synthesis options: excluded cells and the ABC timing target¶
synth.tcl expands the tech file’s dont_use_cells patterns against
the cell LEF and passes the resulting names to dfflibmap and abc
(-dont_use), so synthesis never picks cells the flow excludes
(probe_p_8, probec_p_8, lpflow_* for Sky130 HD). OpenROAD
excludes the same list with set_dont_use.
abc_target in yosys_common.tcl (or ABC_D in the environment,
or scripts/run_flow.sh --abc) selects the ABC mapping mode:
none is plain abc -liberty; auto uses the SDC clock period
minus abc_margin_ps as -D target with the SDC’s driving cell and
load as ABC constraints; a number is an explicit target in ps. auto
and numeric targets read set_driving_cell from the SDC, so a
combinational design without an SDC must stay on none.
5. Post-synthesis gate-level simulation¶
mkdir -p build/${build_name}/gls-icarus
cd build/${build_name}/gls-icarus
cp ../../../tb/<testbench>.sv <testbench>_gls.sv
Before compiling, edit the copy (never the original RTL testbench):
Remove any parameter override on the DUT instantiation — synthesis flattens parameters, so the mapped netlist’s module no longer declares them.
Remove any hierarchical debug references into internal submodule signals (
dut.u_sub.signal) — these fail to bind against a gate-level netlist even when the design is correct.
# cell models for the active PDK come from the tech file (TECH, default sky130hd)
iverilog -g2012 -DFUNCTIONAL -DUNIT_DELAY=#1 \
-o gls_sim.out \
$(tclsh scripts/tech_query.tcl cell_verilog) \
../../../build/${build_name}/synth-yosys/<design_name>_mapped.v \
<testbench>_gls.sv
vvp gls_sim.out
Confirm the same pass as step 3.
6. Pre-route PPA¶
flow-a/scripts/ppa_report.tcl — copy verbatim; branches on whether
clk_port is set:
source scripts/yosys_common.tcl
source scripts/tech.tcl ;# PDK names, from scripts/tech/$TECH.tcl
read_lef $tech_lef
read_lef $cell_lef
read_liberty $liberty
read_verilog $mapped_netlist
link_design $top_design
source scripts/load_constraints.tcl ;# reads constraints/<design>.sdc
;# (clocked) or applies set_max_delay
;# $budget_ns (combinational)
report_design_area
report_checks -path_delay max > $synth_dir/timing_fmax.rpt
report_worst_slack -max
report_power > $synth_dir/power_fmax.rpt
openroad -no_init -exit scripts/ppa_report.tcl
Note
report_power here has no real placement/parasitics or real
switching activity behind it — treat it as provisional. See
Real-activity power (optional, recommended) below.
Finding the real critical-path delay¶
Works identically for clocked and combinational designs:
Run with the default tight clock period (
constraints/<design>.sdcfor a clocked design,budget_nsfor a combinational one) —worst slack maxcomes back negative (VIOLATED).Read the “data arrival time” line at the bottom of
timing_fmax.rpt— that is the real critical-path delay, exact, no bisection needed.Set the clock period in
constraints/<design>.sdc(andTSin the testbench;make checkverifies they match) — orbudget_nsfor a combinational design — to roughly double that value before place & route, for post-route parasitic margin.
Read the report’s Startpoint/Endpoint and per-cell breakdown too, not just the slack number — it shows where the critical path actually is (which pipeline stage, or which input port is disproportionately loaded), which matters more for interpreting results than the single pass/fail number.
7. constraints/<design_name>.sdc — clocked designs only¶
Skip this entirely if clk_port is "" — load_constraints.tcl
applies set_max_delay $budget_ns directly for combinational designs.
create_clock -name <clk_port> -period <period> [get_ports <clk_port>]
set_clock_uncertainty <uncertainty> [get_clocks <clk_port>]
set_input_delay <io_delay> -clock <clk_port> [get_ports {<in_ports>}]
set_output_delay <io_delay> -clock <clk_port> [get_ports {<out_ports>}]
set_driving_cell -lib_cell <cell> -pin <pin> [get_ports {<in_ports>}]
set_load <pf> [get_ports {<out_ports>}]
This SDC is the single source of truth for timing: every OpenROAD
script reads it through scripts/load_constraints.tcl. The delays
deliberately have no -max so input/output hold is checked too, and
the testbench must launch its inputs at the same offset as the SDC input
delay (see Flow A: Signoff and Results).
8. Place & route¶
flow-a/scripts/pnr.tcl — copy verbatim. Run headless only
(-gui is unsafe — detailed_route is known to segfault inside
OpenROAD’s GUI process):
openroad -no_init -exit scripts/pnr.tcl
Reading the results:
worst slack min = INFis correct, not an error, for any design with zero flip-flops (no hold paths exist to check).Post-route delay vs. the pre-route estimate can go either direction — compare explicitly rather than assuming parasitics always add delay; a small, compact design can see real parasitics add less delay than a zero-load pre-route model assumed.
make pnr also runs scripts/post_route.tcl afterwards (seconds,
repeatable with make postroute): it reloads the routed netlist,
reads the SPEF, and writes the timing and power reports and the SDF —
reports made inside pnr.tcl itself would ignore the extracted wires.
make multicorner re-times the routed design at ss/tt/ff with the
matching min/nom/max SPEF (the three SPEFs are written by pnr.tcl).
The timing-margin variables SETUP_MARGIN, HOLD_MARGIN, ANT_MARGIN
and PNR_LIB, the optional make pnr-ss (repair against the ss
library, not part of make all) and the repair/antenna steps are described
in Flow A: Signoff and Results; the stages of pnr.tcl one by one, with the
design choices, are in Place and Route: Stage by Stage.
Output: <design_name>_placed.def (routed, despite the name),
<design_name>_route.v, <design_name>_route.cdl,
<design_name>_route.sdf, <design_name>_route.spef,
<design_name>_antenna*.rpt, plus timing_postroute*.rpt and
power_postroute.rpt.
GDS generation¶
See GDS Generation for full detail (one-time KLayout Python-bindings venv
setup, the oss-cad-suite PATH-shadowing gotcha, PDK file
locations, and how to verify the output). Quick version:
source ~/klayout-venv/bin/activate # NOT oss-cad-suite/environment
make gds # or: ./scripts/gen_gds.sh
Output: <proj_name>.gds in the project root, generated from
<proj_name>_placed.def — despite the name, that DEF already
contains full routing, since write_def runs after
detailed_route in pnr.tcl above.
9. Post-route SDF-back-annotated simulation¶
cd build/${build_name}/gls-icarus
cp ../../../tb/<testbench>.sv <testbench>_sdf.sv
Apply the same two fixes as step 5, plus an $sdf_annotate call
right after the DUT instantiation:
initial begin
$sdf_annotate("../../../<design_name>_route.sdf", dut);
end
Important
Increase every settling delay in the testbench before compiling. A
small delay like #1 between driving inputs and sampling outputs
was sized for a zero-delay functional model — use at least 2x the
real post-route critical-path delay (found in step 6/8’s reports) as
the new settling window. Skipping this produces X-propagation
garbage in the output (including corrupted string output from a
ternary comparison against an unresolved X value) that looks
like a functional bug but is a testbench-timing bug. See
Troubleshooting for the full pattern, including the distinct
clocked-design variant of this same class of issue.
iverilog -g2012 -gspecify -ginterconnect -o gls_sdf_sim.out \
$(tclsh scripts/tech_query.tcl cell_verilog) \
../../../<design_name>_route.v \
<testbench>_sdf.sv
vvp gls_sdf_sim.out -sdf-verbose 2>&1 | tee run.log
Both -gspecify and -ginterconnect are required (unlike step 5):
-gspecify is disabled by default in Icarus, and without it
$sdf_annotate has no ModPath objects to attach delays to — every
cell fails with “Unable to match ModPath” and the sim can silently
report a false-positive pass with zero real delay applied. Do not
define -DFUNCTIONAL here — it bypasses the specify blocks this
stage needs delays attached to.
Confirm the same pass as every prior stage — this is the final correctness gate before this design’s PPA numbers are trustworthy for comparison against the other three flows.
Real-activity power (optional, recommended)¶
report_power without a loaded VCD uses a vectorless estimate that
can overstate real power substantially and doesn’t respond to real
switching activity. To replace it with a trustworthy number at either
the post-synthesis or post-route stage:
Add a VCD dump to a copy of the gate-level testbench:
initial begin $dumpfile("activity.vcd"); $dumpvars(0, <tb_top>.dut); end
Re-run the same gate-level simulation (step 5 or 9), confirming it still passes before trusting the VCD.
Feed the VCD into a Tcl script (post-synthesis: no SPEF loaded; post-route: with the real SPEF from step 8) alongside the same constraints already used for that stage’s PPA report:
read_vcd -scope <tb_top>/dut build/.../gls-icarus/activity.vcd report_power > power_activity.rpt
Check the report’s “Annotated N pin activities” line — a nonzero, plausible pin count confirms the annotation took effect rather than silently falling back to vectorless defaults.
Known gotchas¶
See Troubleshooting for the full list — in particular, both a
multi-file/read_slang-specific section and the original
single-file/clocked-design section apply depending on your design.