Tcl in EDA tools
- Drive Yosys synthesis from a Tcl script with
yosys -candyosys -import - Write timing constraints as Tcl in SDC form with
create_clock,set_input_delayandset_output_delay - Run static timing with an OpenSTA-style script
- Make synthesis runs reproducible
- Extract the numbers that matter from tool reports
Explanation
One language, many tools
Open-source implementation tools share Tcl as their command language. Yosys accepts Tcl scripts; OpenSTA and OpenROAD are Tcl shells; and SDC constraint files are Tcl commands that every timing tool reads. The fundamentals from the previous lesson apply unchanged: variables, [command substitution], lists, proc, foreach and catch.
| Tool | How Tcl is used | Typical command |
|---|---|---|
| Yosys | Synthesis scripts | yosys -c synth.tcl |
| OpenSTA | Static timing analysis shell | sta -exit sta.tcl |
| OpenROAD | Floorplan to routing, plus STA | openroad -exit flow.tcl |
| SDC file | Clocks, I/O delays, exceptions | read_sdc constraints.sdc |
Yosys from Tcl
yosys -c synth.tcl runs a Tcl script inside Yosys. Each Yosys command is available through the yosys Tcl command, for example yosys read_verilog rtl/fifo.v. After yosys -import, most Yosys commands can be called directly as Tcl commands. Commands whose names clash with Tcl built-ins, notably proc, must still be called as yosys proc. A failing Yosys command stops the run with a non-zero exit status, which Make or a CI job sees.
Tcl adds what plain Yosys scripts lack: variables, loops over source files, and values from the environment.
set top [expr {[info exists ::env(TOP)] ? $::env(TOP) : "fifo"}]
set srcs [lsort [glob -nocomplain rtl/*.v]]
if {[llength $srcs] == 0} { error "no RTL found in rtl/" }
Constraints are Tcl
An SDC file is a Tcl script with a standard set of timing commands. Because it is Tcl, you can compute values rather than copy numbers by hand.
set clk_name core_clk
set clk_port [get_ports clk]
set clk_period 10.0
set io_delay [expr {0.2 * $clk_period}]
create_clock -name $clk_name -period $clk_period $clk_port
set_clock_uncertainty 0.25 [get_clocks $clk_name]
set_input_delay $io_delay -clock $clk_name [delete_from_list [all_inputs] $clk_port]
set_output_delay $io_delay -clock $clk_name [all_outputs]
| Command | Meaning |
|---|---|
create_clock -period P port |
A clock of period P (ns) arrives on this port |
set_clock_uncertainty |
Margin for jitter and skew before layout |
set_input_delay D -clock C ports |
Inputs arrive D ns after the clock edge, from outside logic |
set_output_delay D -clock C ports |
Outside logic needs outputs D ns before the next edge |
get_ports, all_inputs, all_outputs |
Return collections of design objects |
The clock port is removed from the input-delay list because a clock is not data. delete_from_list is an OpenSTA command; other tools offer similar helpers.
Reproducible runs
A synthesis result should be repeatable from the repository alone. Pin the tool version and record it, keep scripts and constraints under version control, list sources in a sorted order, and write every report to a named file under build/. Record the exact command line in the run log.
Reading reports
Reports are text, so the parsing techniques from earlier lessons apply. Report formats can change between tool releases: pin the version, test your regular expressions against a real report from that version, and make the script fail loudly when an expected line is missing rather than reporting zero.
Worked example
synth.tcl synthesises the top module to a standard-cell library from an open-source PDK and saves the statistics and netlist. The Liberty path comes from the environment so the script works on every machine.
yosys -import
set top [expr {[info exists ::env(TOP)] ? $::env(TOP) : "fifo"}]
set lib $::env(LIBERTY)
set srcs [lsort [glob -nocomplain rtl/*.v]]
if {[llength $srcs] == 0} { error "no RTL found in rtl/" }
file mkdir build
foreach src $srcs { read_verilog -defer $src }
hierarchy -check -top $top
synth -top $top
dfflibmap -liberty $lib
abc -liberty $lib
opt_clean
tee -o build/${top}_stat.txt stat -liberty $lib
write_verilog -noattr build/${top}_netlist.v
Run it, then check timing with OpenSTA:
export LIBERTY=$PDK_ROOT/sky130A/libs.ref/sky130_fd_sc_hd/lib/sky130_fd_sc_hd__tt_025C_1v80.lib
mkdir -p build
TOP=fifo yosys -c synth.tcl -l build/yosys.log
yosys -V >> build/yosys.log
# sta.tcl - run with: sta -exit sta.tcl
read_liberty $::env(LIBERTY)
read_verilog build/fifo_netlist.v
link_design fifo
read_sdc constraints.sdc
report_checks -path_delay max -digits 3 > build/fifo_timing.rpt
report_wns > build/fifo_wns.rpt
report_tns > build/fifo_tns.rpt
A short Tcl check then gates the flow on the result. It runs in plain tclsh because it only reads files:
set fh [open build/fifo_wns.rpt r]
set text [read $fh]
close $fh
if {![regexp {wns\s+(?:max\s+)?(-?[0-9.]+)} $text -> wns]} {
puts stderr "no WNS value found in build/fifo_wns.rpt"
exit 2
}
puts "worst negative slack: $wns ns"
exit [expr {$wns < 0}]
hierarchy -check fails early on a missing module. tee -o saves the stat output to a file while still printing it. The SDC file uses the Tcl shown earlier, so changing clk_period in one place updates every delay.
Check yourself
What is the difference between yosys read_verilog f.v and read_verilog f.v after yosys -import? Why must proc be called as yosys proc? Why is the clock port removed before set_input_delay? What makes a synthesis run reproducible? Why should a report-parsing script fail when it cannot find the expected line instead of assuming zero?
Practice: Tcl in EDA tools
Wrap the flow in a Makefile with synth, sta and check targets so that editing an RTL file reruns synthesis but editing only constraints.sdc reruns only timing. Add a Tcl foreach over three clock periods (10, 8 and 6.5 ns) that writes one SDC file per period, runs STA for each, and prints a table of period against worst slack. Record the Yosys and OpenSTA versions in each report folder.
Practice work is non-certified and is not stored by ASIC Campus.