========================================== 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 (:doc:`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. .. list-table:: :header-rows: 1 * - 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`` * - ``.core`` - Yes - ``flow-a/templates/design.core.example`` * - ``constraints/.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 ============== .. code-block:: text / ├── Bender.yml ├── .core ├── rtl/*.sv ├── tb/*.sv ├── sim/_sim.flist (generated, not committed) ├── yosys/.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/.sdc (clocked designs only) .. code-block:: sh mkdir -p /{rtl,tb,sim,yosys,scripts,constraints} cd cp /flow-a/scripts/*.tcl scripts/ cp /flow-a/templates/yosys_common.tcl.example scripts/yosys_common.tcl 1. Bender.yml — declare your sources ======================================= .. code-block:: yaml package: 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: .. code-block:: sh bender script flist -t synthesis > yosys/.flist bender script flist -t simulation > sim/_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: .. code-block:: sh 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/.sv # sort into in_ports (every input except a clock), out_ports, and # clk_port (leave "" if the design is purely combinational) .. code-block:: text set common_dir [file dirname [file normalize [info script]]] set project_root [file normalize $common_dir/..] set top_design set proj_name set build_name thesis_flowA__ ;# deterministic from the .core file's CAPI2 name field: ;# "thesis:flowA::0.1.0" -> "thesis_flowA__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 ======================================== .. code-block:: yaml CAPI=2: name: thesis:flowA::0.1.0 description: filesets: sim_flist: files: - sim/_sim.flist : {file_type: user, copyto: _sim.flist} targets: sim: default_tool: icarus filesets: [sim_flist] toplevel: tools: icarus: iverilog_options: [-g2012, -f, _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. .. code-block:: sh source ~/fusesoc-venv/bin/activate source ~/thesis/tools/oss-cad-suite/environment fusesoc --cores-root . run --target=sim thesis:flowA: Confirm a clean pass before moving on. 4. Synthesis ============== Add the ``synth`` target alongside ``sim``: .. code-block:: yaml synth: default_tool: yosys toplevel: tools: yosys: arch: generic output_format: verilog output_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: .. code-block:: text 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. .. code-block:: sh fusesoc --cores-root . run --target=synth thesis:flowA: Sanity checks before trusting the result: .. code-block:: sh 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/_mapped.v # expect 0 unless you deliberately synthesized with power pins See :doc:`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 ========================================== .. code-block:: sh mkdir -p build/${build_name}/gls-icarus cd build/${build_name}/gls-icarus cp ../../../tb/.sv _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. .. code-block:: sh 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/_mapped.v \ _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: .. code-block:: text 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 .. code-block:: sh 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_ns`` — ``worst 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/.sdc — clocked designs only ========================================================== Skip this entirely if ``clk_port`` is ``""`` — ``pnr.tcl`` applies ``set_max_delay`` directly for combinational designs. .. code-block:: text create_clock -name -period [get_ports ] set_clock_uncertainty [get_clocks ] set_input_delay -max -clock [get_ports {}] set_output_delay -max -clock [get_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): .. code-block:: sh 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: ``_placed.def``, ``_route.v``, ``_route.sdf``, ``_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 =============================================== .. code-block:: sh cd build/${build_name}/gls-icarus cp ../../../tb/.sv _sdf.sv Apply the same two fixes as step 5, plus an ``$sdf_annotate`` call right after the DUT instantiation: .. code-block:: systemverilog initial begin $sdf_annotate("../../../_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 :doc:`troubleshooting` for the full pattern, including the distinct clocked-design variant of this same class of issue. .. code-block:: sh 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 \ ../../../_route.v \ _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: 1. Add a VCD dump to a copy of the gate-level testbench: .. code-block:: systemverilog initial begin $dumpfile("activity.vcd"); $dumpvars(0, .dut); end 2. Re-run the same gate-level simulation (step 5 or 9), confirming it still passes before trusting the VCD. 3. 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: .. code-block:: text read_vcd -scope /dut build/.../gls-icarus/activity.vcd report_power > power_activity.rpt 4. 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 :doc:`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.