Tcl for EDA · lesson 7

Procedures, scope and namespaces

Writing reusable procedures with default and variable arguments, where variables live, passing names with upvar, and keeping flow code tidy with namespaces.

8 min read · Free, no login

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.

Caller: step_placeset cells {u1 u2}add_item (callee)upvar 1 $listName llappend l u3l is an alias for cellsupvar makes a local name point at a variable in the caller's frame.Pass the NAME of the variable, not its value.
Figure 7.1 – upvar links a local name in the callee to a variable in the caller’s frame, by name, not by value.

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 args tail, and returns a value or a record.
  • Variables are local unless you use global, variable or upvar.
  • To change the caller's variable, pass its name and use upvar 1.
  • Namespaces keep procedure names and state apart, and info lets you inspect them.