Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
store.pl
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";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
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.

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

The store is Chapter 7.8's hash of hashes with a small record in each cell:

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

Two nestings of the same data. Endpoint-outer makes the worst-corner-for-an-endpoint question a direct lookup and the worst-endpoint-in-a-corner question a full scan. Corner-outer reverses both costs. The review asks about endpoints, so endpoint goes outermost.endpoint -> cornerthe chosen nestingworst corner for Xlookup, then sort 3worst endpoint in Cscan all endpointscorner -> endpointthe flipped nestingboth costs reversedsame data, same facts12
Both structures hold exactly the same facts, and they differ only in which question is a single lookup and which requires walking everything. With the endpoint outermost, asking about one endpoint reaches its whole row immediately and the worst corner is a sort of a handful of values; asking about a corner walks every endpoint. Flipping the nesting exactly reverses that. A timing review asks about endpoints far more often than about corners, which is what decides it.

The measurement states the costs:

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

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

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

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

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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_paths legitimately omitting comfortable endpoints.
  • Check completeness with exists, not defined (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.