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 two real designs — myfir, a
sequential single-file FIR filter, and r4_mbe_mul, a
combinational multi-file Booth multiplier): RTL simulation,
synthesis, post-synthesis gate-level simulation, pre-route PPA,
place & route, post-route PPA, post-route SDF-back-annotated
gate-level simulation.
Not yet implemented — this flow does not currently produce a
GDSII layout. There is no write_gds step anywhere in the
scripts, KLayout is installed (Getting Started) but not
invoked by anything yet, and DRC/LVS are not run. If your goal is a
tapeoutable layout, this flow does not get you there yet — it gets
you a placed-and-routed, function- and timing-verified netlist with
PPA estimates, which is what the thesis’s PPA comparison needs.
Activity-driven (VCD-based) power reporting is also not wired in by
default; see Real-activity power (optional, recommended) below
to add it yourself.
The core idea¶
Three scripts — flow-a/scripts/synth.tcl, ppa_report.tcl, and
pnr.tcl — are identical across every design and should be copied in
unmodified. The only file that changes per design is
scripts/yosys_common.tcl. If you find yourself editing one of the
three 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 |
|
|
Yes |
|
|
Yes |
|
|
Only if the design has a clock |
|
|
No — copy verbatim |
|
Project layout¶
<design_name>/
├── Bender.yml
├── <design_name>.core
├── rtl/*.sv
├── tb/*.sv
├── sim/<design_name>_sim.flist (generated, not committed)
├── yosys/<design_name>.flist (generated, not committed)
├── scripts/
│ ├── yosys_common.tcl (the file you write)
│ ├── synth.tcl (copied from flow-a/scripts/)
│ ├── ppa_report.tcl
│ └── pnr.tcl
└── constraints/<design_name>.sdc (clocked designs only)
mkdir -p <design_name>/{rtl,tb,sim,yosys,scripts,constraints}
cd <design_name>
cp <repo>/flow-a/scripts/*.tcl scripts/
cp <repo>/flow-a/templates/yosys_common.tcl.example scripts/yosys_common.tcl
1. Bender.yml — declare your sources¶
package:
name: <design_name>
sources:
- target: any(synthesis, simulation)
files:
- rtl/leaf_module.sv # leaf modules first, by convention;
- rtl/top_module.sv # read_slang doesn't strictly require it
- target: simulation
files:
- tb/testbench.sv # simulation-only, never pulled into
# the synthesis flist
Generate both flists — re-run any time you add or remove a source file:
bender script flist -t synthesis > yosys/<design_name>.flist
bender script flist -t simulation > sim/<design_name>_sim.flist
A single-file design technically doesn’t need Bender (myfir never
used it) — but any design with more than one RTL file does, so it’s
worth using from the start.
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 your Bender.yml package 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 sv_flist $project_root/yosys/${proj_name}.flist
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 ;# clock period, or a flat max-delay budget
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
set fp_site unithd
3. FuseSoC core file — RTL simulation¶
CAPI=2:
name: thesis:flowA:<design_name>:0.1.0
description: <one-line description>
filesets:
sim_flist:
files:
- sim/<design_name>_sim.flist : {file_type: user, copyto: <design_name>_sim.flist}
targets:
sim:
default_tool: icarus
filesets: [sim_flist]
toplevel: <testbench_module_name>
tools:
icarus:
iverilog_options: [-g2012, -f, <design_name>_sim.flist]
Note what’s deliberately absent: no per-file files: list. RTL/tb
sources are read straight from the Bender-generated flist via -f,
so regenerating the flist (step 1) is the only step needed after
changing a source file — no .core edit required.
source ~/fusesoc-venv/bin/activate
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¶
Add the synth target alongside sim:
synth:
default_tool: yosys
toplevel: <top_design>
tools:
yosys:
arch: generic
output_format: verilog
output_name: <design_name>_mapped.v
yosys_template: ../../../scripts/synth.tcl
scripts/synth.tcl (flow-a/scripts/synth.tcl — copy verbatim;
shown here for reference) uses Yosys’s read_slang command — a real
SystemVerilog elaborator (also known as yosys-slang / sv-elab) that
compiles the whole flist 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
yosys plugin -i slang
yosys read_slang -f $sv_flist --top $top_design \
--allow-use-before-declare
yosys hierarchy -top $top_design
yosys proc
yosys opt
yosys memory
yosys techmap
yosys opt
set liberty $::env(PDK_ROOT)/$::env(PDK)/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib
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.
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.
iverilog -g2012 -DFUNCTIONAL -DUNIT_DELAY=#1 \
-o gls_sim.out \
$PDK_ROOT/$PDK/libs.ref/sky130_fd_sc_hd/verilog/primitives.v \
$PDK_ROOT/$PDK/libs.ref/sky130_fd_sc_hd/verilog/sky130_fd_sc_hd.v \
../../../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
read_lef $::env(PDK_ROOT)/$::env(PDK)/libs.ref/sky130_fd_sc_hd/techlef/sky130_fd_sc_hd__nom.tlef
read_lef $::env(PDK_ROOT)/$::env(PDK)/libs.ref/sky130_fd_sc_hd/lef/sky130_fd_sc_hd.lef
read_liberty $::env(PDK_ROOT)/$::env(PDK)/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib
read_verilog $mapped_netlist
link_design $top_design
if {$clk_port ne ""} {
create_clock -name $clk_port -period $budget_ns [get_ports $clk_port]
set_clock_uncertainty $clk_uncertainty [get_clocks $clk_port]
set_input_delay $io_delay -max -clock $clk_port [get_ports $in_ports]
set_output_delay $io_delay -max -clock $clk_port [get_ports $out_ports]
} else {
set_max_delay $budget_ns -from [get_ports $in_ports] -to [get_ports $out_ports]
}
set_load $out_load [get_ports $out_ports]
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
budget_ns—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
budget_nsinyosys_common.tclto 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 "" — pnr.tcl applies
set_max_delay directly for combinational designs.
create_clock -name <clk_port> -period <budget_ns> [get_ports <clk_port>]
set_clock_uncertainty <clk_uncertainty> [get_clocks <clk_port>]
set_input_delay <io_delay> -max -clock <clk_port> [get_ports {<in_ports>}]
set_output_delay <io_delay> -max -clock <clk_port> [get_ports {<out_ports>}]
Keep these values consistent with yosys_common.tcl — not currently
auto-generated from it.
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.
Output: <design_name>_placed.def, <design_name>_route.v,
<design_name>_route.sdf, <design_name>_route.spef, plus
timing_postroute*.rpt and power_postroute.rpt. This is the last
artifact this flow currently produces — see the status note at the top
of this page for what a complete RTL-to-GDSII flow would add next.
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 \
$PDK_ROOT/$PDK/libs.ref/sky130_fd_sc_hd/verilog/primitives.v \
$PDK_ROOT/$PDK/libs.ref/sky130_fd_sc_hd/verilog/sky130_fd_sc_hd.v \
../../../<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.