Tcl · Module 26
The Driver Loop
Walking the scenario list is a nested foreach. Making one scenario's failure harmless to the rest is the part that takes thought — and the obvious fix produces a confident wrong number.
Advanced22 min readtcledapart-iimcmmautomationerror-handling
Part II · Module 26 · Chapter 26.5 · The Driver Loop
Chapter 26.4 produced a list of scenarios. Walking it is three lines of Part I:
foreach s $scenarios {
apply_mode [dict get $s mode]
apply_corner [dict get $s corner]
report_timing [dict get $s check]
}That loop is correct for a plan with no mistakes in it, and plans have mistakes in them. The rest of this chapter is about what the loop does when one scenario fails, which is the only interesting question it raises.
1. The plan with one typo
set PLAN {
{functional ss_0p72v_125c}
{functional tt_0p80v_25C}
{functional ff_0p88v_m40c}
}The middle entry ends in 25C rather than 25c. That is not a contrived defect — it is what a hand-maintained list acquires, and Chapter 26.3 argued that corner names are identifiers rather than data precisely so that a typo in one is a lookup failure rather than a silently different corner.
So the lookup fails. The question is what the loop does about it.
2. Shape A: no isolation at all
foreach s $PLAN {
apply_mode [lindex $s 0]
apply_corner [lindex $s 1]
puts [format "%s %s worst setup %.4f" ... [worst_slack setup]]
}=== the same plan, but the failure is not caught at all ===
functional ss_0p72v_125c worst setup -0.4800
loop aborted: 1 / no such corner: tt_0p80v_25C
scenarios completed: 1 of 3One of three. The error propagates out of the foreach, the loop stops, and the two remaining scenarios — including ff_0p88v_m40c, which is a required hold-sign-off corner — never run.
This is bad, and it is the good failure mode. It is loud, it names the cause, it reports 1 of 3, and nobody can mistake the run for complete. In a nightly flow it costs a night. It does not cost correctness.
3. Shape B: catch, and carry on
The obvious fix is to catch the error so the loop survives:
foreach s $PLAN {
set mode [lindex $s 0] ; set corner [lindex $s 1]
catch { apply_mode $mode ; apply_corner $corner }
puts [format "%-13s %-14s %9.4f" $mode $corner [worst_slack setup]]
}=== catching the error but reporting anyway ===
MODE CORNER SLACK NOTE
functional ss_0p72v_125c -0.4800 session actually holds: functional ss_0p72v_125c
functional tt_0p80v_25C -0.4800 session actually holds: functional ss_0p72v_125c
functional ff_0p88v_m40c 1.8980 session actually holds: functional ff_0p88v_m40cThis is the same shape as every central finding in Part II — Chapter 24.1 §7's truncated slack, Chapter 25.4 §2's failing test exiting 0, Chapter 25.5 §3's PASSED row pointing at a FAILED log. A correct mechanism, applied to state it did not establish, producing a confident number.
Note also what made the lie visible here: the loop printed session_state beside each row. A driver that reports what the session actually holds, rather than what the plan said it should hold, turns this defect from invisible into obvious — which is the cheapest possible instrumentation.
4. Shape C: reset first, then apply
foreach s $PLAN {
set mode [lindex $s 0] ; set corner [lindex $s 1]
reset_session
if {[catch { apply_mode $mode ; apply_corner $corner } e]} {
puts [format " %-13s %-14s %9s SKIPPED: %s" $mode $corner "-" $e]
continue
}
puts [format " %-13s %-14s %9.4f" $mode $corner [worst_slack setup]]
}=== with reset_session first, the failure cannot produce a number ===
MODE CORNER SLACK NOTE
functional ss_0p72v_125c -0.4800
functional tt_0p80v_25C - SKIPPED: no such corner: tt_0p80v_25C
functional ff_0p88v_m40c 1.8980Two scenarios ran, one was skipped by name with its reason, and nothing produced a number it was not entitled to.
5. The production loop
proc run_scenarios {scenarios} {
set results {} ; set failures 0
foreach s $scenarios {
set mode [dict get $s mode]
set corner [dict get $s corner]
set check [dict get $s check]
reset_session
if {[catch { apply_mode $mode ; apply_corner $corner } e]} {
incr failures
lappend results [dict merge $s [dict create status SETUP-FAILED reason $e worst ""]]
puts stderr " $mode / $corner / $check — setup failed: $e"
continue
}
if {[catch { worst_slack $check } w]} {
incr failures
lappend results [dict merge $s [dict create status ANALYSIS-FAILED reason $w worst ""]]
puts stderr " $mode / $corner / $check — analysis failed: $w"
continue
}
lappend results [dict merge $s [dict create status OK reason "" worst $w]]
}
return [list $results $failures]
}Five decisions, each one a lesson from an earlier module.
reset_session first, per §4.
Setup failure and analysis failure are separate statuses. SETUP-FAILED means the scenario could not be established — a typo, a missing library, a mode that does not exist. ANALYSIS-FAILED means it was established and the analysis broke. Those go to different people, which is Chapter 25.4 §7's argument about distinct exit statuses applied to a results table.
Every scenario produces a row, including the failed ones. A results list with the failures missing is a list whose length no longer matches the plan — and Chapter 26.6 depends on being able to compare those two numbers.
dict merge $s ... keeps the scenario's own identity in the result rather than rebuilding it. The row is the scenario plus what happened to it, so nothing downstream has to re-derive which mode a number came from.
Errors go to stderr, results to the returned list. A log and a data structure are different audiences, per Chapter 15.4.
6. What catch does not isolate
Being honest, because §4 reads like a solution and in a real tool it is a partial one.
The mock's reset_session clears two variables. A real tool session accumulates far more, and the things that leak between scenarios are:
- Constraints.
set_false_pathandset_multicycle_pathfrom the previous mode do not vanish because you sourced a different SDC. Most tools need an explicitreset_designor equivalent, and some exceptions survive even that. - Derates and operating conditions. A
set_timing_derateapplied for one corner stays applied. This one is particularly nasty because it changes numbers without changing anything a report prints about the configuration. - The timing graph. Even with constraints replaced, the previously computed graph may be reused unless invalidated — Chapter 22.5's entire subject, arriving as a cross-scenario hazard rather than a within-scenario one.
- The design database itself. If a scenario modified the netlist — an ECO, a sizing experiment — nothing in a constraint reset undoes that.
7. Exercises
Exercise 1. Why is a typo'd corner name a lookup failure rather than a different corner?
Exercise 2. Shape A aborts after 1 of 3. Why is that the good failure mode?
Exercise 3. Shape B reported −0.4800 for the typical corner. Trace exactly how.
Exercise 4. Why is shape B worse than shape A?
Exercise 5. Why is the mislabelled row particularly unlikely to be questioned?
Exercise 6. What made the lie visible in the §3 output?
Exercise 7. Why does the reset belong at the top of the iteration rather than after the failure?
Exercise 8. Why continue rather than letting a skipped scenario fall through?
Exercise 9. Why are SETUP-FAILED and ANALYSIS-FAILED separate statuses?
Exercise 10. Why does every scenario produce a row, including the failures?
Exercise 11. Name three things that leak between scenarios in one tool session.
Exercise 12. What is the only complete isolation, what does it cost, and what is the usual compromise?
Where this fits
Part of the Tcl curriculum.
