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

scripts/yosys_common.tcl

Yes — this is the file you write

flow-a/templates/yosys_common.tcl.example

Bender.yml

Yes

flow-a/templates/Bender.yml.example

<design>.core

Yes

flow-a/templates/design.core.example

constraints/<design>.sdc

Only if the design has a clock

flow-a/templates/constraints.sdc.example

scripts/synth.tcl, ppa_report.tcl, pnr.tcl

No — copy verbatim

flow-a/scripts/

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):

  1. Remove any parameter override on the DUT instantiation — synthesis flattens parameters, so the mapped netlist’s module no longer declares them.

  2. 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:

  1. Run with the default tight budget_nsworst slack max comes back negative (VIOLATED).

  2. Read the “data arrival time” line at the bottom of timing_fmax.rpt — that is the real critical-path delay, exact, no bisection needed.

  3. Set budget_ns in yosys_common.tcl 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 ""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 = INF is 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.

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.