Skip to content
VLSI Mentor

Perl Part II · Module 23 · Static Timing Analysis Report Parsing

Anatomy of a report_timing Path

Static timing analysis is the work Perl became an EDA standard for, and a timing report is the most structured text in the whole flow: a repeated block that names where a path starts, where it ends, which group it belongs to, and how much margin it has. Learning that shape once means every pattern in the rest of the module is looking for a field you already know is present. This chapter establishes it, verifies the report's own arithmetic rather than trusting it, and identifies the one field a path block does not carry.

Intermediate12 min readPerlPart IISTA Reports

Part II · Module 23 · Chapter 23.1 · Anatomy of a report_timing Path

1. Four Paths, Six Fields

Azvya Education Pvt. Ltd.VLSI Mentor
anatomy.pl
use strict;
use warnings;

# typ_1v00_25c.pt.rpt is generated by gen_timing.pl from the REAL Module 20
# netlist: pin directions from the cell library's own declarations, topology
# from the netlist, delays from lib/delays.txt. So every number below can be
# checked by hand. No STA tool exists on this machine.
my $RPT = "reports/typ_1v00_25c.pt.rpt";

# the five fields that IDENTIFY a path, as opposed to describing it
my %FIELD = (
  startpoint => qr/^[ \t]*Startpoint:[ \t]*(\S+)/m,
  endpoint   => qr/^[ \t]*Endpoint:[ \t]*(\S+)/m,
  path_group => qr/^[ \t]*Path Group:[ \t]*(\S+)/m,
  path_type  => qr/^[ \t]*Path Type:[ \t]*(\S+)/m,
  slack      => qr/^[ \t]*slack[ \t]*\((MET|VIOLATED)\)[ \t]*(-?[\d.]+|INFINITY)/m,
);

# a report holds many paths; split on the field that starts one
open my $fh, "<", $RPT or die "$RPT: $!";
local $/;
my $text = <$fh>;
close $fh;
# NOTE the character class. `\s*` MATCHES NEWLINES, so /^\s*Startpoint:/m
# also matches at the blank line BEFORE each header -- which split on a
# lookahead turns into a phantom empty block between every real one. My
# first version reported 7 blocks where there are 4. Use [ \t]* for
# "leading whitespace on THIS line".
my @blocks = split /(?=^[ \t]*Startpoint:)/m, $text;
shift @blocks while @blocks && $blocks[0] !~ /Startpoint:/;

printf "%s: %d path block(s)\n\n", $RPT, scalar @blocks;
printf "%-10s %-10s %-11s %-10s %-9s %s\n",
    "START", "END", "PATH GROUP", "PATH TYPE", "ANNOT", "SLACK";
my $complete = 0;
for my $b (@blocks) {
    my %v;
    for my $k (keys %FIELD) {
        if ($k eq "slack") { my ($a2, $s) = $b =~ $FIELD{$k}; $v{annot} = $a2; $v{slack} = $s }
        else { ($v{$k}) = $b =~ $FIELD{$k} }
    }
    my @missing = grep { !defined $v{$_} } qw(startpoint endpoint path_group path_type annot slack);
    $complete++ unless @missing;
    printf "%-10s %-10s %-11s %-10s %-9s %s%s\n",
        ($v{startpoint} // "-"), ($v{endpoint} // "-"), ($v{path_group} // "-"),
        ($v{path_type} // "-"), ($v{annot} // "-"), ($v{slack} // "-"),
        (@missing ? "   MISSING: @missing" : "");
}
printf "\n%d of %d blocks carry all six identifying fields\n", $complete, scalar @blocks;

# ---- the arithmetic, which the report states and a parser can CHECK
print "\nthe report states its own arithmetic; verify it rather than trust it:\n";
printf "%-10s %-10s %-10s %-10s %s\n", "END", "REQUIRED", "ARRIVAL", "SLACK", "req-arr == slack?";
for my $b (@blocks) {
    my ($ep) = $b =~ /^[ \t]*Endpoint:[ \t]*(\S+)/m;
    next unless defined $ep;
    my ($req) = $b =~ /^[ \t]*data required time[ \t]+([\d.]+)[ \t]*$/m;
    my ($arr) = $b =~ /^[ \t]*data arrival time[ \t]+(-[\d.]+)[ \t]*$/m;
    my (undef, $slack) = $b =~ /^[ \t]*slack[ \t]*\((MET|VIOLATED)\)[ \t]*(-?[\d.]+)/m;
    next unless defined $req && defined $arr && defined $slack;
    my $calc = $req + $arr;                 # $arr is printed NEGATIVE here
    printf "%-10s %-10s %-10s %-10s %s (%.4f)\n", $ep, $req, $arr, $slack,
        (abs($calc - $slack) < 5e-5 ? "yes" : "NO"), $calc;
}

# ---- what a path block does NOT tell you
print "\nwhat the block does not say:\n";
my ($corner) = $text =~ /^Corner\s*:\s*(\S+)/m;
printf "  the CORNER is in the report header (%s), not in any path block\n",
    ($corner // "absent");
print "  so a slack number carries its endpoint but NOT its corner -- which\n";
print "  is why 23.9 keys the store on BOTH, and why concatenating two\n";
print "  reports loses the distinction silently.\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
reports/typ_1v00_25c.pt.rpt: 4 path block(s)

START      END        PATH GROUP  PATH TYPE  ANNOT     SLACK
bit0/Q     bit0/D     clk         max        MET       0.7290
bit0/Q     bit1/D     clk         max        MET       0.6530
bit1/Q     bit2/D     clk         max        MET       0.5800
bit1/Q     bit3/D     clk         max        MET       0.5070

4 of 4 blocks carry all six identifying fields

the report states its own arithmetic; verify it rather than trust it:
END        REQUIRED   ARRIVAL    SLACK      req-arr == slack?
bit0/D     0.9050     -0.1760    0.7290     yes (0.7290)
bit1/D     0.9050     -0.2520    0.6530     yes (0.6530)
bit2/D     0.9050     -0.3250    0.5800     yes (0.5800)
bit3/D     0.9050     -0.3980    0.5070     yes (0.5070)

what the block does not say:
  the CORNER is in the report header (typ_1v00_25c), not in any path block
  so a slack number carries its endpoint but NOT its corner -- which
  is why 23.9 keys the store on BOTH, and why concatenating two
  reports loses the distinction silently.

Four paths, and all four carry all six identifying fields. The slacks differ — 0.7290 down to 0.5070 — because the tracer followed real logic: bit0/D is one inverter from its launching flop and bit3/D is two AND gates and an XOR.

2. The Shape Of A Path Block

Figure 23.1.1 — What a path block carries, and where

A path block has four identifying header fields (startpoint, endpoint, path group, path type), then a data arrival table built from cell delays, then a data required table built from the clock period minus setup and uncertainty, then a slack line that is their difference. The corner is in the report header, outside all path blocks.Report headerdesign, and the CORNERStartpoint, EndpointPath Group, Path Typedata arrivalclk_to_Q + cell chaindata requiredperiod - setup - uncslack (MET)required minus arrivalthe dashboard keyendpoint AND corner12
The header fields identify the path and are what a dashboard keys on. The two tables beneath them are the derivation — a data arrival built up cell by cell, and a data required time built down from the clock period — and the slack line is their difference. The corner sits outside every block, in the report header, which is the one fact about this layout that changes how a multi-corner parser has to be written.

The parse is therefore two steps: split into blocks, then pull fields from each block. Splitting on the field that starts a block is the idiom:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my @blocks = split /(?=^[ \t]*Startpoint:)/m, $text;

A lookahead split keeps the delimiter at the head of the following piece, so each block still contains its own Startpoint: line — which is what makes the per-block patterns uniform.

3. Verify The Report's Own Arithmetic

A timing report states its derivation, which means a parser can check itself against the file it just read:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
END      REQUIRED  ARRIVAL   SLACK    req-arr == slack?
bit0/D   0.9050    -0.1760   0.7290   yes (0.7290)
bit1/D   0.9050    -0.2520   0.6530   yes (0.6530)
bit2/D   0.9050    -0.3250   0.5800   yes (0.5800)
bit3/D   0.9050    -0.3980   0.5070   yes (0.5070)

All four agree. The arrival time is printed negative in the summary block — that is the tool subtracting it — so the check is required + arrival == slack rather than a subtraction, and getting that sign wrong makes every row fail.

This is the same free cross-check Chapter 21.7 found in UVM's severity counts and Chapter 22.1 found across three synthesis reports. It is worth building for the same reason: it is the only check available that does not come from your own assumptions. Three numbers printed by the tool, one relationship between them, and a parser that reads all three can confirm it read them correctly.

Two notes on doing it reliably:

Compare with a tolerance, not ==. These are decimal strings parsed into floats; abs($calc - $slack) < 5e-5 against values printed to four places. Exact equality on floats will fail on a correct report eventually.

A failure means a parse error, not a tool error. If the three numbers do not reconcile, the overwhelmingly likely cause is that one of your three patterns matched the wrong line — there are two data required time lines in a PrimeTime block, and Chapter 23.6 is about exactly that kind of trap.

4. What The Block Does Not Say

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
the CORNER is in the report header (typ_1v00_25c), not in any path block

A slack number carries its endpoint but not its corner. That one fact shapes the rest of the module:

A parser must read the header before the blocks, and attach the corner to every path it extracts. A per-path pattern alone cannot recover it.

Concatenating two reports destroys the distinction silently. cat typ.rpt slow.rpt > all.rpt produces a file where bit3/D appears twice with different slacks and nothing says which corner each came from. The blocks are all still well-formed, so no parser notices.

The dashboard key is the pair. Chapter 23.9 stores slack in a two-level hash keyed on endpoint and corner for this reason, and Chapter 23.4 is about capturing the fields that make a number attributable in the first place.

The general form of this is worth carrying: when a record's identity is partly in its container, the parser's first job is to read the container. Chapter 22.4 met the same shape — a hierarchy row's identity was its path, not its name — and the failure mode is identical: records that look complete, are not, and merge.

5. What To Remember

  • A timing report is the most structured text in the flow — a repeated block with named fields.
  • No STA tool exists here, so the reports are generated with pin directions from the library, topology from the real netlist, and a labelled delay table — every number derivable.
  • Measured: 4 path blocks, 6 identifying fields each, all present.
  • The slacks differ because the tracer followed real logic — one inverter versus two ANDs and an XOR.
  • Parse in two steps: split into blocks, then pull fields per block.
  • Split on a lookahead at the field that STARTS a block, so each block keeps its own header line.
  • \s matches a newline. /^\s*Startpoint:/m matched twice per header and produced 7 blocks where 4 exist.
  • Use [ \t]* for "leading whitespace on this line" in any /m-anchored pattern (4.3).
  • The failure presented as a malformed report, not a malformed regex — which is why it was worth chasing.
  • A timing report states its own arithmetic: verify it. Measured, all 4 paths reconcile.
  • The arrival is printed NEGATIVE in the summary, so the check is required + arrival == slack.
  • Compare with a tolerance, not ==, on decimal strings parsed to floats.
  • A reconciliation failure means YOUR parse is wrong — there are two data required time lines in a block (23.6).
  • The corner is in the report header, not in any path block.
  • Read the header before the blocks and attach the corner to every path.
  • Concatenating two reports destroys the corner distinction silently — every block stays well-formed.
  • The dashboard key is endpoint AND corner (23.9).
  • When a record's identity is partly in its container, read the container first — 22.4's hierarchy rows had the same shape.

Chapter 23.2 takes the single most important field. Capturing a slack value sounds like one pattern, and the chapter measures the four forms that pattern meets in practice — including one that is not a number at all.

Where this fits

Part of the Perl curriculum.