A script that repeats the same five lines for each stage of a flow is a script waiting for a typing error. A procedure gives those lines a name, takes the parts that change as arguments, and returns a result. This lesson covers how to write procedures, where their variables live, how to pass a variable by name, and how to keep related procedures together.
Reading time: about 8 minutes.
7.1 Defining a procedure
proc half {x} { return [expr {$x / 2.0}] }
puts [half 5]
proc slack_of {required arrival {margin 0.0}} {
return [expr {$required - $arrival - $margin}]
}
puts [slack_of 2.0 1.75]
puts [slack_of 2.0 1.75 0.125]
This prints:
2.5
0.25
0.125
proc takes a name, a list of arguments and a body. An argument written as {margin 0.0} has a default value, so the caller may leave it out. A procedure returns the value of return, or the value of its last command if return is absent.
If a caller gives the wrong number of arguments, Tcl says so:
catch {half} err
puts $err
catch {half 1 2} err
puts $err
Both print wrong # args: should be "half x".
7.2 A variable number of arguments
An argument called args at the end collects all remaining arguments into a list.
proc total {args} {
set sum 0
foreach a $args { set sum [expr {$sum + $a}] }
return $sum
}
puts [total]
puts [total 1 2 3]
This prints 0 and 6. This is the pattern used by tool commands that accept any number of cell names or options.
7.3 Returning more than one value
Return a list and take it apart with lassign, or return a dictionary.
proc stats {values} {
set n [llength $values]
set sum 0.0
foreach v $values { set sum [expr {$sum + $v}] }
return [list $n [expr {$sum / $n}]]
}
lassign [stats {1.0 2.0 4.5}] n mean
puts "n=$n mean=$mean"
This prints n=3 mean=2.5. A dictionary is clearer when there are many fields, and Lesson 8 shows how to build and read one.
7.4 Where variables live
A procedure has its own local variables. A name used inside a procedure is not the same as a name outside, even if they look the same.
set x global_value
proc show {} {
set x local_value
return $x
}
puts [show]
puts $x
This prints:
local_value
global_value
To use a variable from the top level inside a procedure, declare it with global.
proc bump {} { global counter; incr counter }
set counter 0
bump; bump
puts $counter
This prints 2. Without the declaration, the procedure cannot see the variable:
catch {proc noglobal {} { return $counter }; noglobal} err
puts $err
This prints can't read "counter": no such variable.
Globals are convenient for settings that every step reads, such as a design name. Use them sparingly. A procedure that depends on several globals is hard to test, and passing values as arguments is clearer.
7.5 Passing a variable by name with upvar
A procedure receives values, not variables. To let it change a variable of the caller, pass the name and use upvar to make a local alias.
proc add_item {listName item} {
upvar 1 $listName l
lappend l $item
}
set cells {}
add_item cells u1
add_item cells u2
puts $cells
This prints u1 u2. Figure 7.1 shows the alias. The 1 means one level up in the call stack, which is the caller. Notice that the call writes cells, not $cells. With $cells, the procedure would receive the contents and could not change the variable.
The same technique writes a swap:
proc swap {aName bName} {
upvar 1 $aName a $bName b
set t $a; set a $b; set b $t
}
set p 1; set q 2
swap p q
puts "$p $q"
This prints 2 1.
7.6 Running code in the caller with uplevel
uplevel evaluates a script in the caller's context. With it you can write your own control commands.
proc repeat {n body} {
for {set i 0} {$i < $n} {incr i} { uplevel 1 $body }
}
set hits 0
repeat 3 { incr hits }
puts $hits
This prints 3. The body ran in the caller, so it could see and change hits. This is also how wrappers such as a timed procedure in Lesson 11 run the code you pass to them.
7.7 Returning a record
A procedure that finds the worst path can return a dictionary with named fields.
proc worst {paths} {
set best ""
foreach p $paths {
lassign $p s e sl
if {$best eq "" || $sl < [dict get $best slack]} {
set best [dict create start $s end $e slack $sl]
}
}
return $best
}
set w [worst {{ff_a ff_b -0.12} {ff_e ff_f -0.45}}]
puts $w
puts [dict get $w end]
This prints:
start ff_e end ff_f slack -0.45
ff_f
Each field has a name, so the caller does not need to remember which position holds the slack.
7.8 Namespaces
As a script grows, procedure names collide. A namespace groups related names under a prefix, with :: as the separator. Variables inside a namespace live with it and stay out of the global space.
namespace eval flow {
variable steps_run 0
namespace export run_step
proc run_step {name} {
variable steps_run
incr steps_run
return "ran $name ($steps_run)"
}
proc count {} { variable steps_run; return $steps_run }
}
puts [flow::run_step floorplan]
puts [flow::run_step place]
puts [flow::count]
namespace eval util { proc run_step {name} { return "util says $name" } }
puts [util::run_step x]
namespace eval other { namespace import ::flow::run_step }
puts [other::run_step cts]
This prints:
ran floorplan (1)
ran place (2)
2
util says x
ran cts (3)
Two namespaces can each have a run_step without a clash. variable inside a procedure connects it to the namespace's variable. namespace export lists the names others may import, and namespace import makes them available without the prefix. For a flow library, one namespace per area, such as flow, report and util, keeps names predictable.
7.9 Looking inside
info answers questions about procedures, which helps when you read a colleague's script.
puts [info args slack_of]
puts [string trim [info body half]]
puts [info commands half]
puts [info procs slack*]
This prints:
required arrival margin
return [expr {$x / 2.0}]
half
slack_of
7.10 A small Innovus example
A procedure that picks a timing stage turns a name into the tool's option. To be run at the Innovus prompt. Check the options against your release with timeDesign -help.
Legacy UI.
proc time_stage {stage {paths 50}} {
switch -exact -- $stage {
preCTS { timeDesign -preCTS -numPaths $paths }
postCTS { timeDesign -postCTS -numPaths $paths }
postRoute { timeDesign -postRoute -numPaths $paths }
default { error "unknown stage: $stage" }
}
}
time_stage postCTS 100
The reference for timeDesign lists -preCTS, -postCTS and -postRoute as stage selectors and -numPaths with a default of 50, so the procedure uses the same default. The procedure rejects an unknown stage with a clear message, so a typo is caught on the first line of the log.
7.11 Why an interviewer asks about this
Typical questions: "what does upvar do", "how do you pass an array or list to a procedure and change it", and "when would you use a namespace". The strong answers say that upvar aliases a caller variable by name, that you pass the name and not the value, and that namespaces prevent clashes and keep state tidy. A follow-up may ask for the difference between global and variable, which differ in where the variable lives: the top level or the current namespace.
Key takeaways
- A procedure takes arguments, can have defaults and an
argstail, and returns a value or a record. - Variables are local unless you use
global,variableorupvar. - To change the caller's variable, pass its name and use
upvar 1. - Namespaces keep procedure names and state apart, and
infolets you inspect them.