Tcl · Module 19
Listing the Clocks That Reach a Block
Two lists matter: the clocks somebody constrained, and the clocks that actually arrive somewhere. When a block times wrongly, the difference between them is the first thing to look at.
Intermediate16 min readtcledapart-iiclocksintegrationqueriesdebugging
Part II · Module 19 · Chapter 19.2 · Listing the Clocks That Reach a Block
A block times wrongly, or does not time at all. Before looking at a single path, there is one question to answer: which clocks actually reach it?
It is the first question in any integration debug because the answer is so often "not the one you think", and because everything downstream is meaningless until it is settled.
1. Two different lists
The distinction the whole chapter rests on:
- Defined clocks — what somebody constrained in this session. A property of the constraints.
- Arriving clocks — what actually propagates to a given block's clock pins. A property of the design.
They are supposed to agree. When they do not, you have found something.
2. What is defined
foreach c [all_clocks] {
puts [format " %-10s period %.2f" $c [get_clock_period $c]]
}=== 1. what clocks are DEFINED in this session ===
core_clk period 2.00
mem_clk period 4.00
io_clk period 10.00
spare_clk period 8.00Four clocks with periods. This list comes from the constraints that have been read — so it reflects what was asked for, and it would look exactly the same on a design where none of them connected to anything.
3. What actually arrives
The other list is a property of the design, and you get it by looking at the clock pins:
proc clocks_in {blk} {
set seen {}
foreach_in_collection p [get_pins -hierarchical "$blk/*"] {
if {[get_attribute $p is_clock] ne "true"} { continue }
set c [get_attribute $p clock]
if {$c ne "" && [lsearch -exact $seen $c] < 0} { lappend seen $c }
}
return [lsort $seen]
}
foreach_in_collection blk [get_cells *] {
set cl [clocks_in $blk]
puts [format " %-8s %s" $blk [expr {[llength $cl] ? $cl : "<none>"}]]
}=== 2. what clocks actually ARRIVE at each block ===
u_core core_clk
u_mem mem_clk
u_io io_clkEach block, and the clocks reaching it. Three things about that procedure are worth naming:
- It selects clock pins by attribute, not by name — Chapter 18.4 §6's rule, because
CLK,CK,CPandGall exist. - It de-duplicates with
lsearch -exact, since many pins share one clock. A block with four hundred flops on one clock should report one clock, not four hundred. - It returns a sorted list, so two runs are comparable — the same argument as Chapter 14.9 §3's sorted
parray.
4. The gap
Now put the two lists together, which is the actual diagnostic:
set used {}
foreach_in_collection p [get_pins -hierarchical *] {
if {[get_attribute $p is_clock] eq "true"} {
set c [get_attribute $p clock]
if {$c ne "" && [lsearch -exact $used $c] < 0} { lappend used $c }
}
}
puts " defined : [all_clocks]"
puts " arriving: [lsort $used]"
set unused {}
foreach c [all_clocks] { if {[lsearch -exact $used $c] < 0} { lappend unused $c } }
if {[llength $unused]} {
puts " DEFINED BUT UNUSED: [join $unused {, }]"
}=== 3. the gap: defined but never arriving anywhere ===
defined : core_clk mem_clk io_clk spare_clk
arriving: core_clk io_clk mem_clk
DEFINED BUT UNUSED: spare_clk5. The other direction, which is worse
The gap runs both ways, and the reverse case is more dangerous:
A clock pin that no defined clock reaches. Those flops are unconstrained. Every path through them is unconstrained. An unconstrained path has no requirement, so it cannot violate one, so the timing report says nothing about it at all.
That is Chapter 18.8's silent failure at clock level: not a wrong number but a missing one, in a report that looks complete.
So the check worth automating is the pair:
proc check_clocks {} {
set defined [all_clocks]
set arriving {}
set unclocked {}
foreach_in_collection p [get_pins -hierarchical *] {
if {[get_attribute $p is_clock] ne "true"} { continue }
set c [get_attribute $p clock]
if {$c eq ""} {
lappend unclocked $p
} elseif {[lsearch -exact $arriving $c] < 0} {
lappend arriving $c
}
}
set unused {}
foreach c $defined { if {[lsearch -exact $arriving $c] < 0} { lappend unused $c } }
return [dict create defined $defined arriving [lsort $arriving] \
unused $unused unclocked $unclocked]
}A dict of four lists. Print it at the start of every timing run and both failure modes become visible in the log rather than being inferred later — the make-it-visible argument from Chapter 17.4 §5.
6. Why this is the first question
When a block times wrongly, the possibilities are roughly: the constraints are wrong, the design is wrong, or the analysis is wrong. Checking which clocks arrive separates them in one step.
- No clock arrives → a constraint or connectivity problem. Nothing further is worth looking at.
- The wrong clock arrives → an integration problem. The block is connected to something you did not expect, which explains every strange number at once.
- The right clocks arrive → now the paths are worth reading.
Running it first saves the hours people spend reading timing reports for a block that was never constrained.
7. Making it a report
The version to keep, per block, with the periods included:
foreach_in_collection blk [get_cells *] {
set cl [clocks_in $blk]
if {[llength $cl] == 0} {
puts [format " %-8s <none>" $blk]
continue
}
set parts {}
foreach c $cl { lappend parts [format "%s (%.2f)" $c [get_clock_period $c]] }
puts [format " %-8s %s" $blk [join $parts {, }]]
}Adding the period is worth the extra line: a block on a 10 ns clock and a block on a 2 ns clock have very different expectations, and seeing both in one table is how you notice a block on the clock you did not intend.
8. Exercises
Exercise 1. What are the two lists this chapter compares, and what is each a property of?
Exercise 2. Why select clock pins by attribute rather than by a */CLK pattern?
Exercise 3. Why does clocks_in de-duplicate?
Exercise 4. A clock is defined and arrives nowhere. Give two likely causes.
Exercise 5. Why does that not show up as a timing violation?
Exercise 6. What is the reverse case, and why is it worse?
Exercise 7. A block reports two arriving clocks. Is that a bug?
Where this fits
Part of the Tcl curriculum.
