Perl Part II · Module 23 · Static Timing Analysis Report Parsing
Extracting Slack
Slack is the number the whole report exists to deliver, and capturing it looks like one pattern against one line. It is five lines in practice: the two annotated forms, a value that is not a number, a version with no annotation, and a path whose slack line is simply absent. This chapter measures what the obvious pattern does with each, and establishes why the non-numeric case is the dangerous one — for a reason that turned out to be the opposite of what I expected.
Intermediate12 min readPerlPart IISTA Reports
Part II · Module 23 · Chapter 23.2 · Extracting Slack
1. Five Slack Lines, Two Patterns
use strict;
use warnings;
# annotations.pt.rpt carries five endpoints whose slack lines take the five
# forms a real report produces: MET, VIOLATED, INFINITY, no annotation at
# all, and no slack line. The numbers come from the generated corner reports.
my $RPT = "reports/annotations.pt.rpt";
open my $fh, "<", $RPT or die "$RPT: $!";
local $/;
my $text = <$fh>;
close $fh;
my @blocks = split /(?=^[ \t]*Endpoint:)/m, $text;
shift @blocks while @blocks && $blocks[0] !~ /Endpoint:/;
# ---- A: the pattern almost everybody writes first
my $A = qr/^[ \t]*slack[ \t]*\((MET|VIOLATED)\)[ \t]*(-?[\d.]+)/m;
# ---- B: annotation optional, value may be INFINITY, and ABSENCE is a state
my $B = qr/^[ \t]*slack[ \t]*(?:\((\w+)\))?[ \t]*(-?[\d.]+|INFINITY)[ \t]*$/m;
printf "%-14s %-22s %s\n", "ENDPOINT", "A (strict)", "B (tolerant)";
my (@a_ok, @b_ok);
for my $blk (@blocks) {
my ($ep) = $blk =~ /^[ \t]*Endpoint:[ \t]*(\S+)/m;
next unless defined $ep;
my ($aa, $av) = $blk =~ $A;
my ($ba, $bv) = $blk =~ $B;
push @a_ok, $ep if defined $av;
push @b_ok, $ep if defined $bv;
printf "%-14s %-22s %s\n", $ep,
(defined $av ? "$av ($aa)" : "NOTHING"),
(defined $bv ? ($bv . " (" . ($ba // "no annotation") . ")") : "NOTHING");
}
printf "\nA read %d of %d endpoints; B read %d of %d\n",
scalar @a_ok, scalar @blocks, scalar @b_ok, scalar @blocks;
# ---- the three outcomes, named (18.8)
print "\nclassifying each endpoint into THREE states, not two:\n";
printf "%-14s %-12s %s\n", "ENDPOINT", "STATE", "why";
for my $blk (@blocks) {
my ($ep) = $blk =~ /^[ \t]*Endpoint:[ \t]*(\S+)/m;
next unless defined $ep;
my ($ann, $val) = $blk =~ $B;
my ($state, $why);
if (!defined $val) { ($state, $why) = ("UNKNOWN", "no slack line in the block") }
elsif ($val eq "INFINITY") { ($state, $why) = ("UNCONSTRAINED", "no capture clock reaches it") }
elsif (defined $ann && $ann eq "VIOLATED") { ($state, $why) = ("VIOLATED", "the tool says so") }
elsif (defined $ann && $ann eq "MET") { ($state, $why) = ("MET", "the tool says so") }
elsif ($val < 0) { ($state, $why) = ("VIOLATED", "no annotation; sign is negative") }
else { ($state, $why) = ("MET", "no annotation; sign is non-negative") }
printf "%-14s %-12s %s\n", $ep, $state, $why;
}
# ---- why INFINITY must not become a number
print "\nwhat happens if INFINITY is treated as a number:\n";
my $inf = "INFINITY";
{
no warnings 'numeric';
printf " 'INFINITY' + 0 = %s\n", $inf + 0;
printf " is 'INFINITY' < 0 ? %s\n", ($inf < 0 ? "yes" : "no");
printf " does it sort as the worst? %s\n", (($inf + 0) < -0.09 ? "yes" : "no");
}
printf " does it sort as the BEST? %s\n", (($inf + 0) > 0.465 ? "yes" : "no");
print " so Perl recognises the WORD: 'INFINITY' numifies to Inf, not to 0.\n";
print " That is worse than a zero would be. An unconstrained path becomes\n";
print " the path with the MOST margin, so it sorts to the bottom of a\n";
print " worst-slack list and vanishes from every top-N report (23.10).\n";
print " Perl also numifies 'Inf', 'Infinity' and 'NaN' this way, so a\n";
print " string field is not safely numeric just because it is not empty.\n";
# ---- and the sign-only rule, measured against the annotation
print "\nis the SIGN a reliable substitute for the annotation?\n";
my ($agree, $total) = (0, 0);
for my $blk (@blocks) {
my ($ann, $val) = $blk =~ $B;
next unless defined $val && defined $ann && $val ne "INFINITY";
$total++;
my $by_sign = ($val < 0) ? "VIOLATED" : "MET";
$agree++ if $by_sign eq $ann;
}
printf " on the %d endpoints that have BOTH, sign and annotation agree %d times\n",
$total, $agree;
print " -- so the sign is usually right, and 23.3 is about the cases where\n";
print " trusting it instead of the annotation changes the answer.\n";ENDPOINT A (strict) B (tolerant)
ep_met/D 0.4650 (MET) 0.4650 (MET)
ep_viol/D -0.0900 (VIOLATED) -0.0900 (VIOLATED)
ep_infinite/D NOTHING INFINITY (MET)
ep_noann/D NOTHING 0.1234 (no annotation)
ep_missing/D NOTHING NOTHING
A read 2 of 5 endpoints; B read 4 of 5
classifying each endpoint into THREE states, not two:
ENDPOINT STATE why
ep_met/D MET the tool says so
ep_viol/D VIOLATED the tool says so
ep_infinite/D UNCONSTRAINED no capture clock reaches it
ep_noann/D MET no annotation; sign is non-negative
ep_missing/D UNKNOWN no slack line in the block
what happens if INFINITY is treated as a number:
'INFINITY' + 0 = Inf
is 'INFINITY' < 0 ? no
does it sort as the worst? no
does it sort as the BEST? yes
so Perl recognises the WORD: 'INFINITY' numifies to Inf, not to 0.
That is worse than a zero would be. An unconstrained path becomes
the path with the MOST margin, so it sorts to the bottom of a
worst-slack list and vanishes from every top-N report (23.10).
Perl also numifies 'Inf', 'Infinity' and 'NaN' this way, so a
string field is not safely numeric just because it is not empty.
is the SIGN a reliable substitute for the annotation?
on the 2 endpoints that have BOTH, sign and annotation agree 2 times
-- so the sign is usually right, and 23.3 is about the cases where
trusting it instead of the annotation changes the answer.The strict pattern reads 2 of 5 endpoints. The tolerant one reads 4, and the fifth has no slack line at all — which is the correct answer for it.
The difference between the two patterns is three decisions:
my $A = qr/^[ \t]*slack[ \t]*\((MET|VIOLATED)\)[ \t]*(-?[\d.]+)/m;
my $B = qr/^[ \t]*slack[ \t]*(?:\((\w+)\))?[ \t]*(-?[\d.]+|INFINITY)[ \t]*$/m;The annotation is optional — (?:\((\w+)\))?. Some tool versions and some options print the bare word slack followed by a value.
The annotation is captured as \w+, not as an alternation of the two words you have seen. A tool that prints (UNCONSTRAINED) or a localised word still parses, and the value is still recovered.
The value may not be a number. (-?[\d.]+|INFINITY) — and section 3 is about why that alternative matters more than it looks.
Both use [ \t]* rather than \s*, for the reason Chapter 23.1 measured: \s matches newlines and turns a /m-anchored pattern into one that matches in places you did not intend.
2. Three States, Not Two
A slack value is not a number with two labels. It is one of four situations, and the script classifies them:
ep_met/D MET the tool says so
ep_viol/D VIOLATED the tool says so
ep_infinite/D UNCONSTRAINED no capture clock reaches it
ep_noann/D MET no annotation; sign is non-negative
ep_missing/D UNKNOWN no slack line in the blockUNCONSTRAINED is not a pass. An endpoint with infinite slack has no capture clock reaching it — which usually means a missing constraint, not a comfortable path. It is one of the most useful things a timing report can tell you, and treating it as a very good MET discards it.
UNKNOWN is not a pass either. A path block with no slack line means the parse is incomplete or the report is truncated, and Chapter 18.9's argument applies unchanged: the absence of a value is a distinct fact, and mapping it onto zero or onto MET manufactures a result.
So the classification has four outcomes and only two of them are verdicts about timing. Chapter 18.8's rule once more: unrecognised must be a named outcome.
3. INFINITY Numifies To Inf, Not To Zero
Here is the measurement that corrected my own expectation. I assumed "INFINITY" in numeric context would behave like any other non-numeric string and become 0. It does not:
'INFINITY' + 0 = Inf
is 'INFINITY' < 0 ? no
does it sort as the worst? no
does it sort as the BEST? yesPerl recognises the word. INFINITY, Inf, Infinity and NaN all numify to real infinities and NaN, case-insensitively — unlike abc, which becomes 0, or 12abc, which becomes 12.
And that is worse than a zero would be:
A zero would put the path in the middle of a sorted slack list — wrong, but visible.
An Inf puts it at the far end as the best path in the design. So an unconstrained endpoint — one of the highest-value findings in a timing report — sorts to the bottom of a worst-slack list and never appears in a top-N report at all (Chapter 23.10). The report is not wrong about any number it shows; it silently omits the one that mattered.
4. When The Annotation Is Absent
ep_noann/D has a value and no (MET) or (VIOLATED), and the classifier falls back to the sign:
elsif ($val < 0) { ("VIOLATED", "no annotation; sign is negative") }
else { ("MET", "no annotation; sign is non-negative") }The measurement checks that fallback against the annotation where both exist:
on the 2 endpoints that have BOTH, sign and annotation agree 2 timesThe sign is usually right. Which is exactly why it is a trap, and why Chapter 23.3 is a separate chapter: a rule that agrees with the authority on every case you have tested is not a verified rule, it is an untested one — the same shape as Chapter 21.2's error-counting verdict that was correct on every healthy log.
Two reasons to prefer the annotation when it is present:
The tool knows about things the sign does not encode — a path that is unconstrained, a check that was waived, a hold path where the sign convention is the same but the meaning differs.
Zero is ambiguous. A slack of exactly 0.0000 is MET by the annotation and MET by >= 0, but a slack of -0.0000 — which a tool can print — is VIOLATED by annotation and MET by >= 0 in floating point. Rare, and the kind of rare that appears once and costs a day.
So: use the annotation when present, the sign only as a fallback, and record which one you used. That last part is Chapter 18.8's label again — a verdict whose provenance is in the output is one somebody can check.
5. What To Remember
- Slack is the number the report exists to deliver, and it arrives in five forms.
- Measured: the strict pattern read 2 of 5 endpoints; the tolerant one read 4, with the fifth correctly unreadable.
- Make the annotation optional —
(?:\((\w+)\))?— and capture it as\w+, not as a two-word alternation. - Allow a non-numeric value in the pattern:
(-?[\d.]+|INFINITY). - Use
[ \t]*, never\s*, in a/m-anchored pattern (23.1). - Four outcomes, not two: MET, VIOLATED, UNCONSTRAINED, UNKNOWN.
- UNCONSTRAINED is not a pass — it usually means a missing constraint, and it is one of the most valuable findings in a report.
- UNKNOWN is not a pass — a missing slack line means an incomplete parse or a truncated report (18.9).
- Measured, correcting my own expectation:
'INFINITY' + 0isInf, not 0. Perl recognises the word. Inf,Infinity,NaNall numify, case-insensitively;abcgives 0 and12abcgives 12.- An
Infslack sorts as the BEST path in the design, so an unconstrained endpoint vanishes from every top-N report (23.10). - "Not empty" is not "safely numeric." Test the string's shape:
/^-?\d+(?:\.\d+)?$/. looks_like_numberreturns TRUE forInfandNaN— correct, and useless here.- Related:
'3.5ns' + 1is 4.5 — Perl takes the leading number and drops the unit (14.7). Both failures are silent. - Measured: where both exist, sign and annotation agreed 2 of 2 — which is why the sign is a trap, not a rule (23.3).
- Zero is ambiguous: a printed
-0.0000is VIOLATED by annotation and MET by>= 0. - Prefer the annotation, fall back to the sign, and record which you used (18.8).
Chapter 23.3 takes that fallback seriously. Finding violated endpoints from the annotation rather than from arithmetic sounds like a detail, and it decides whether a timing signoff report can be trusted.
Where this fits
Part of the Perl curriculum.
