Perl Part II · Module 23 · Static Timing Analysis Report Parsing
A Per-Endpoint Slack Store Across Corners
A timing review asks two questions, and they look like they need two data structures: which corner is worst for this endpoint, and which endpoint is worst in this corner. One two-level hash answers both, and the only real decision is which key goes on the outside. This chapter builds it from three real corner reports, derives a third finding the two questions do not ask for, and then measures what happens when one cell of the table is missing — which is the case that matters, because the answer stays confident.
Advanced11 min readPerlPart IISTA Reports
Part II · Module 23 · Chapter 23.9 · A Per-Endpoint Slack Store Across Corners
1. Four Endpoints, Three Corners
use strict;
use warnings;
# Builds the per-endpoint, per-corner slack store from the three generated
# PrimeTime corner reports, whose numbers are derived from the real Module 20
# netlist (23.1). No STA tool exists on this machine.
my @RPTS = qw(
reports/typ_1v00_25c.pt.rpt
reports/slow_0v90_125c.pt.rpt
reports/fast_1v10_m40c.pt.rpt
);
my $SLACK = qr/^[ \t]*slack[ \t]*(?:\((\w+)\))?[ \t]*(-?[\d.]+|INFINITY)[ \t]*$/m;
# ---- the store: endpoint -> corner -> { slack, verdict }
my (%store, @corners);
for my $r (@RPTS) {
open my $fh, "<", $r or die "$r: $!";
local $/;
my $text = <$fh>;
close $fh;
my ($corner) = $text =~ /^Corner[ \t]*:[ \t]*(\S+)/m;
push @corners, $corner;
my @blocks = split /(?=^[ \t]*Startpoint:)/m, $text;
shift @blocks while @blocks && $blocks[0] !~ /Startpoint:/;
for my $b (@blocks) {
my ($ep) = $b =~ /^[ \t]*Endpoint:[ \t]*(\S+)/m;
my ($ann, $val) = $b =~ $SLACK;
next unless defined $ep && defined $val;
$store{$ep}{$corner} = { slack => $val, verdict => $ann };
}
}
printf "%d endpoint(s) x %d corner(s)\n\n", scalar keys %store, scalar @corners;
printf "%-10s", "ENDPOINT";
printf " %-17s", $_ for @corners;
print " WORST CORNER\n";
for my $ep (sort keys %store) {
printf "%-10s", $ep;
printf " %-17s", $store{$ep}{$_}{slack} for @corners;
# QUESTION 1: the worst corner for THIS endpoint
my ($worst) = sort { $store{$ep}{$a}{slack} <=> $store{$ep}{$b}{slack} } @corners;
printf " %s (%s)\n", $worst, $store{$ep}{$worst}{slack};
}
# QUESTION 2: the worst endpoint for a given corner
print "\nthe worst endpoint in each corner:\n";
for my $c (@corners) {
my ($worst) = sort { $store{$a}{$c}{slack} <=> $store{$b}{$c}{slack} } keys %store;
printf " %-17s %-10s %s\n", $c, $worst, $store{$worst}{$c}{slack};
}
# ---- why endpoint-first and not corner-first
print "\nwhy the OUTER key is the endpoint:\n";
my %flipped;
for my $ep (keys %store) { for my $c (@corners) { $flipped{$c}{$ep} = $store{$ep}{$c} } }
printf " endpoint-first: 'worst corner for %s' is one hash lookup then a sort\n",
(sort keys %store)[0];
printf " of %d values\n", scalar @corners;
printf " corner-first: the same question needs a scan of all %d corners,\n", scalar @corners;
printf " each a hash of %d endpoints\n", scalar keys %store;
print " Both structures hold the same data; they differ in which question is\n";
print " a lookup and which is a scan. Put the key you ASK ABOUT outermost --\n";
print " a timing review asks about endpoints.\n";
# ---- and the derived view that answers both at once
print "\nthe derived summary, computed once from the store:\n";
printf "%-10s %-12s %-17s %-10s %s\n", "ENDPOINT", "WORST", "AT CORNER", "VERDICT", "spread";
for my $ep (sort keys %store) {
my ($worst) = sort { $store{$ep}{$a}{slack} <=> $store{$ep}{$b}{slack} } @corners;
my ($best) = sort { $store{$ep}{$b}{slack} <=> $store{$ep}{$a}{slack} } @corners;
printf "%-10s %-12s %-17s %-10s %.4f\n", $ep,
$store{$ep}{$worst}{slack}, $worst,
($store{$ep}{$worst}{verdict} // "-"),
$store{$ep}{$best}{slack} - $store{$ep}{$worst}{slack};
}
print " the SPREAD is the corner sensitivity: a large spread means the\n";
print " endpoint's margin depends heavily on the corner, which is a\n";
print " different finding from a small worst-case slack.\n";
# ---- a missing cell is a real state
print "\nwhat if a corner is missing an endpoint?\n";
delete $store{"bit3/D"}{ $corners[1] };
my @missing;
for my $ep (sort keys %store) {
push @missing, "$ep\@$_" for grep { !exists $store{$ep}{$_} } @corners;
}
printf " after deleting one cell: %d missing cell(s): %s\n",
scalar @missing, join(", ", @missing);
print " a worst-corner sort over the REMAINING corners returns a worst that\n";
print " is not the worst -- the missing corner might have been worse. So an\n";
print " incomplete row must be reported, not silently reduced (18.9).\n";
my ($w) = sort { ($store{"bit3/D"}{$a}{slack} // 9e9) <=> ($store{"bit3/D"}{$b}{slack} // 9e9) }
grep { exists $store{"bit3/D"}{$_} } @corners;
printf " bit3/D now reports worst=%s at %s, having lost the corner where it\n",
$store{"bit3/D"}{$w}{slack}, $w;
print " was VIOLATED.\n";4 endpoint(s) x 3 corner(s)
ENDPOINT typ_1v00_25c slow_0v90_125c fast_1v10_m40c WORST CORNER
bit0/D 0.7290 0.4650 0.7818 slow_0v90_125c (0.4650)
bit1/D 0.6530 0.2750 0.7286 slow_0v90_125c (0.2750)
bit2/D 0.5800 0.0925 0.6775 slow_0v90_125c (0.0925)
bit3/D 0.5070 -0.0900 0.6264 slow_0v90_125c (-0.0900)
the worst endpoint in each corner:
typ_1v00_25c bit3/D 0.5070
slow_0v90_125c bit3/D -0.0900
fast_1v10_m40c bit3/D 0.6264
why the OUTER key is the endpoint:
endpoint-first: 'worst corner for bit0/D' is one hash lookup then a sort
of 3 values
corner-first: the same question needs a scan of all 3 corners,
each a hash of 4 endpoints
Both structures hold the same data; they differ in which question is
a lookup and which is a scan. Put the key you ASK ABOUT outermost --
a timing review asks about endpoints.
the derived summary, computed once from the store:
ENDPOINT WORST AT CORNER VERDICT spread
bit0/D 0.4650 slow_0v90_125c MET 0.3168
bit1/D 0.2750 slow_0v90_125c MET 0.4536
bit2/D 0.0925 slow_0v90_125c MET 0.5850
bit3/D -0.0900 slow_0v90_125c VIOLATED 0.7164
the SPREAD is the corner sensitivity: a large spread means the
endpoint's margin depends heavily on the corner, which is a
different finding from a small worst-case slack.
what if a corner is missing an endpoint?
after deleting one cell: 1 missing cell(s): bit3/D@slow_0v90_125c
a worst-corner sort over the REMAINING corners returns a worst that
is not the worst -- the missing corner might have been worse. So an
incomplete row must be reported, not silently reduced (18.9).
bit3/D now reports worst=0.5070 at typ_1v00_25c, having lost the corner where it
was VIOLATED.One structure, both questions.
ENDPOINT typ slow fast WORST CORNER
bit3/D 0.5070 -0.0900 0.6264 slow_0v90_125c (-0.0900)
the worst endpoint in each corner:
typ_1v00_25c bit3/D 0.5070
slow_0v90_125c bit3/D -0.0900
fast_1v10_m40c bit3/D 0.6264The store is Chapter 7.8's hash of hashes with a small record in each cell:
$store{$endpoint}{$corner} = { slack => $val, verdict => $ann };A record rather than a bare number, because Chapter 23.3 established that the verdict is not derivable from the slack. Storing only the number would force every consumer to re-derive a verdict the tool already gave you, by the one method measured to be wrong.
2. Why The Endpoint Is The Outer Key
Figure 23.9.1 — The same data, two nestings, two different costs
The measurement states the costs:
endpoint-first: 'worst corner for bit0/D' is one hash lookup then a sort of 3 values
corner-first: the same question needs a scan of all 3 corners,
each a hash of 4 endpointsBoth structures hold identical data. Neither is more correct. They differ in which question is cheap, and on a real design the asymmetry is large: three corners and two hundred thousand endpoints.
So the rule is simple and worth stating as a rule: put the key you ask about outermost. A timing review asks "why is this endpoint failing" far more often than "what is the worst path in the slow corner" — the second question is answered once a day by a dashboard, the first is answered continually by whoever is fixing things.
And when both questions are hot, the answer is not to flip the nesting but to derive an index — a second small hash of corner to worst endpoint, computed once from the store. Cheap, and it keeps one structure authoritative. Chapter 20.4's multiplier map was the same move.
3. The Spread
The derived summary carries a column neither headline question asks for:
ENDPOINT WORST AT CORNER VERDICT spread
bit0/D 0.4650 slow MET 0.3168
bit1/D 0.2750 slow MET 0.4536
bit2/D 0.0925 slow MET 0.5850
bit3/D -0.0900 slow VIOLATED 0.7164The spread is best minus worst across corners — corner sensitivity — and here it rises with path depth, from 0.3168 to 0.7164. That is expected and worth seeing: a longer combinational chain accumulates more derate, so its margin varies more between corners.
Why it is a separate finding from the worst-case slack:
A large spread with comfortable worst-case margin means the endpoint is highly corner-sensitive. It is fine today and it will be the first thing to fail if a corner is added, a derate changes, or the library is recharacterised.
A small spread near zero means the endpoint is uniformly tight. It fails everywhere or nowhere, and fixing it is a structural change rather than a corner-specific one.
Both can have the same worst-case number. Reporting only the worst collapses two different situations into one, which is the shape Chapter 20.5 found with text-versus-elaborated counts and Chapter 21.4 with occurrences-versus-logs: the number you report determines the conclusion, and the obvious number is usually not the only one available for free.
4. A Missing Cell Turns A Violation Into A Pass
This is the measurement that makes the chapter Critical. Delete one cell — bit3/D at the slow corner — and ask the same question:
1 missing cell(s): bit3/D@slow_0v90_125c
bit3/D now reports worst=0.5070 at typ_1v00_25c, having lost the corner
where it was VIOLATEDThe answer is confident, plausible, and wrong. 0.5070 MET is a perfectly reasonable worst-case slack for a path like this. Nothing in the row says a corner is missing, because a sort over the remaining values does not know what it is not looking at.
How a cell goes missing in practice, and none of these is exotic:
A corner's run failed and its report was never produced — Chapter 21.2's whole subject, and the reason a missing input must be a state rather than an absence.
A report was truncated mid-way, so the later endpoints have no blocks.
report_timing -max_paths N returned only the worst N paths per group, so comfortable endpoints are legitimately absent from some corners — which is the common case and the reason you cannot simply require a full matrix.
So the store needs a completeness check, and it is three lines:
my @missing;
for my $ep (sort keys %store) {
push @missing, "$ep\@$_" for grep { !exists $store{$ep}{$_} } @corners;
}exists, not defined — Chapter 6.4's distinction, and it matters here: a cell could legitimately hold a record whose slack is undef (an unparsed block), and that is a different state from no cell at all.
And the reporting rule is Chapter 18.9's: an incomplete row is reported, not reduced. bit3/D: worst -0.0900 (slow) [3 of 3 corners] is trustworthy; bit3/D: worst 0.5070 (typ) from two corners is a lie by omission. Carrying the corner count in the output costs one field and makes every row self-describing.
5. What To Remember
- A timing review asks two questions, and one structure answers both.
$store{$endpoint}{$corner} = { slack, verdict }— a hash of hashes (7.8) with a record per cell.- Store a record, not a bare number — the verdict is not derivable from the slack (23.3).
- Measured: 4 endpoints x 3 corners, with the worst corner per endpoint and the worst endpoint per corner both answered.
- Both nestings hold identical data and differ only in which question is a lookup and which is a scan.
- Put the key you ask about outermost — a review asks about endpoints continually and about corners once a day.
- When both questions are hot, derive an index rather than flipping the nesting (20.4's multiplier map).
- The SPREAD — best minus worst — is corner sensitivity, measured rising 0.3168 to 0.7164 with path depth.
- A large spread with comfortable margin means corner-sensitive; a small spread near zero means uniformly tight. Same worst-case number, different situations.
- The number you report determines the conclusion (20.5, 21.4).
- Measured: deleting ONE cell made a VIOLATED endpoint report
0.5070 MET— confidently, from a row that still looks complete. - A sort over what remains does not know what it is missing.
- Cells go missing for ordinary reasons: a failed corner run, a truncated report, or
-max_pathslegitimately omitting comfortable endpoints. - Check completeness with
exists, notdefined(6.4) — a cell holding an unparsed record is a different state from no cell. - Report an incomplete row, do not reduce it (18.9) — carry the corner count so every row is self-describing.
Chapter 23.10 closes the module by making the store readable. Ten thousand paths reduce to the twenty that matter and a bucketed distribution, and the two views answer different questions about the same numbers.
Where this fits
Part of the Perl curriculum.
