Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach c [all_clocks] {
    puts [format "  %-10s period %.2f" $c [get_clock_period $c]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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.00

Four 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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>"}]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 2. what clocks actually ARRIVE at each block ===
  u_core   core_clk
  u_mem    mem_clk
  u_io     io_clk

Each 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, CP and G all 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 {, }]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== 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_clk

5. 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.