Cell names, net names, file names and path records are all lists. A script that works well with lists can select, count, sort and loop over almost anything a tool returns. This lesson covers the list commands you will use daily, the nested lists that hold records, and the mistakes that come from treating a list as plain text.
Reading time: about 8 minutes.
4.1 What a list is
A Tcl list is a string with a particular structure: elements separated by spaces, with braces or quotes around an element that itself contains spaces. Every command that works on lists understands this structure.
set cells {u1 u2 u10 ff_a ff_b}
puts [llength $cells]
puts [lindex $cells 0]
puts [lindex $cells end]
puts [lrange $cells 1 2]
This prints:
5
u1
ff_b
u2 u10
Indexes start at zero, and end means the last element. end-1 is the one before it. lrange returns a new list, and it never fails for indexes outside the list: it returns what exists.
4.2 Building and changing lists
lappend cells ff_c
puts $cells
set cells [linsert $cells 1 u_new]
puts $cells
set cells [lreplace $cells 1 1]
puts $cells
This prints:
u1 u2 u10 ff_a ff_b ff_c
u1 u_new u2 u10 ff_a ff_b ff_c
u1 u2 u10 ff_a ff_b ff_c
lappend changes the variable in place. linsert and lreplace return a new list, so assign the result. The list command builds a list from arguments, and it quotes elements for you.
set l [list a "b c" d]
puts [llength $l]
puts $l
set s "a b c d"
puts [llength $s]
This prints:
3
a {b c} d
4
The element b c stayed one element, and Tcl showed it with braces. Always create a list with list or lappend, not by gluing text together, because the commands then handle spaces correctly.
4.3 Searching and sorting
puts [lsearch $cells ff_a]
puts [lsearch -all -glob $cells ff_*]
puts [lsearch -inline -glob $cells u*]
puts [lsort $cells]
puts [lsort -dictionary $cells]
puts [lsort -decreasing {3 20 100 7}]
puts [lsort -integer -decreasing {3 20 100 7}]
This prints:
3
3 4 5
u1
ff_a ff_b ff_c u1 u10 u2
ff_a ff_b ff_c u1 u2 u10
7 3 20 100
100 20 7 3
lsearch returns the index of the first match, or -1. With -all it returns every index, and with -inline it returns the matching element itself. The default match is a glob pattern, so ff_* works as in a shell. Pass -exact if you need an exact comparison.
Sorting needs care. The default sort is alphabetical, so u10 comes before u2. Use -dictionary to sort embedded numbers as numbers, and use -integer or -real for lists of numbers. The line lsort -decreasing {3 20 100 7} returned 7 3 20 100, because as text 7 is the largest. The -integer option fixes it.
4.4 Looping
foreach c {u1 ff_a} { puts "cell $c" }
foreach {name type} {u1 AND2 ff_a DFF} { puts "$name is $type" }
This prints:
cell u1
cell ff_a
u1 is AND2
ff_a is DFF
foreach can take several variables at once, which reads a flat list in pairs, triples and so on. This is how a list of alternating names and values is processed.
4.5 Taking lists apart
lassign copies elements into variables. Extra elements are returned.
lassign {ff_a ff_b -0.12} start end slack
puts "$start -> $end : $slack"
set rest [lassign {1 2 3 4} p q]
puts "$p $q rest=$rest"
This prints:
ff_a -> ff_b : -0.12
1 2 rest=3 4
The expansion operator {*} turns a list into separate arguments. It is the clean way to pass a stored list of options to a command.
set args {-from ff_a -to ff_b}
puts [list report_timing {*}$args]
This prints:
report_timing -from ff_a -to ff_b
Without {*}, the list would be one argument. Section 11.4 returns to this.
4.6 Transforming a list with lmap
lmap is like foreach but collects a result for each element.
puts [lmap c {u1 u2 u3} {string toupper $c}]
puts [lmap n {1 2 3 4 5 6} {expr {$n % 2 ? [continue] : $n * 10}}]
This prints:
U1 U2 U3
20 40 60
The second line uses continue inside the body to skip the odd numbers, so only the even ones are transformed. lmap arrived in Tcl 8.6, so check the version of your tool's Tcl before using it in a flow script.
4.7 Splitting and joining text
split turns text into a list at the characters you give. join does the reverse.
puts [split "top/core/u_alu/add1" /]
puts [join {top core u_alu} /]
puts [split "a,b,,c" ,]
This prints:
top core u_alu add1
top/core/u_alu
a b {} c
This is how a hierarchical instance name is cut into levels. Notice that two adjacent separators give an empty element, shown as {}.
4.8 Nested lists as records
A list of lists is a table. Each inner list is one record.
set paths {{ff_a ff_b -0.12} {ff_c ff_d 0.30} {ff_e ff_f -0.45}}
puts [lsort -real -index 2 $paths]
foreach p [lsort -real -index 2 $paths] {
lassign $p s e sl
puts [format "%-6s %-6s %7.2f" $s $e $sl]
}
puts [lindex $paths 1 2]
This prints:
{ff_e ff_f -0.45} {ff_a ff_b -0.12} {ff_c ff_d 0.30}
ff_e ff_f -0.45
ff_a ff_b -0.12
ff_c ff_d 0.30
0.30
lsort -index 2 sorts the records by their third field, and -real compares as numbers, so the worst slack comes first. lindex $paths 1 2 reaches into a nested list: record 1, field 2. A flow script that holds one record per path in a list of lists can sort, filter and print timing results with the commands above.
4.9 Pitfalls
set bad "a {b"
catch {llength $bad} err
puts $err
puts [lrange {a b c d} 1 end-1]
puts [llength [lrange {a b c d} 5 9]]
puts [lindex {a b c} 7]
puts [llength [lindex {a b c} 7]]
This prints:
unmatched open brace in list
b c
0
0
Three lessons are in that output. A string with an unbalanced brace is not a valid list. lrange past the end quietly returns an empty list. And lindex with an index that does not exist returns an empty string rather than an error, so a loop that runs one step too far does not stop on its own. Be careful with <= against llength in a for loop.
4.10 A small Innovus example
A tool command that returns many names gives you a list to work with. In the Legacy command reference, dbGet walks the database by a dotted path and returns a list or a string. Its example dbGet top.insts.cell.name returns the master name of every instance. To be run at the Innovus prompt. Check the details against your release with dbGet -help.
Legacy UI.
set masters [dbGet top.insts.cell.name]
puts "instances: [llength $masters]"
set kinds [lsort -unique $masters]
puts "different masters: [llength $kinds]"
The reference notes that dbGet returns a list or a string, so every command in this lesson applies to the result. The -u option of dbGet returns unique results directly, which avoids sorting in Tcl.
4.11 Why an interviewer asks about this
Expect "how do you loop over a list of cells", "what is the difference between lappend and linsert" and "why did lsort put u10 before u2". Strong answers use foreach, mention that lappend changes in place while linsert returns a copy, and name -dictionary, -integer and -real. A practical question may ask you to sort path records by slack, which is the lsort -real -index pattern above.
Key takeaways
- A list is a string with a structure, so build lists with
listandlappend, and loop withforeach. lsearchfinds,lsortorders (choose-dictionary,-integeror-realfor the data), andlassignand{*}take lists apart.- Nested lists hold records, and
lsort -indexsorts them by a field. - Out-of-range
lrangeandlindexreturn empty values without an error, so check the bounds yourself.