Tcl for EDA · lesson 8

Arrays and dictionaries

Keyed storage in Tcl: arrays for quick lookups and dictionaries for values you pass around, with counting, grouping and nested records.

8 min read · Free, no login

A list is good when you have a sequence. A flow script also needs to answer "what is the value for this name": the slack of each view, the number of warnings for each message id, the settings for each stage. Tcl has two keyed containers for this. The array is a variable with named elements. The dictionary is a value that holds key and value pairs, so it can be passed to a procedure and returned from one.

Reading time: about 8 minutes.

8.1 Arrays

An array element is written name(key). There is no declaration.

set slack(setup) -0.12
set slack(hold) 0.04
puts $slack(setup)
puts [array exists slack]
puts [lsort [array names slack]]
puts [array size slack]

This prints:

-0.12
1
hold setup
2

array names returns the keys, in no guaranteed order, so sort them when the order matters. array get returns a flat list of keys and values, and array set loads one. parray prints a whole array in a readable form.

array set limits {max_fanout 16 max_tran 0.30}
parray limits

This prints:

limits(max_fanout) = 16
limits(max_tran)   = 0.30

An array is not a value. You cannot write puts $slack or pass $slack to a procedure. To give a procedure access, pass the array name and use upvar, as in Lesson 7, or use array get. This is the main reason many scripts prefer dictionaries.

8.2 Dictionaries

A dictionary is a list of keys and values with a set of commands that treat it as a map.

set d [dict create effort high period 2.0]
dict set d margin 0.1
puts [dict get $d period]
puts [dict exists $d margin]
puts [dict keys $d]
dict unset d margin
puts [dict size $d]

The result is 2.0, then 1, then effort period margin, then 2. A dictionary keeps its keys in the order they were added, which makes printed output predictable. Reading a key that does not exist is an error, so test with dict exists first when the key is optional.

if {[dict exists $d margin]} { puts "has margin" } else { puts "no margin" }

This prints no margin, because the key was removed. Tcl 8.7 adds dict getdef to read a key with a default, but Tcl 8.6, which most tools ship, does not have it. Use dict exists as shown.

Walking a dictionary uses dict for.

set views [dict create view1 -0.12 view2 0.30 view3 0.02]
dict for {name sl} $views {
    puts [format "%-6s %6.2f" $name $sl]
}
puts [dict keys $views]
dict unset views view2
puts [dict keys $views]

This prints:

view1  -0.12
view2   0.30
view3   0.02
view1 view2 view3
view1 view3

8.3 Counting with dict incr

The commonest use is a tally. dict incr creates the key at zero if it is missing.

set counts {}
foreach id {ENCTS-423 IMPOPT-1 ENCTS-423 IMPOPT-1 ENCTS-423} {
    dict incr counts $id
}
puts $counts

This prints ENCTS-423 3 IMPOPT-1 2. Lesson 9 uses this to count the messages in a log. The ids here are placeholders for the pattern. A second command, dict lappend, collects values per key.

set groups {}
dict lappend groups warn w1
dict lappend groups err e1
dict lappend groups warn w2
puts $groups

This prints warn {w1 w2} err e1.

8.4 Merging and updating

set base {effort high period 2.0}
set over {period 2.5}
puts [dict merge $base $over]
dict update base period p effort e {
    set p 2.5
}
puts [dict get $base period]

dict merge gives a new dictionary in which later values replace earlier ones, which is how a default settings dictionary is combined with a user override. dict update links dictionary keys to local variables for the length of a body, and writes the changes back. This prints effort high period 2.5 and then 2.5.

8.5 Sorting by value

A dictionary has no sort command. Convert it to a list of pairs, sort, and print.

set area [dict create u_a 12 u_b 3 u_c 40]
set pairs {}
dict for {k v} $area { lappend pairs [list $k $v] }
foreach p [lsort -integer -decreasing -index 1 $pairs] {
    lassign $p k v
    puts [format "%-4s %3d" $k $v]
}

This prints:

u_c   40
u_a   12
u_b    3

The pattern, collect pairs and lsort -index 1, is reused in every "top N" report.

8.6 Nested dictionaries

A value can itself be a dictionary, which gives a small tree. Use the multi-key forms to reach inside.

set t {}
dict set t func max setup -0.12
dict set t func min hold 0.04
puts $t
puts [dict get $t func max setup]

This prints:

func {max {setup -0.12} min {hold 0.04}}
-0.12

The keys are func, max, setup, and the value is -0.12. A structure of this shape is a natural fit for results by view, by corner and by check type.

8.7 Transforming and filtering

Tcl 8.6 has dict map and dict filter.

puts [dict map {k v} {a 1 b 2 c 3} {expr {$v*10}}]
puts [dict filter {a 1 b 2 c 3} script {k v} {expr {$v>1}}]

This prints a 10 b 20 c 30 and then b 2 c 3. The first scales every value. The second keeps only the pairs whose value is greater than one.

8.8 Array or dictionary?

Use a dictionary when you will pass the data to a procedure, return it, or print it. Use an array when many procedures update the same table in place, or when you want the per-element trace and parray. In a new flow library, dictionaries are the usual choice because they behave like any other value.

8.9 A small Innovus example

Let us consider a per-view timing table. The Legacy reference lists timeDesign with -postRoute, -expandedViews and -outDir, and it writes one report per view. To be run at the Innovus prompt. Check the options against your release with timeDesign -help.

Legacy UI.

timeDesign -postRoute -expandedViews -outDir sample

The step produces one report for each analysis view. A script then reads the worst slack from each report and keeps them in one dictionary, keyed by view name:

set wns [dict create]
dict set wns func_ss_setup -0.12
dict set wns func_ff_hold 0.04
dict for {view value} $wns {
    puts [format "%-14s %7.3f %s" $view $value [expr {$value < 0 ? "VIOLATED" : "met"}]]
}

The two numbers here are made up to show the table shape. In a real script they come from parsing the reports, which Lesson 12 shows in a pure-Tcl script. The dictionary keeps the view name and the number together, so a later step can sort the views, count the failing ones or write the table to a file.

8.10 Why an interviewer asks about this

Typical questions: "what is the difference between an array and a dictionary", "how do you count how many times each message appears" and "how do you pass an array to a procedure". The strong answers say that a dictionary is a value and an array is a variable, show dict incr for the count, and explain that an array is passed by name with upvar or converted with array get. A follow-up may ask what happens when a key is missing, and the answer is an error, so use dict exists.

Key takeaways

  • An array is a variable with named elements, and array names gives the keys in no fixed order.
  • A dictionary is a value, so it can be passed, returned and printed, and it keeps keys in insertion order.
  • dict incr and dict lappend build tallies and groups in one line each.
  • Sort a dictionary by converting it to a list of pairs, and use dict exists before reading an optional key.