Skip to content
VLSI Mentor

Tcl · Module 22

A First Pass Across Corners

A design has to work across process, voltage and temperature. Iterating the analysis is a foreach — and the thing to resist is summarising the results with the single worst number.

Advanced16 min readtcledapart-iistacornersloopssignoff

Part II · Module 22 · Chapter 22.8 · A First Pass Across Corners

A chip has to work across a range of manufacturing outcomes, supply voltages and temperatures. Timing at one corner says nothing about the others, so a signoff run covers several.

The iteration is an ordinary foreach. The interesting part is what you do with the results.

1. One procedure, one corner

Everything from 22.1 through 22.7, with the corner as a parameter:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc analyse_corner {corner} {
    global ROOT
    set ::sta::netlist "" ; set ::sta::libs {} ; set ::sta::sdc_applied 0 ; set ::sta::updated 0
    read_netlist    $ROOT/synth_run/netlist/cpu_top_mapped.v
    read_libs       [list $ROOT/lib/$corner.lib]
    read_parasitics $ROOT/spef/cpu_top_$corner.spef
    read_sdc        $ROOT/sdc/constraints.sdc
    set ::sta::corner $corner
    update_timing
    return [dict create corner $corner wns [worst_slack] \
                        violations [llength [report_all_violators]] \
                        viol_list [report_all_violators]]
}

Three Part I decisions:

  • The per-corner paths come from the corner name, which is what Chapter 22.1 §2's lib_for helper was for. Adding a corner is one list entry.
  • The session is reset first. Leftover state from the previous corner is exactly the Chapter 17.1 §6 long-session hazard, and in a loop it is easy to create.
  • It returns a dict, not printed text, so the caller decides what to do with the results.

2. The loop

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set results {}
foreach c {slow typ fast} {
    set r [analyse_corner $c]
    lappend results $r
    puts [format "    %-5s  wns %7s  violations %d" \
        [dict get $r corner] [dict get $r wns] [dict get $r violations]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== the same analysis over three corners ===
    slow   wns  -0.168  violations 1
    typ    wns   0.090  violations 0
    fast   wns  -0.045  violations 2

A foreach, a dict, a lappend. Nothing in the iteration is EDA — Module 9 would recognise all of it.

3. Now do not summarise it

Here is the registry purpose's point, and it is visible in the numbers above.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== the worst number across all corners ===
    worst wns anywhere: -0.168 (at the slow corner)

That is the figure somebody will put on a slide. Look at what it hides:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
=== but which endpoints violate, per corner? ===
    slow   u_io/u_sync/D
    typ    <none>
    fast   u_core/u_ctrl/HOLD, u_mem/u_bank1/HOLD

4. What a multi-corner summary must contain

Per corner, not collapsed:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach r $results {
    puts [format "  %-5s  wns %7s  violations %3d  %s" \
        [dict get $r corner] [dict get $r wns] [dict get $r violations] \
        [expr {[dict get $r violations] ? "NOT CLOSED" : "closed"}]]
}

Four things it should carry:

  • Which corners were run. A corner not analysed is not a corner that passed — Chapter 22.9 checks for coverage.
  • Worst slack per corner, because they mean different things.
  • Violation count per corner, which is Chapter 22.7 §2's argument applied per corner.
  • Which endpoints, at least for the failing ones, so overlap is visible.

The last is what tells you whether you have one problem or several.

5. The endpoint overlap

The question that decides the work:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc endpoints_of {r} {
    set out {}
    foreach p [dict get $r viol_list] { lappend out [lindex $p 0] }
    return $out
}

set all {}
foreach r $results { lappend all {*}[endpoints_of $r] }
set unique [lsort -unique $all]
puts "  [llength $unique] distinct failing endpoint(s) across all corners"
foreach ep $unique {
    set where {}
    foreach r $results {
        if {[lsearch -exact [endpoints_of $r] $ep] >= 0} { lappend where [dict get $r corner] }
    }
    puts [format "    %-22s fails at: %s" $ep [join $where {, }]]
}

An endpoint failing at every corner is a path that is simply too slow — one problem, one fix. An endpoint failing at one corner is corner-specific, and the fix usually differs: a setup failure at slow is a speed problem, a hold failure at fast is a delay-insertion problem.

Collapsing to a single worst number destroys exactly this information.

6. The cost, and keeping it honest

Each corner is a full timing update (Chapter 22.5 §6) — the expensive step, repeated. Three consequences:

Do the cheap checks once, outside the loop. The netlist freshness check from Chapter 22.2 §4 does not depend on the corner. Running it before the loop means a stale netlist costs seconds, not three full analyses.

Resist trimming the corner list. Dropping the fast corner because it "usually passes" would, in the data above, have hidden two violations entirely. A corner skipped for time is a corner not signed off, and the summary must say which were run.

Make each corner's artefacts separate. One report directory per corner, named for it, so the per-corner results stay distinguishable — the run-directory discipline from Chapter 21.8 §6.

7. Exercises

Exercise 1. Why does timing at one corner say nothing about the others?

Exercise 2. Why does analyse_corner reset the session state first?

Exercise 3. Why return a dict rather than printing?

Exercise 4. The worst slack is at the slow corner. What does the fast corner's result add?

Exercise 5. Why do setup and hold violations cluster at different corners?

Exercise 6. What does an endpoint failing at every corner mean, versus at only one?

Exercise 7. Why do the freshness checks belong outside the corner loop?

Where this fits

Part of the Tcl curriculum.