VLSI

Tcl scripting for VLSI/EDA

By CourseTron Team ·  8 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Every major EDA tool embeds a Tcl interpreter because Tcl is a tiny, embeddable "everything-is-a-string" language, and mastering procs, collection commands, and a handful of flow idioms lets you drive Synopsys, Cadence, and Siemens tools with one portable skill set.

    Open a shell in Design Compiler, Innovus, PrimeTime, Genus, IC Compiler II, or Tessent and you are talking to the same language: Tcl. For a VLSI engineer this is one of the highest-leverage things you can learn, because the scripting layer is identical across vendors even when the underlying algorithms differ wildly. This article explains why the whole industry converged on Tcl, then builds up from language fundamentals to a reusable starter you can drop into a place-and-route or static-timing flow.

    Why does every EDA tool use Tcl?

    Tcl (Tool Command Language, created by John Ousterhout in 1988) was designed from day one to be embedded. The entire interpreter is a C library of roughly 10,000 lines that a tool vendor links into their binary and extends with custom commands. When Synopsys wants report_timing or Cadence wants place_opt, they simply register a new C function as a Tcl command. To the user it looks native; under the hood it is just Tcl_CreateObjCommand().

    Three properties sealed the deal:

    • Everything is a string. There is no separate type system to marshal across the C boundary. A net name, a slack value, and a list of cells are all strings, which makes the embedding API trivial.
    • It is a real programming language. You get proc, if, for, foreach, associative arrays, and regexp — enough to express a full flow, not just a config file.
    • It was first. By the mid-1990s Synopsys adopted Tcl for dc_shell; OpenAccess and the OASIS-era tools followed, and the IEEE 1801 (UPF) and SDC constraint formats are themselves Tcl dialects. Network effects did the rest.

    Key fact: SDC (Synopsys Design Constraints) is not a bespoke format — it is pure Tcl. Commands like create_clock -period 2.0 [get_ports clk] execute in the tool's interpreter, which is why you can wrap them in loops and conditionals.

    Tcl interpreter embedded in EDA toolsYour .tcl scriptcreate_clock ...foreach c [get_cells]report_timingproc my_flow {} {...}Tclinterpreterregistered C commandsVendor C engineDesign CompilerInnovus / GenusPrimeTimeTessentCourseTron · coursetron.com
    The same Tcl script drives different vendor C engines through an embedded interpreter.

    The language in five minutes

    Tcl's syntax is almost brutally simple: a command is a word followed by arguments, separated by whitespace. Square brackets mean "run this and substitute the result"; braces defer evaluation. That is nearly the whole grammar.

    set period 2.0                   ;# a variable (a string "2.0")
    set clk [get_ports clk]          ;# command substitution
    puts "Period is $period ns"      ;# $ expands a variable
    if {$period < 1.0} {
        puts "Fast clock!"
    }
    foreach pin {A B CIN} {
        puts "input: $pin"
    }

    Two rules trip up every beginner. First, {braces} suppress substitution — they are how you pass code blocks and literal expressions. Second, expr is required for math: set x [expr {$a + $b}], never set x $a+$b. Always brace the expression inside expr for both speed and safety.

    Procs: your flow's building blocks

    A proc is a reusable procedure. In production flows you wrap every repeated step — clock setup, reporting, ECO patterns — so your top-level script reads like pseudocode.

    proc report_worst {corner {n 10}} {
        puts "=== Worst $n paths @ $corner ==="
        report_timing -delay_type max -nworst $n \
            -max_paths $n > reports/${corner}_timing.rpt
    }
    
    report_worst slow 20
    report_worst fast

    Note the default argument {n 10} and args for variadic procs. Because Tcl passes everything by value, large flows use upvar and global deliberately to share state — a common interview topic.

    Collections: the one abstraction that differs by vendor

    Here is the subtlety that separates a Tcl beginner from someone who can actually ship a flow. A plain Tcl list holds strings. But querying a 50-million-instance netlist and returning every cell as a string list would be catastrophically slow and memory-hungry. So vendors invented collections: opaque handles to objects living in the tool's C database.

    Synopsys calls them collections and iterates with foreach_in_collection; Cadence Innovus/Genus exposes similar object queries. The critical gotcha: you cannot use a normal foreach on a Synopsys collection, and $coll does not expand to a usable list.

    # Synopsys style — correct
    set viol [get_cells -filter "is_violating == true"]
    foreach_in_collection c $viol {
        set nm [get_object_name $c]
        puts "size_only $nm"
    }
    puts "Total: [sizeof_collection $viol] cells"
    ConceptSynopsys (DC/PT)Cadence (Innovus/Genus)
    Query objectsget_cells / get_pinsget_cells / get_db
    Iterateforeach_in_collectionforeach on get_db list
    Countsizeof_collectionllength
    Nameget_object_nameget_db obj .name

    Key fact: On a modern SoC, get_cells * can match tens of millions of objects. A collection returns in milliseconds; converting it to a string list can blow past gigabytes of RAM — this is exactly why the abstraction exists.

    Collection handle versus string listTool C databasemillions of cellsCollection handleopaque, fastforeach_in_collectionsizeof_collectionConverting to a plain string list of every cellcan cost gigabytes of RAM, hence collectionsCourseTron · coursetron.com
    A collection is a handle into the tool database; iterate it with vendor commands, not foreach.

    A starter you can adapt for PnR and STA

    Below is a minimal, vendor-neutral skeleton. In practice you source a project settings file, guard every stage, and write a report after each step so a crashed run is debuggable. This "sourced setup + staged flow + per-stage reports" pattern is what you will see in any real production repository.

    # ---- settings.tcl ----
    set DESIGN   aes_core
    set CLK_PER  1.25
    set REPORTS  ./reports
    file mkdir $REPORTS
    
    # ---- run.tcl ----
    source settings.tcl
    read_verilog  ${DESIGN}.v
    link_design   $DESIGN
    
    create_clock -name clk -period $CLK_PER [get_ports clk]
    set_input_delay  0.2 -clock clk [all_inputs]
    set_output_delay 0.2 -clock clk [all_outputs]
    
    proc stage {name body} {
        puts "\n>>> STAGE: $name"
        if {[catch {uplevel 1 $body} err]} {
            puts "ERROR in $name: $err"; exit 1
        }
    }
    
    stage synth   { compile_ultra }
    stage timing  { report_timing -nworst 10 > $::REPORTS/post_syn.rpt }
    stage power   { report_power  > $::REPORTS/power.rpt }

    Swap compile_ultra for place_opt/route_opt in Innovus, or drop the synthesis stage entirely in a PrimeTime sign-off script. The scaffolding — stage, catch, per-corner reports — stays identical. That portability is the whole point.

    A staged Tcl flow, one guarded proc per stageread / linksynthP&Rreporteach stage runs under catch and writes a reportCourseTron · coursetron.com
    The design object flows stage-to-stage; each stage is a guarded proc that writes a report.

    Debugging and good habits

    Real flows live or die on error handling. Wrap risky commands in catch, use set_message_severity to demote noisy warnings, and keep scripts idempotent so a re-run does not corrupt state. For tool-specific syntax lookups, prefer the interactive man/help built into dc_shell and Innovus over guessing — argument names differ subtly between tool versions and will silently no-op if misspelled. This mirrors the broader VLSI flow we cover in our RTL-to-GDS design flow guide and our deeper static timing analysis primer.

    If you want hands-on tool time on genuine Synopsys and Cadence licences, the design-services and lab team at Avecas VLSI design services runs industry flows worth studying alongside the theory.

    Want to script real tapeout flows? CourseTron's VLSI Physical Design and STA tracks put you in front of live dc_shell, Innovus, and PrimeTime sessions with guided Tcl labs. Explore the full course catalogue to get started.

    Frequently asked questions

    Do I need to master all of Tcl to work in VLSI?

    No. Around 80% of flow scripting uses a small core: set, proc, foreach/foreach_in_collection, if, regexp, catch, and the tool's own commands. Depth comes from knowing the vendor command set, not obscure language features.

    Why can't I just use Python for EDA scripting?

    Newer tools expose Python APIs, but the interpreter embedded in the binary and every legacy constraint file (SDC, UPF) is Tcl. You will read and maintain Tcl for years regardless, so it remains the foundational skill.

    What is the difference between a list and a collection?

    A Tcl list is strings in your interpreter. A collection is an opaque handle to objects in the tool's C database — far faster and smaller for large designs, but it requires vendor commands like foreach_in_collection to iterate.

    Why does my math give the wrong answer?

    Because Tcl treats everything as a string. Use expr with braced operands: set s [expr {$a + $b}]. Without expr, $a+$b is just the literal text.

    Advertisement