Tcl · Module 21
Compilation and Its Options
Compile is where the hours go and where the result is decided. Its options are also the most-copied lines in EDA scripting — inherited from a script that worked once, for reasons nobody recorded.
Advanced15 min readtcledapart-iisynthesiscompileoptionsreproducibility
Part II · Module 21 · Chapter 21.7 · Compilation and Its Options
Compile is the phase that does the work: logic is optimised, mapped onto library cells, and sized to meet the constraints. It is where the runtime goes — minutes for a small block, many hours for a large one — and where the quality of the result is decided.
It is also, in my experience of reading flow scripts, where the largest number of unexplained options live.
1. What compile does
Three things, roughly in order:
- Optimises the logic — restructuring to reduce depth or area while preserving function.
- Maps to real cells — choosing library cells that implement the logic, which is what the libraries from Chapter 21.4 were for.
- Sizes and fixes — choosing drive strengths, inserting buffers, and resolving violations against the constraints from Chapter 21.6.
All three are driven by the constraints. A design compiled with no clock has no timing target, so the optimiser has nothing to optimise toward — which is why the order in Chapter 21.1 §2 is not negotiable.
2. What the options control
Without naming flags, the categories are consistent across tools:
Effort. How hard the tool tries. More effort costs runtime and usually improves the result — with diminishing returns, and not monotonically: a higher effort level can occasionally produce a worse result for a particular design.
Scope. Whether optimisation is allowed across hierarchy boundaries, or each block is handled separately. Crossing boundaries gives better results and makes the netlist harder to relate back to the RTL.
What may change. Whether the tool may restructure logic, retime across registers, or alter the hierarchy. Each gives the optimiser more freedom and makes the output less recognisable.
What is preserved. Which instances or hierarchies must survive unchanged — usually because something downstream depends on their names.
Which goal wins. Area, timing or power, when they conflict. Most tools let you weight this.
3. The question to ask of every option
For each option in a compile line you are responsible for:
What does this change, and why is that right for this block?
If the answer is "it was in the script I copied", that is Chapter 17.6 §2's problem in the most expensive place it occurs. Compile options directly determine the result, they often cost hours of runtime, and an option that was necessary for a different block three years ago may be actively harmful here.
The remedy is the same: look it up once, and write down why.
# -<option>: this block has a hard macro whose instance name the
# place-and-route scripts depend on, so the hierarchy must be preserved.
compile_design <options>That comment survives the person who wrote it, which a memory does not.
4. Record what you ran
The Part I half, and it is what makes a result reproducible:
proc run_compile {opts logvar} {
upvar 1 $logvar log
set start [clock seconds]
dict set log compile_options $opts
dict set log compile_started [clock format $start -format "%Y-%m-%d %H:%M:%S"]
compile_design {*}$opts ;# the tool command
set elapsed [expr {[clock seconds] - $start}]
dict set log compile_seconds $elapsed
puts [format " compile finished in %d:%02d with options: %s" \
[expr {$elapsed / 60}] [expr {$elapsed % 60}] $opts]
return $elapsed
} compile finished in 0:02 with options: -effort high -preserve_hierarchy
recorded: -effort high -preserve_hierarchy(The option names above are placeholders standing in for whatever your tool calls them — the point is that they were recorded, not what they are.)
Four things that gives you:
- The options are in the log, so a result can be tied to how it was produced.
{*}$optsexpands a list into arguments — Chapter 14.8 §5. Built withlistrather than as a string, so an option containing a space survives.- The runtime is recorded, which is how you notice that this week's run took three hours instead of forty minutes.
- It goes into a dict that Chapter 21.9 §4 writes beside the netlist.
5. Options as data
Because the options are a list, they can be built rather than written:
set opts [list]
if {$EFFORT eq "high"} { lappend opts <high-effort-option> }
if {$PRESERVE_HIER} { lappend opts <preserve-option> }That makes the compile line configurable from the command line — Chapter 15.3 — so an exploratory run at low effort and a signoff run at high effort are the same script with different arguments rather than two scripts that have drifted apart.
6. Compile is where runs go wrong slowly
Three practical points about the phase that takes the hours.
Fail before it, not during it. Everything in Chapters 21.2 through 21.6 is cheap. Each of their checks exists so a run that cannot succeed stops in the first seconds rather than after three hours of compile.
A long run is not a working run. Compile finishing says nothing about quality — that is what Chapter 21.8's reports are for. A run that compiled for four hours and met nothing is still a failed run, and the exit status should say so.
Runtime is a signal. Record it. A compile that suddenly takes four times as long usually means something changed — a constraint became unachievable, a library was swapped, the design grew. Noticing it in the log is cheaper than discovering it in the schedule.
7. Exercises
Exercise 1. Name the three things compile does.
Exercise 2. Why does compiling an unconstrained design produce a poor result rather than an error?
Exercise 3. Name four categories of thing compile options control.
Exercise 4. What question should every option in your compile line have an answer to?
Exercise 5. Why {*}$opts rather than building the options as a string?
Exercise 6. Why is one script with options better than two scripts for exploration and signoff?
Exercise 7. Why record the compile runtime?
Where this fits
Part of the Tcl curriculum.
