The previous nine lessons each looked at one part of a flow. This lesson puts them together in a small but complete example built only from open-source tools: Yosys for synthesis and OpenROAD for floorplanning, placement, clock tree synthesis, routing, extraction and timing, on the open SKY130 process. The design is deliberately tiny, so that the flow itself, not the design, is what the reader studies.
The goal is not a production flow. Open reference flows such as OpenROAD-flow-scripts and OpenLane do far more. The goal is a flow small enough to read in one sitting, in which every earlier lesson can be pointed at: a directory layout, a configuration file, stage scripts, a Makefile with real file dependencies, and a QoR summary at the end.
Reading time: about 9 minutes.
10.1 Assumptions and the design
We assume that Yosys and OpenROAD are installed and on the path, and that the SKY130 PDK has been installed with open_pdks, which provides the sky130_fd_sc_hd standard-cell library in its LEF and Liberty views (lesson 5). Paths below follow the open_pdks layout and might need adjusting for a particular installation.
The design is a 16-bit counter with an enable and an asynchronous active-low reset.
module cnt16 (input clk, input rst_n, input en, output reg [15:0] q);
always @(posedge clk or negedge rst_n)
if (!rst_n) q <= 16'd0;
else if (en) q <= q + 16'd1;
endmodule
Its constraints define a 10 ns clock and IO delays referenced to it.
create_clock -name clk -period 10.0 [get_ports clk]
set_input_delay 2.0 -clock clk [get_ports {rst_n en}]
set_output_delay 2.0 -clock clk [get_ports q*]
10.2 Directory layout
Following the conventions of lesson 5, source data, scripts and runs are kept apart, and each run has its own directory.
mini_flow/
Makefile
config.mk # design settings (section 10.3)
src/cnt16.v
constraints/cnt16.sdc
scripts/
syn.tcl floorplan.tcl place.tcl cts.tcl route.tcl report.tcl
common.tcl # shared loading code for OpenROAD stages
qor.py # lesson 7 parser
runs/
base/ # one directory per TAG
syn.v fp.odb place.odb cts.odb route.odb route.spef
*.log qor.json
10.3 The configuration file
Lesson 3 separated configuration from scripts. Here the configuration is written in Make syntax, so that the Makefile can include it directly; export passes each variable to the tools as an environment variable, which Tcl reads as $::env(NAME).
# config.mk -- everything that is specific to this design
TAG ?= base
LIBDIR = /tools/pdk/sky130A/libs.ref/sky130_fd_sc_hd
export DESIGN = cnt16
export VERILOG = src/cnt16.v
export SDC = constraints/cnt16.sdc
export TECH_LEF = $(LIBDIR)/techlef/sky130_fd_sc_hd__nom.tlef
export CELL_LEF = $(LIBDIR)/lef/sky130_fd_sc_hd.lef
export LIB = $(LIBDIR)/lib/sky130_fd_sc_hd__tt_025C_1v80.lib
export CLOCK_PERIOD = 10.0
export CORE_UTIL = 40
export PLACE_DENSITY = 0.60
export RCX_RULES = /tools/orfs/platforms/sky130hd/rcx_patterns.rules
export RUN = runs/$(TAG)
Notice that the Liberty file names a corner – typical process, 25 °C, 1.80 V – as lesson 5 required. The extraction rules file for OpenRCX is not part of the PDK itself; the path shown points to the copy distributed with the OpenROAD flow scripts' platform files.
10.4 The stage scripts
Synthesis
Yosys runs a Tcl script with yosys -c. In this mode, Yosys commands are called through the yosys Tcl command.
# scripts/syn.tcl
set lib $::env(LIB)
yosys read_verilog -sv $::env(VERILOG)
yosys synth -top $::env(DESIGN) -flatten
yosys dfflibmap -liberty $lib
yosys abc -liberty $lib -D [expr {$::env(CLOCK_PERIOD) * 1000}]
yosys hilomap -hicell sky130_fd_sc_hd__conb_1 HI -locell sky130_fd_sc_hd__conb_1 LO
yosys opt_clean -purge
yosys tee -o $::env(RUN)/syn_stat.rpt stat -liberty $lib
yosys write_verilog -noattr -noexpr $::env(RUN)/syn.v
dfflibmap maps flip-flops onto library cells, abc maps the combinational logic with a delay target in picoseconds (hence the factor 1000), and hilomap replaces constant drivers with tie cells.
OpenROAD stages
The OpenROAD database stores the design and its LEF data but not the Liberty timing or the SDC, so every stage after the first loads them again. That shared code goes in one file, in the spirit of lesson 3.
# scripts/common.tcl -- load the previous stage's database
proc load_stage {prev} {
read_db $::env(RUN)/$prev.odb
read_liberty $::env(LIB)
read_sdc $::env(SDC)
set_wire_rc -layer met2
}
# scripts/floorplan.tcl
read_lef $::env(TECH_LEF)
read_lef $::env(CELL_LEF)
read_liberty $::env(LIB)
read_verilog $::env(RUN)/syn.v
link_design $::env(DESIGN)
read_sdc $::env(SDC)
check_setup -verbose
initialize_floorplan -utilization $::env(CORE_UTIL) -aspect_ratio 1 \
-core_space 2 -site unithd
make_tracks
place_pins -hor_layers met3 -ver_layers met2
tapcell -distance 14 -tapcell_master sky130_fd_sc_hd__tapvpwrvgnd_1
write_db $::env(RUN)/fp.odb
# scripts/place.tcl
source scripts/common.tcl
load_stage fp
global_placement -density $::env(PLACE_DENSITY)
estimate_parasitics -placement
repair_design
detailed_placement
check_placement
write_db $::env(RUN)/place.odb
# scripts/cts.tcl
source scripts/common.tcl
load_stage place
clock_tree_synthesis -root_buf sky130_fd_sc_hd__clkbuf_4 \
-buf_list sky130_fd_sc_hd__clkbuf_4
set_propagated_clock [all_clocks]
estimate_parasitics -placement
repair_timing -hold
detailed_placement
write_db $::env(RUN)/cts.odb
# scripts/route.tcl
source scripts/common.tcl
load_stage cts
set_propagated_clock [all_clocks]
global_route
detailed_route -output_drc $::env(RUN)/route_drc.rpt
write_db $::env(RUN)/route.odb
# scripts/report.tcl
source scripts/common.tcl
load_stage route
set_propagated_clock [all_clocks]
define_process_corner -ext_model_index 0 X
extract_parasitics -ext_model_file $::env(RCX_RULES)
write_spef $::env(RUN)/route.spef
report_checks -path_delay max
report_wns
report_tns
report_design_area
report_power
Several checks from lesson 9 appear as ordinary commands: check_setup before any physical work, check_placement after placement, and the router's DRC report after routing. Two things are deliberately left out to keep the scripts short. The floorplan does not build a power grid – a real flow would use OpenROAD's pdngen commands, and without a grid the layout would fail LVS. And the flow stops at a routed database with extracted parasitics; producing GDS and running DRC and LVS with the foundry decks would use further tools such as KLayout, Magic and Netgen.
10.5 The Makefile
Each stage is a file target whose prerequisites are the previous stage's output, its own script and the configuration – the rules of lesson 4. Each recipe also applies lesson 3's rule that the log, not only the exit code, decides success.
# Makefile
include config.mk
OR := openroad -no_init -exit
.PHONY: all syn floorplan place cts route report qor clean
.DELETE_ON_ERROR:
all: qor
syn: $(RUN)/syn.v
floorplan: $(RUN)/fp.odb
place: $(RUN)/place.odb
cts: $(RUN)/cts.odb
route: $(RUN)/route.odb
report: $(RUN)/report.log
qor: $(RUN)/qor.json
$(RUN)/syn.v: $(VERILOG) scripts/syn.tcl config.mk
mkdir -p $(RUN)
yosys -c scripts/syn.tcl -l $(RUN)/syn.log
$(RUN)/fp.odb: $(RUN)/syn.v $(SDC) scripts/floorplan.tcl config.mk
$(OR) -log $(RUN)/floorplan.log scripts/floorplan.tcl
! grep -q '^\[ERROR' $(RUN)/floorplan.log
$(RUN)/place.odb: $(RUN)/fp.odb scripts/place.tcl scripts/common.tcl config.mk
$(OR) -log $(RUN)/place.log scripts/place.tcl
! grep -q '^\[ERROR' $(RUN)/place.log
$(RUN)/cts.odb: $(RUN)/place.odb scripts/cts.tcl scripts/common.tcl config.mk
$(OR) -log $(RUN)/cts.log scripts/cts.tcl
! grep -q '^\[ERROR' $(RUN)/cts.log
$(RUN)/route.odb: $(RUN)/cts.odb scripts/route.tcl scripts/common.tcl config.mk
$(OR) -log $(RUN)/route.log scripts/route.tcl
! grep -q '^\[ERROR' $(RUN)/route.log
$(RUN)/report.log: $(RUN)/route.odb scripts/report.tcl scripts/common.tcl
$(OR) -log $@ scripts/report.tcl
! grep -q '^\[ERROR' $@
$(RUN)/qor.json: $(RUN)/report.log scripts/qor.py
python3 scripts/qor.py $(RUN)/route.log $(RUN)/report.log > $@
clean:
rm -rf $(RUN)
Typing make runs the whole flow. Editing PLACE_DENSITY in config.mk and typing make TAG=dens70 starts a fresh run directory, leaving runs/base intact for comparison. Editing only scripts/route.tcl and typing make reruns routing, reporting and QoR, and nothing before them. To send each recipe to a farm, the tool command would be prefixed with bsub -K or wrapped in sbatch --wait, as lesson 6 described.
make can decide from timestamps what must rerun. The list below the row shows where each earlier lesson of the path appears; notice that nothing in the flow is new – it is the earlier pieces placed in order.10.6 The QoR summary
The final target runs the parser of lesson 7 on the routing log (for the DRC count) and the report log (for timing, area and power). A few extra lines merge the two records into one row per run, which is then easy to tabulate.
Assumptions: runs/base uses the configuration above. runs/util60 differs only in CORE_UTIL = 60. The values below are illustrative of the shape of the result; actual numbers depend upon tool and PDK versions.
| Run | Util. | WNS | TNS | Cell area | Power | DRC | Missing |
|---|---|---|---|---|---|---|---|
| base | 40% | 0.000 | 0.000 | 690 | 2.4e-05 | 0 | none |
| util60 | 60% | 0.000 | 0.000 | 688 | 2.4e-05 | 0 | none |
For a design this small at 100 MHz, both runs meet timing with a wide margin, and OpenSTA's report_wns and report_tns print 0 when there is no violation – the sign convention lesson 7 warned about. The useful result here is the empty "Missing" column: every metric was found, so the flow ran to completion and the parser still understands the reports. On a larger design, the same table would show the trade-off seen in lesson 7.
From here, the path to the remaining lessons is short. Adding runs/base/qor.json as the golden record and running compare.py from lesson 8 in a CI job turns this flow into a regression. Adding a power grid, GDS stream-out and DRC and LVS with real rule decks turns it toward signoff, with the checklist of lesson 9 as the measure of completion.
10.7 Interview questions
Why does every OpenROAD stage after the floorplan read Liberty and SDC again? The OpenROAD database stores the design and its physical library data but not the timing libraries or the constraints, so they must be loaded in each new session before any timing-driven step.
Why does each Makefile recipe grep the log after running the tool? A tool may print an error and still exit with status 0. The grep returns a non-zero status if an error line exists, so make stops at the failing stage, and .DELETE_ON_ERROR removes the suspect output.
What would you add to make this mini flow tape-out capable? A power grid, stream-out to GDS with merged cell layouts, DRC, LVS and antenna checks with the foundry decks, multi-corner signoff STA, IR drop and EM analysis, and equivalence checking, all judged against a signoff checklist.
How would you run two configurations of this flow side by side? Use different TAG values so that each run writes its own directory, then compare their qor.json records in one table.
Key takeaways
- A complete mini flow needs only a few parts: configuration, stage scripts, a Makefile of file targets, and a QoR parser.
- Yosys and OpenROAD on SKY130 are enough to go from RTL to a routed, extracted and timed design with open tools.
- Each stage writes one file that the next reads, so timestamps decide reruns and a
TAGkeeps experiments apart. - Checks such as
check_setupandcheck_placement, and log scanning in every recipe, catch failures at the stage that caused them. - Power grid, GDS stream-out, physical verification and full signoff are the steps that separate this teaching flow from a production one.