Tcl · Module 26
Scenarios: Mode × Corner
Eighteen combinations, eight actually required. Getting from one to the other needs a reason per drop and a coverage check — and the obvious coverage check reports two failures that are not failures.
Advanced23 min readtcledapart-iimcmmscenariossignoff
Part II · Module 26 · Chapter 26.4 · Scenarios: Mode × Corner
A scenario is one mode paired with one corner, analysed for one check. It is the unit of work in MCMM: one scenario is one analysis, one set of reports, one row in the summary.
Chapter 26.2 and Chapter 26.3 built the two lists. This chapter is about what to do with their product.
1. The cross-product is a default, not a plan
=== the full cross-product ===
3 modes x 3 corners x 2 checks = 18 scenariosRunning the whole product is the easy decision and it is usually the wrong one, for reasons that pull in opposite directions:
Against running everything: analysis time, licence cost, and the fact that eighteen reports nobody reads are worse than eight somebody does. On a real design the product is hundreds, and each one is hours.
Against pruning: every combination you do not run is a combination you are claiming cannot fail. Chapter 26.1 §3 showed 7 of 18 violating — the product is not full of harmless cells.
So the pruning has to be a decision with a reason, and the reason has to be checkable. That is the rest of the chapter.
2. Measure the redundancy before pruning it
The first thing to establish is how much of the product is actually telling you something different. That is measurable:
=== how much of the cross-product is distinct? ===
setup 9 scenarios -> 9 distinct worst-slack value(s)
hold 9 scenarios -> 3 distinct worst-slack value(s)Nine setup scenarios give nine different answers. Nine hold scenarios give three.
=== the hold results, grouped by corner ===
functional ss_0p72v_125c 0.0430
functional tt_0p80v_25c 0.0100
functional ff_0p88v_m40c -0.0208
scan_shift ss_0p72v_125c 0.0430
scan_shift tt_0p80v_25c 0.0100
scan_shift ff_0p88v_m40c -0.0208
scan_capture ss_0p72v_125c 0.0430
scan_capture tt_0p80v_25c 0.0100
scan_capture ff_0p88v_m40c -0.0208The three modes give identical hold numbers at each corner. The hold cross-product is threefold redundant, and the reason is the one Chapter 26.2 §2 noted: hold requirement is a property of the capturing flip-flop, not of the clock period, so changing the mode does not change it.
3. Pruning with a reason per drop
The pruning rules are data, each carrying its justification, matched with string match so wildcards work:
set PRUNE {
{scan_shift * setup {shift runs at 20 ns; setup here is not the limiter}}
{scan_capture * hold {capture is one edge; hold is covered by the functional run}}
{* tt_0p80v_25c * {typical is for tracking, not for sign-off}}
}
proc pruned_reason {mode corner check prune} {
foreach rule $prune {
lassign $rule m c k why
if {[string match $m $mode] && [string match $c $corner] && [string match $k $check]} {
return $why
}
}
return ""
}=== dropped, each with its reason ===
functional tt_0p80v_25c setup typical is for tracking, not for sign-off
functional tt_0p80v_25c hold typical is for tracking, not for sign-off
scan_shift ss_0p72v_125c setup shift runs at 20 ns; setup here is not the limiter
scan_shift tt_0p80v_25c setup shift runs at 20 ns; setup here is not the limiter
scan_shift tt_0p80v_25c hold typical is for tracking, not for sign-off
scan_shift ff_0p88v_m40c setup shift runs at 20 ns; setup here is not the limiter
scan_capture ss_0p72v_125c hold capture is one edge; hold is covered by the functional run
scan_capture tt_0p80v_25c setup typical is for tracking, not for sign-off
scan_capture tt_0p80v_25c hold capture is one edge; hold is covered by the functional run
scan_capture ff_0p88v_m40c hold capture is one edge; hold is covered by the functional run=== required, and therefore run ===
functional ss_0p72v_125c setup
functional ss_0p72v_125c hold
functional ff_0p88v_m40c setup
functional ff_0p88v_m40c hold
scan_shift ss_0p72v_125c hold
scan_shift ff_0p88v_m40c hold
scan_capture ss_0p72v_125c setup
scan_capture ff_0p88v_m40c setup
required 8, dropped 10, total 1818 down to 8. Four decisions in that rule list are worth naming.
required + dropped = total is printed. Every scenario is accounted for as either required or deliberately dropped — there is no third category of "nobody looked". That is Chapter 23.2 §4's declared-list argument: the full product is the denominator, so a combination cannot go missing by being forgotten.
The rules use string match, not regexp. The patterns are globs over names, so * means "any", and Chapter 13.10 prefers the glob where the pattern is a glob. Note the hazard: string match is anchored at both ends by definition (Chapter 5.5), so tt_0p80v_25c matches only that corner — which is what is wanted here, and would not be what you wanted if you had meant "any typical corner".
First match wins. pruned_reason returns on the first matching rule, so rule order decides which reason gets recorded when two apply. scan_shift / tt / hold is dropped by the typical rule rather than the shift rule because the shift rule only covers setup — but scan_capture / tt / hold is dropped by the capture rule, which comes first. Rule order is therefore part of the meaning, and a rule list where order matters should say so.
The typical corner is dropped for sign-off entirely. That is a real and defensible project decision — typical is where you track convergence, and sign-off happens at the extremes — and it is the rule doing most of the work here, removing 4 of the 10.
4. Checking the pruning: the obvious way is wrong
A pruning plan is a claim that nothing important was dropped. That claim is testable, because the mock can run the dropped scenarios too and see what they would have said.
The obvious test:
=== the naive check: did any dropped scenario violate? ===
scan_capture tt_0p80v_25c setup -4.1500
scan_capture ff_0p88v_m40c hold -0.0208
2 dropped scenario(s) violatedTwo dropped scenarios violate. That reads like two pruning mistakes, and on that evidence the plan should be rejected.
5. The criterion that is actually rigorous
The right question is whether the required set reveals the same (check, path) pair:
proc violating_pairs {mode corner check} {
reset_session ; apply_mode $mode ; apply_corner $corner
set out {}
foreach r [report_timing $check] {
if {[dict get $r slack] < 0} { lappend out [list $check [dict get $r path]] }
}
return $out
}=== the (check, path) pairs the REQUIRED set reveals as violating ===
hold short_hold
setup io_to_core
setup scan_chain=== every dropped scenario's violating pairs, checked against that ===
scan_capture tt_0p80v_25c setup scan_chain covered
scan_capture ff_0p88v_m40c hold short_hold covered
pairs a dropped scenario would have revealed and nothing required does: 0Zero, on the rigorous criterion. scan_chain failing setup is revealed by scan_capture at both the slow and fast corners, which are required. short_hold failing hold is revealed by functional at the fast corner, which is required. Both dropped violations are genuinely covered, by name.
6. What even the rigorous check cannot tell you
Being honest about the limit, because §5 looks conclusive.
It validates the pruning against the design as it is now. A design change can make a pruned scenario the only one that reveals a new violation — a retimed path, a new clock, a different capture flop — and the check's previous "0 missed" says nothing about that. So the coverage check belongs in the flow, re-run when the required set or the design changes, not performed once and recorded as settled.
It can only see what the mock can compute. The real version of this check runs every dropped scenario, which is precisely the cost the pruning was meant to avoid. The practical resolution is to run the full product rarely — at a milestone, or when the mode or corner lists change — and the pruned set nightly. The full run then exists to validate the pruning, not to sign off.
It cannot justify a scenario nobody has thought of. The product is modes × corners × checks, and a mode that was never declared is not in the denominator. Chapter 26.2 §5's -owner and -reason fields exist for the opposite problem — a mode nobody can justify — but an absent mode is invisible to every check in this chapter.
7. Scenarios as data, for the driver
The output of all this is a list the driver walks:
proc required_scenarios {modes corners checks prune} {
set out {}
foreach mode $modes {
foreach corner $corners {
foreach check $checks {
if {[pruned_reason $mode $corner $check $prune] eq ""} {
lappend out [dict create mode $mode corner $corner check $check]
}
}
}
}
return $out
}A list of dicts, each one a complete description of one unit of work — which is what Chapter 26.5 iterates and what Chapter 26.6 names directories after.
Two properties of that shape matter downstream. A scenario is self-describing, so nothing later has to re-derive which mode a result came from. And the list is built once, so the driver, the reporter and the aggregator all walk the same thing — which is the defence against Chapter 26.6 §3's aggregation-by-directory-scan.
8. Exercises
Exercise 1. What is a scenario, and why is it the unit of work?
Exercise 2. Give the argument for and the argument against running the full cross-product.
Exercise 3. Nine hold scenarios gave three distinct answers. Why, in this design?
Exercise 4. Name two changes that would break that collapse.
Exercise 5. Why print required + dropped = total?
Exercise 6. Why does rule order matter in the prune list, and give an example from the output.
Exercise 7. The naive coverage check flagged two mistakes. Why is it the wrong question?
Exercise 8. Why is the per-check criterion worse than a wrong answer?
Exercise 9. State the rigorous criterion, and what it found.
Exercise 10. Why must the coverage check live in the flow rather than be run once?
Exercise 11. What can no check in this chapter see?
Where this fits
Part of the Tcl curriculum.
