Tcl for EDA · lesson 3

Variables and expressions

Setting and reading variables, doing arithmetic with expr, integer versus floating point, comparisons, and why expressions go in braces.

7 min read · Free, no login

Flow scripts keep settings in variables, compute numbers from them and compare results with limits. This lesson covers the commands for that work, the difference between integer and floating-point arithmetic, and one rule about expr that avoids a whole class of bugs and speeds up loops.

Reading time: about 7 minutes.

3.1 Setting and changing variables

A variable is created the first time you set it. There are no declarations and no types.

set count 0
incr count
incr count 5
puts $count
set s "clk"
append s "_gated" "_en"
puts $s

This prints:

6
clk_gated_en

incr adds an integer, one by default, and creates nothing if the variable is missing, so set it first. append adds text to the end of a variable without copying it, which is the efficient way to build a long string piece by piece.

3.2 Does it exist?

Reading a variable that was never set is an error. Check first when a variable is optional.

puts [info exists nosuch]
set nosuch 1
puts [info exists nosuch]
unset nosuch
puts [info exists nosuch]

This prints:

0
1
0

info exists returns 1 or 0 and never fails. unset removes a variable, which is useful when a script must not carry a setting from one step to the next.

3.3 Arithmetic with expr

Arithmetic is not part of the language syntax. It is done by the expr command, which takes an expression and evaluates it.

puts [expr {7 / 2}]
puts [expr {7 / 2.0}]
puts [expr {7 % 3}]
puts [expr {2 ** 10}]
puts [expr {int(2.7)}]
puts [expr {round(2.5)}]
puts [expr {sqrt(16)}]

This prints:

3
3.5
1
1024
2
3
4.0

The first line is the common trap: two integers divide as integers, so 7 / 2 is 3. Make one of them a floating-point number to get 3.5. This matters in timing arithmetic. Dividing a period of 2 by 4 gives 0, while dividing 2.0 by 4 gives 0.5. Write time values with a decimal point.

Common functions include abs, int, round, sqrt, pow, min and max.

3.4 Always brace the expression

Compare these four lines.

set a 4
puts [expr $a*2]
puts [expr {$a*2}]
set b "3 + 4"
puts [expr $b]
puts [expr {$b}]

This prints:

8
8
7
3 + 4

The unbraced form gave the right answer for a but treated the contents of b as part of the expression, so it evaluated 3 + 4 and returned 7. The braced form saw $b as one value and returned it unchanged. Two reasons to brace every expression follow.

  1. Safety. An unbraced expression is substituted by Tcl first and then evaluated again by expr, so a variable that holds text with special characters can change the meaning, or even run a command.
  2. Speed. A braced expression can be compiled once and reused. In a loop that runs thousands of times, the difference is large.

Make expr {...} a habit.

3.5 Comparisons and conditions

Comparison operators are ==, !=, <, >, <=, >=. For strings, use eq and ne. Logical operators are &&, || and !. The conditional operator ? : picks one of two values.

puts [expr {"1.0" == 1}]
puts [expr {"abc" eq "abc"}]
puts [expr {5 > 3 && 2 > 1}]
puts [expr {-0.12 < 0 ? "violated" : "met"}]

This prints:

1
1
1
violated

The first result shows that == compares numerically when both sides look like numbers, so "1.0" == 1 is true. If you need exact text equality, use eq. A slack value of -0.12 compared with zero gives a violated setup path, which is the sort of test a flow script makes after a timing step.

3.6 Units and conversions

Tcl does not know about nanoseconds or picofarads. The number is just a number, and the unit comes from the tool's library or settings. A script that mixes units gives wrong answers without any error.

set period_ns 2.0
set load_pf 0.020
puts "period in ps: [expr {$period_ns * 1000}]"
puts "load in fF: [expr {$load_pf * 1000}]"

This prints:

period in ps: 2000.0
load in fF: 20.0

When a tool returns a number, check the unit it uses before comparing it with a limit. Constraint files and reports can use different units, and the unit is stated in the library or in the tool's settings, not in the number.

3.7 Floating-point surprises

Floating-point numbers are stored in binary, so some decimal values are not exact. Formatting hides the difference when you print.

puts [format "%.3f" 0.1]
puts [format "%.2f" 1.005]

This prints:

0.100
1.00

The second line shows 1.005 rounding to 1.00, because the stored value is slightly below 1.005. For timing work this is rarely a problem, but do not test two computed floats for equality. Compare with a small tolerance, for example expr {abs($a - $b) < 1e-9}.

3.8 A small Innovus example

Let us consider a script that asks for more timing paths than the default.

Legacy UI. To be run at the Innovus prompt. Check the options against your release with timeDesign -help.

set n 100
timeDesign -postCTS -numPaths $n

The variable holds the integer, and Tcl substitutes it into the command. The timeDesign command takes an integer for -numPaths, with a default of 50 in the Legacy reference. Changing the number in one place, at the top of the script, is the whole value of using a variable here.

3.9 Why an interviewer asks about this

Expect "why do you put expr expressions in braces" and "what does expr {7/2} return". The strong answer to the first gives both reasons, safety and speed. For the second, say 3, explain that integers divide as integers, and show that 7/2.0 returns 3.5. A follow-up may ask how you would compare two delay numbers that came from different reports, and the expected answer mentions units first.

Key takeaways

  • set, incr and append create and change variables, and info exists checks without failing.
  • Integers divide as integers, so write time values with a decimal point.
  • Always write expr {...} with braces for safety and speed.
  • Tcl numbers carry no units, so check the unit of any value a tool returns.