Perl Part II · Module 23 · Static Timing Analysis Report Parsing
Detecting Violated Endpoints
Finding the failing paths is the one thing a timing parser absolutely must get right, and the obvious way to do it — a negative slack is a violation — is wrong in both directions. This chapter measures it against the tool's own annotation on six endpoints and finds four disagreements: a real violation missed, a false one reported, an unconstrained endpoint hidden, and a case where Perl and the tool disagree about whether a printed number is negative at all.
Advanced12 min readPerlPart IISTA Reports
Part II · Module 23 · Chapter 23.3 · Detecting Violated Endpoints
1. Six Endpoints, Two Detectors
use strict;
use warnings;
# disagree.pt.rpt uses slack VALUES from the generated corner reports with
# annotations that exercise the cases where the annotation and the sign of
# the number do not agree -- a hold check annotated VIOLATED on a positive
# number, a waived path annotated MET on a negative one, and a printed -0.0000.
my $RPT = "reports/disagree.pt.rpt";
open my $fh, "<", $RPT or die "$RPT: $!";
local $/;
my $text = <$fh>;
close $fh;
my @blocks = split /(?=^[ \t]*Startpoint:)/m, $text;
shift @blocks while @blocks && $blocks[0] !~ /Startpoint:/;
my $SLACK = qr/^[ \t]*slack[ \t]*(?:\((\w+)\))?[ \t]*(-?[\d.]+|INFINITY)[ \t]*$/m;
my $NUMERIC = qr/^-?\d+(?:\.\d+)?$/;
printf "%-18s %-10s %-11s %-12s %-12s %s\n",
"ENDPOINT", "ANNOT", "SLACK", "BY SIGN", "BY ANNOT", "agree?";
my (@by_sign, @by_annot, $disagreements);
for my $b (@blocks) {
my ($ep) = $b =~ /^[ \t]*Endpoint:[ \t]*(\S+)/m;
next unless defined $ep;
my ($ann, $val) = $b =~ $SLACK;
next unless defined $val;
# BY SIGN: arithmetic only, as a naive detector does it
my $sign_v = ($val =~ $NUMERIC && $val < 0) ? "VIOLATED" : "MET";
# BY ANNOTATION: the tool's own word, with a non-numeric value as its
# own state rather than forced into one of the two
my $annot_v = !($val =~ $NUMERIC) ? "UNCONSTRAINED"
: (defined $ann ? $ann : $sign_v);
push @by_sign, $ep if $sign_v eq "VIOLATED";
push @by_annot, $ep if $annot_v eq "VIOLATED";
my $agree = ($sign_v eq $annot_v) ? "yes" : "NO";
$disagreements++ if $agree eq "NO";
printf "%-18s %-10s %-11s %-12s %-12s %s\n",
$ep, ($ann // "-"), $val, $sign_v, $annot_v, $agree;
}
printf "\nviolations BY SIGN (%d): %s\n", scalar @by_sign, join(", ", @by_sign);
printf "violations BY ANNOTATION (%d): %s\n", scalar @by_annot, join(", ", @by_annot);
printf "the two detectors disagree on %d of %d endpoints\n", $disagreements, scalar @blocks;
print "\nwhat each disagreement costs:\n";
print " hold_viol/D sign says MET, the tool says VIOLATED\n";
print " -> a REAL violation missed. This is a hold check:\n";
print " the number is positive and the path still fails.\n";
print " waived_neg/D sign says VIOLATED, the tool says MET\n";
print " -> a FALSE violation reported, and a false violation\n";
print " in a signoff report costs somebody an afternoon.\n";
print " unconstrained/D sign says MET (INFINITY is not < 0)\n";
print " -> hidden entirely. 23.2 measured why: Perl numifies\n";
print " INFINITY to Inf, so it is the BEST path, not a gap.\n";
print "\nand the negative zero:\n";
my ($nz) = grep { /negzero/ } map { ($_ =~ /Endpoint:[ \t]*(\S+)/)[0] // "" } @blocks;
{
no warnings 'numeric';
my $v = "-0.0000";
printf " the report prints %s ; is it < 0 in Perl? %s\n", $v, ($v < 0 ? "yes" : "NO");
printf " so a sign test calls it MET; the annotation says VIOLATED\n";
printf " (and %s is the endpoint it happens on)\n", ($nz || "negzero/D");
}
print "\nthe rule that follows:\n";
print " trust the ANNOTATION. Use the sign only when there is no annotation,\n";
print " and record which one decided -- a verdict whose provenance is in the\n";
print " output is one somebody can check (18.8).\n";ENDPOINT ANNOT SLACK BY SIGN BY ANNOT agree?
agree_met/D MET 0.4650 MET MET yes
agree_viol/D VIOLATED -0.0900 VIOLATED VIOLATED yes
hold_viol/D VIOLATED 0.0120 MET VIOLATED NO
waived_neg/D MET -0.0310 VIOLATED MET NO
negzero/D VIOLATED -0.0000 MET VIOLATED NO
unconstrained/D MET INFINITY MET UNCONSTRAINED NO
violations BY SIGN (2): agree_viol/D, waived_neg/D
violations BY ANNOTATION (3): agree_viol/D, hold_viol/D, negzero/D
the two detectors disagree on 4 of 6 endpoints
what each disagreement costs:
hold_viol/D sign says MET, the tool says VIOLATED
-> a REAL violation missed. This is a hold check:
the number is positive and the path still fails.
waived_neg/D sign says VIOLATED, the tool says MET
-> a FALSE violation reported, and a false violation
in a signoff report costs somebody an afternoon.
unconstrained/D sign says MET (INFINITY is not < 0)
-> hidden entirely. 23.2 measured why: Perl numifies
INFINITY to Inf, so it is the BEST path, not a gap.
and the negative zero:
the report prints -0.0000 ; is it < 0 in Perl? NO
so a sign test calls it MET; the annotation says VIOLATED
(and negzero/D is the endpoint it happens on)
the rule that follows:
trust the ANNOTATION. Use the sign only when there is no annotation,
and record which one decided -- a verdict whose provenance is in the
output is one somebody can check (18.8).violations BY SIGN (2): agree_viol/D, waived_neg/D
violations BY ANNOTATION (3): agree_viol/D, hold_viol/D, negzero/D
the two detectors disagree on 4 of 6 endpointsThe two lists share exactly one endpoint. The sign detector finds two violations, the annotation finds three, and only agree_viol/D is in both. On a signoff report that is not a discrepancy, it is a different answer.
2. The Three Disagreements, And What Each Costs
hold_viol/D — slack 0.0120, annotated VIOLATED. The sign says met.
This is a hold check, and hold failures are the reason the sign rule cannot work. The number in a hold path's slack line is positive and the path still fails, because the quantity being compared is different — Chapter 23.4's Path Type field is what distinguishes them, and a parser that ignores it and looks only at the sign cannot possibly get hold paths right.
A real violation missed is the worst outcome in this chapter. A signoff report that says a design is clean when a hold path fails is the failure mode this whole module exists to prevent.
waived_neg/D — slack -0.0310, annotated MET. The sign says violated.
A waived path, or one excluded by a set_false_path the tool has applied. A false violation reported costs somebody an afternoon chasing a path the tool has already decided about — and Chapter 20.7 measured the compounding cost: a report with false entries stops being read, taking the true entries with it.
unconstrained/D — slack INFINITY, annotated MET. The sign says met, and the endpoint disappears.
Chapter 23.2 measured why this is worse than it looks: INFINITY numifies to Inf, so it is not merely not a violation — it is the best path in the design, and it sorts to the bottom of every list.
3. Negative Zero
The fourth disagreement is the one that is hardest to believe until it is measured:
the report prints -0.0000 ; is it < 0 in Perl? NOA tool can print -0.0000, and it annotates it VIOLATED — the true slack is a tiny negative value that rounds to zero at four decimal places. Perl reads the string as a zero, and zero is not less than zero.
So the sign test calls it MET.
This matters beyond the single case, because it is the general shape of comparing a rounded string against a threshold:
The report's printed precision is not the tool's internal precision. Four decimal places is a display choice, and a value of -0.00004 prints as -0.0000.
Any threshold comparison on a printed value inherits that rounding. A rule like "report paths with slack below -0.001" will silently pass a path whose real slack is -0.0009 and whose printed value is -0.0009, and that is fine — but it will also pass one printed as -0.0000 that is genuinely negative.
The annotation does not have this problem, because the tool computed it before rounding. That is the strongest argument for using it: it is the only field in the block that reflects the full-precision value.
4. Keeping The Per-Endpoint Detail
A violation count is almost useless on its own. 3 violations tells a reviewer nothing they can act on, and Chapter 21.5 made the same argument about UVM error counts: the count plus the identifying detail is what starts work.
So the detector keeps a record per endpoint, not a tally:
push @by_annot, $ep if $annot_v eq "VIOLATED";and in a real script that is a hash of hashes (Chapter 7.8) carrying what Chapter 23.4 extracts — startpoint, endpoint, path group, path type — plus the slack and, critically, which rule decided:
$v{$ep} = { slack => $val, state => $annot_v, decided_by => (defined $ann ? "annotation" : "sign") };That last field is Chapter 18.8's provenance label, and it has a specific payoff here: when somebody disputes a violation, the first question is whether the tool said so or your script inferred it. A report that answers that in its own output does not need the argument.
And the fourth state stays separate throughout:
UNKNOWN is not zero violations. A block whose slack line did not parse contributes no verdict, and a summary that says "0 violations" when three blocks failed to parse is the quiet failure Chapter 18.9 measured. The honest summary is 3 violated, 1 unconstrained, 2 unparsed — and the exit status follows Chapter 15.4: unparsed blocks mean "I could not tell", not "clean".
5. What To Remember
- Finding the failing paths is the one thing a timing parser must get right.
- Measured: sign and annotation disagree on 4 of 6 endpoints, sharing only one violation between their two lists.
- The sign detector fails in BOTH directions — it misses real violations and reports false ones.
- A hold check fails with a POSITIVE slack, so the sign rule cannot handle hold paths at all;
Path Typeis what distinguishes them (23.4). - A missed real violation is the worst outcome — it is a signoff report calling a failing design clean.
- A false violation costs an afternoon, and a report with false entries stops being read (20.7).
- An unconstrained endpoint is hidden by the sign rule and sorts as the best path (23.2).
- Measured: Perl says
-0.0000 < 0is FALSE, and a tool annotates that lineVIOLATED. - The printed precision is not the tool's precision —
-0.00004prints as-0.0000. - Any threshold on a printed value inherits its rounding; the annotation was computed before rounding.
- The sign rule survives because it is right on every ordinary setup path (23.2 measured 2 of 2) — which makes it untested, not verified (21.2's shape).
- Build the awkward cases on purpose, as a separate fixture.
- Keep a record per endpoint, not a tally — a count alone is not actionable (21.5).
- Record which rule decided —
decided_by => "annotation"or"sign"(18.8). - UNKNOWN is not zero violations. Report
3 violated, 1 unconstrained, 2 unparsedand exit 3 for the unparsed (18.9, 15.4).
Chapter 23.4 captures the fields that make a slack number mean something. A number attached to an endpoint, a path group and a clock can be attributed to a block and a review owner; the same number on its own cannot.
Where this fits
Part of the Perl curriculum.
