Tcl · Module 18
Collections vs Tcl Lists
This is the mistake every engineer makes once. The Tcl list commands do not fail on a collection — they succeed, quietly, with the wrong answer.
Intermediate17 min readtcledapart-iicollectionslistsdebugging
Part II · Module 18 · Chapter 18.2 · Collections vs Tcl Lists
You know Tcl lists well by now — Module 6 was fourteen chapters of them. A collection looks like something you could use them on. It is not.
The trouble is that the list commands do not object. They return answers. The answers are wrong, and nothing tells you.
1. The two things side by side
set cells [get_cells -hierarchical *]
puts " value : $cells"
puts " sizeof_collection : [sizeof_collection $cells]"
puts " contents (for us) : [collection_to_list $cells]" value : _sel1
sizeof_collection : 7
contents (for us) : u_fifo u_alu u_fifo/u_ctrl u_fifo/u_ram u_fifo/u_decode u_alu/u_add u_alu/u_acc_regSeven cells. The value held in $cells is the single word _sel1.
That is the whole explanation for everything in this chapter: to Tcl, a collection is one ordinary string. Tcl has no idea it means anything.
2. What the list commands actually do
puts " llength \$cells : [llength $cells]"
puts " lindex \$cells 0 : [lindex $cells 0]"
puts " lindex \$cells 1 : '[lindex $cells 1]'"
set n 0
foreach c $cells { incr n ; puts " iteration $n: c = '$c'" }
puts " -> $n iteration(s), for a collection of [sizeof_collection $cells]" llength $cells : 1
lindex $cells 0 : _sel1
lindex $cells 1 : ''
foreach c $cells {...} runs:
iteration 1: c = '_sel1'
-> 1 iteration(s), for a collection of 73. One iteration, or zero?
The registry describes this as "why a loop runs zero times". You will see both, and knowing which is which saves an hour.
- A non-empty collection is a handle — one word — so
foreachruns once, with the handle as the value. - An empty collection is the empty string, so
foreachruns zero times.
So a loop that ran zero times tells you the query matched nothing (Chapter 18.8). A loop that ran exactly once, whatever the design size, tells you that you used foreach on a collection.
"Exactly one iteration" is the signature. If a loop over cells reports processing one thing on a design with four thousand, you have this bug and not a design problem.
4. The commands that do work
set n 0
foreach_in_collection c $cells { incr n ; puts " iteration $n: c = $c" }
puts " -> $n iteration(s)" iteration 1: c = u_fifo
iteration 2: c = u_alu
iteration 3: c = u_fifo/u_ctrl
iteration 4: c = u_fifo/u_ram
iteration 5: c = u_fifo/u_decode
iteration 6: c = u_alu/u_add
iteration 7: c = u_alu/u_acc_reg
-> 7 iteration(s)Seven iterations, each binding a real object. foreach_in_collection knows what the handle refers to; foreach cannot.
The substitutions to learn:
| Instead of | Use |
|---|---|
llength $c | sizeof_collection $c |
foreach x $c {...} | foreach_in_collection x $c {...} |
lindex $c 0 | index_collection $c 0 |
concat $a $b | add_to_collection $a $b |
| a filtering loop | filter_collection $c "expr" |
llength $c == 0 | sizeof_collection $c == 0 |
5. Why not make collections be lists?
It is a fair question, and the answer is Chapter 18.1's three reasons: a list of names would have to be materialised as a string, could not distinguish same-named objects, and would carry no attributes.
The cost of those three benefits is exactly this chapter. It is a real cost, and the tools pay it because the alternative is worse on a design with millions of objects.
6. Catching it
Three habits, in increasing order of how much they save you.
Suspect "one". Any count that is 1 when it should be large is this bug. Print sizeof_collection next to llength while developing and the difference is immediate.
Name the variable for what it is. set cell_coll [get_cells ...] rather than set cells [...]. A suffix on the name makes foreach c $cell_coll look wrong on the page, which is the cheapest possible check.
Convert deliberately at the boundary. If you want Tcl-list behaviour, ask for it explicitly, and then everything from Part I genuinely does apply:
set names [collection_to_list [get_cells -hierarchical *reg*]]
puts " [llength $names] cells" ;# now llength is correct
foreach n [lsort $names] { ... } ;# now foreach is correctThat conversion is also the Chapter 16.4 §6 boundary: past this line the data is plain Tcl and the logic is testable without a tool.
7. A worked diagnosis
A script reports that it constrained one cell on a design with thousands. The loop:
set regs [get_cells -hierarchical *_reg*]
set n 0
foreach r $regs {
incr n
# ... do something with $r
}
puts "constrained $n cells"Diagnosis, in order:
- The count is 1. Suspect the collection bug before anything about the design.
- Check the size properly:
puts [sizeof_collection $regs]. If it is large and the loop ran once, it is confirmed. - Fix the loop, not the query:
foreach_in_collection r $regs {
incr n
# ... do something with $r
}If instead sizeof_collection reports 0, the loop was right and the query matched nothing — a different bug, and Chapter 18.8's.
Those two cases produce almost the same symptom and have opposite fixes, which is why checking the size first is the step that matters.
8. Exercises
Exercise 1. Why does llength on a collection return 1?
Exercise 2. What does foreach bind the loop variable to, and how many times does it run?
Exercise 3. You see "processed 1 cell" on a large design. What do you suspect, and what do you check first?
Exercise 4. A loop over a collection runs zero times. Is that the same bug?
Exercise 5. Give the collection equivalents of llength, foreach, lindex and concat.
Exercise 6. Why not convert every collection to a list immediately?
Exercise 7. Name a cheap habit that makes this mistake visible while reading the code.
Where this fits
Part of the Tcl curriculum.
