Skip to content
VLSI Mentor

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

PrimeTime Report Formats

Everything so far in this module has used the header fields, which are the easy part. The path table underneath them is where a generic parser fails, and it fails completely rather than partially: the point names contain spaces, so a pattern built on whitespace-delimited tokens reads nothing at all. This chapter measures three readings of the same table, each handling one more of the format's properties, and then establishes what the two numeric columns actually mean — because one of them restarts partway through every block.

Advanced11 min readPerlPart IISTA Reports

Part II · Module 23 · Chapter 23.6 · PrimeTime Report Formats

1. Three Readings, Two Reports

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

# slow_0v90_125c.pt.rpt is generated from the real netlist. split_layout is
# the same report with long point names WRAPPED onto their own line, which is
# what PrimeTime does by default and what -nosplit suppresses.
my @RPTS = ("reports/slow_0v90_125c.pt.rpt", "reports/split_layout.pt.rpt");

# ---- A: a TOKEN reading. The point name is taken as one \S+ run.
my $A = qr/^[ \t]+(\S+)[ \t]+(-?[\d.]+)[ \t]+(-?[\d.]+)[ \t]*\w?[ \t]*$/;

# ---- B: a COLUMN reading. The name is everything up to the run of spaces
# that precedes the first number, so it may contain spaces itself.
my $B = qr/^[ \t]+(\S.*?)[ \t]{2,}(-?[\d.]+)[ \t]+(-?[\d.]+)[ \t]*\w?[ \t]*$/;
# ---- and the wrapped form: a name alone, numbers on the next line
my $NAME_ONLY = qr/^[ \t]+(\S.*?)[ \t]*$/;
my $NUMS_ONLY = qr/^[ \t]{20,}(-?[\d.]+)[ \t]+(-?[\d.]+)[ \t]*\w?[ \t]*$/;

my $SKIP = qr/^[ \t]*(?:\*|Report|Design|Corner|Generated|\(no STA|-+[ \t]*$|$
            |Startpoint|Endpoint|Path Group|Path Type|Point[ \t]|\(rising|\(falling
            |slack[ \t]*\(|[0-9]+[ \t]*$)/x;

sub parse {
    my ($file, $mode) = @_;
    open my $fh, "<", $file or die "$file: $!";
    my (@pts, $pending);
    while (my $l = <$fh>) {
        chomp $l;
        next if $l =~ $SKIP;
        if ($mode eq "wrap") {
            if (defined $pending && $l =~ $NUMS_ONLY) {
                push @pts, { name => $pending, incr => $1, path => $2 };
                undef $pending; next;
            }
            if ($l !~ $B && $l =~ $NAME_ONLY && $1 !~ /^-?[\d.]+/) { $pending = $1; next }
        }
        my $pat = ($mode eq "token") ? $A : $B;
        next unless $l =~ $pat;
        push @pts, { name => $1, incr => $2, path => $3 };
    }
    close $fh;
    return @pts;
}

printf "%-18s %-11s %-11s %-11s %s\n",
    "REPORT", "TOKEN", "COLUMN", "COL+WRAP", "note";
for my $r (@RPTS) {
    my ($short) = $r =~ m{([^/]+)\.pt\.rpt$};
    my @t = parse($r, "token");
    my @c = parse($r, "column");
    my @w = parse($r, "wrap");
    printf "%-18s %-11d %-11d %-11d %s\n", $short, scalar @t, scalar @c, scalar @w,
        (@t == 0 ? "TOKEN reads nothing" : "");
}

print "\nwhy the TOKEN reading finds nothing:\n";
open my $fh, "<", "reports/slow_0v90_125c.pt.rpt" or die $!;
my @sample;
while (my $l = <$fh>) { chomp $l; push @sample, $l if $l =~ $B && @sample < 3 }
close $fh;
for my $l (@sample) {
    my ($name) = $l =~ $B;
    printf "  the point name is %-28s (%d space-separated words)\n",
        "'" . $name . "'", scalar split(/\s+/, $name);
}
print "  so (\\S+) captures only the first word and the pattern then fails to\n";
print "  find two numbers after it. PrimeTime's layout is COLUMNS, not tokens.\n";

print "\nthe wrapped report, point by point:\n";
my %tok = map { $_->{name} => 1 } parse("reports/split_layout.pt.rpt", "column");
for my $p (parse("reports/split_layout.pt.rpt", "wrap")) {
    printf "  %-40s %8s %8s %s\n", $p->{name}, $p->{incr}, $p->{path},
        ($tok{ $p->{name} } ? "" : "<- only the wrap-aware reading finds this");
}

print "\nthe two numeric columns are NOT independent:\n";
my @pts = parse("reports/slow_0v90_125c.pt.rpt", "column");
# The Path column RESTARTS at each section: the arrival derivation and the
# required derivation each begin again at "clock ... (rise edge)". So a
# cumulative check must reset there, and the first version of this
# measurement -- which summed straight through -- diverged at every
# boundary and looked like a report inconsistency.
my ($run, $sections, $rows, $agree) = (0, 0, 0, 0);
printf "%-32s %9s %9s %s\n", "POINT", "INCR", "PATH", "sum within section";
for my $p (@pts[0 .. 9]) {
    if ($p->{name} =~ /^clock \S+ \(rise edge\)/) { $run = 0; $sections++ }
    $run += $p->{incr};
    $rows++;
    my $ok = abs($run - $p->{path}) < 5e-5;
    $agree++ if $ok;
    printf "%-32s %9s %9s %9.4f%s\n", $p->{name}, $p->{incr}, $p->{path}, $run,
        ($ok ? "" : "  <- diverged");
}
# and over the whole report, not just the first ten rows
($run, $rows, $agree) = (0, 0, 0);
for my $p (@pts) {
    $run = 0 if $p->{name} =~ /^clock \S+ \(rise edge\)/;
    $run += $p->{incr};
    $rows++;
    $agree++ if abs($run - $p->{path}) < 5e-5;
}
printf "\n  resetting at each section: %d of %d rows reconcile\n", $agree, $rows;
print "  Path is the CUMULATIVE total WITHIN a section; Incr is this point's\n";
print "  own contribution. Summing Path instead of Incr double-counts every\n";
print "  step -- the same inclusive-versus-exclusive trap 22.4 measured on a\n";
print "  hierarchy roll-up -- and summing Incr ACROSS sections is the mirror\n";
print "  mistake, which is what my first version of this check did.\n";

print "\nand how many lines in one block say 'data required time'?\n";
open my $f2, "<", "reports/slow_0v90_125c.pt.rpt" or die $!;
local $/;
my $text = <$f2>;
close $f2;
my ($blk) = (split /(?=^[ \t]*Startpoint:)/m, $text)[1];
my @req = ($blk =~ /^[ \t]*data required time[ \t]+.*$/mg);
printf "  %d, and they are different lines:\n", scalar @req;
printf "    [%s]\n", $_ for @req;
print "  the first ends the required-time derivation, the second is the\n";
print "  summary's operand. A pattern taking the FIRST match gets a number\n";
print "  that is equal here and need not be; 23.1's reconciliation catches it.\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
REPORT             TOKEN       COLUMN      COL+WRAP    note
slow_0v90_125c     0           39          39          TOKEN reads nothing
split_layout       0           0           39          TOKEN reads nothing

why the TOKEN reading finds nothing:
  the point name is 'clock clk (rise edge)'      (4 space-separated words)
  the point name is 'clock network delay (ideal)' (4 space-separated words)
  the point name is 'bit0/Q (DFCNQD1)'           (2 space-separated words)
  so (\S+) captures only the first word and the pattern then fails to
  find two numbers after it. PrimeTime's layout is COLUMNS, not tokens.

the wrapped report, point by point:
  core0/clock clk (rise edge)                0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/clock network delay (ideal)          0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/bit0/Q (DFCNQD1)                     0.3350   0.3350 <- only the wrap-aware reading finds this
  core0/U20/n11 (INVD1)                      0.1050   0.4400 <- only the wrap-aware reading finds this
  core0/data arrival time                    0.0000   0.4400 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                1.0000   1.0000 <- only the wrap-aware reading finds this
  core0/clock uncertainty                   -0.0300   0.9700 <- only the wrap-aware reading finds this
  core0/library setup time                  -0.0650   0.9050 <- only the wrap-aware reading finds this
  core0/data required time                   0.0000   0.9050 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/clock network delay (ideal)          0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/bit0/Q (DFCNQD1)                     0.3350   0.3350 <- only the wrap-aware reading finds this
  core0/U21/n12 (XOR2D1)                     0.2950   0.6300 <- only the wrap-aware reading finds this
  core0/data arrival time                    0.0000   0.6300 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                1.0000   1.0000 <- only the wrap-aware reading finds this
  core0/clock uncertainty                   -0.0300   0.9700 <- only the wrap-aware reading finds this
  core0/library setup time                  -0.0650   0.9050 <- only the wrap-aware reading finds this
  core0/data required time                   0.0000   0.9050 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/clock network delay (ideal)          0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/bit1/Q (DFCNQD1)                     0.3350   0.3350 <- only the wrap-aware reading finds this
  core0/U22/n13 (AN2D1)                      0.1825   0.5175 <- only the wrap-aware reading finds this
  core0/U23/n14 (XOR2D1)                     0.2950   0.8125 <- only the wrap-aware reading finds this
  core0/data arrival time                    0.0000   0.8125 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                1.0000   1.0000 <- only the wrap-aware reading finds this
  core0/clock uncertainty                   -0.0300   0.9700 <- only the wrap-aware reading finds this
  core0/library setup time                  -0.0650   0.9050 <- only the wrap-aware reading finds this
  core0/data required time                   0.0000   0.9050 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/clock network delay (ideal)          0.0000   0.0000 <- only the wrap-aware reading finds this
  core0/bit1/Q (DFCNQD1)                     0.3350   0.3350 <- only the wrap-aware reading finds this
  core0/U22/n13 (AN2D1)                      0.1825   0.5175 <- only the wrap-aware reading finds this
  core0/U24/n15 (AN2D1)                      0.1825   0.7000 <- only the wrap-aware reading finds this
  core0/U25/n16 (XOR2D1)                     0.2950   0.9950 <- only the wrap-aware reading finds this
  core0/data arrival time                    0.0000   0.9950 <- only the wrap-aware reading finds this
  core0/clock clk (rise edge)                1.0000   1.0000 <- only the wrap-aware reading finds this
  core0/clock uncertainty                   -0.0300   0.9700 <- only the wrap-aware reading finds this
  core0/library setup time                  -0.0650   0.9050 <- only the wrap-aware reading finds this
  core0/data required time                   0.0000   0.9050 <- only the wrap-aware reading finds this

the two numeric columns are NOT independent:
POINT                                 INCR      PATH sum within section
clock clk (rise edge)               0.0000    0.0000    0.0000
clock network delay (ideal)         0.0000    0.0000    0.0000
bit0/Q (DFCNQD1)                    0.3350    0.3350    0.3350
U20/n11 (INVD1)                     0.1050    0.4400    0.4400
data arrival time                   0.0000    0.4400    0.4400
clock clk (rise edge)               1.0000    1.0000    1.0000
clock uncertainty                  -0.0300    0.9700    0.9700
library setup time                 -0.0650    0.9050    0.9050
data required time                  0.0000    0.9050    0.9050
clock clk (rise edge)               0.0000    0.0000    0.0000

  resetting at each section: 39 of 39 rows reconcile
  Path is the CUMULATIVE total WITHIN a section; Incr is this point's
  own contribution. Summing Path instead of Incr double-counts every
  step -- the same inclusive-versus-exclusive trap 22.4 measured on a
  hierarchy roll-up -- and summing Incr ACROSS sections is the mirror
  mistake, which is what my first version of this check did.

and how many lines in one block say 'data required time'?
  2, and they are different lines:
    [  data required time                       0.0000   0.9050 ]
    [  data required time                                0.9050 ]
  the first ends the required-time derivation, the second is the
  summary's operand. A pattern taking the FIRST match gets a number
  that is equal here and need not be; 23.1's reconciliation catches it.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
REPORT           TOKEN  COLUMN  COL+WRAP
slow_0v90_125c   0      39      39
split_layout     0      0       39

A three-rung ladder, and each rung handles exactly one more property of the format. The token reading finds nothing at all; the column reading finds everything until the names wrap; only the wrap-aware reading handles both files.

2. A Point Name Contains Spaces

This is the one that makes the token reading find zero rather than most:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'clock clk (rise edge)'        4 space-separated words
'clock network delay (ideal)'  4 words
'bit0/Q (DFCNQD1)'             2 words

Every point name in the table has a space in it. The instance names carry their cell type in parentheses; the clock rows are English phrases.

So /^[ \t]+(\S+)[ \t]+(-?[\d.]+)[ \t]+(-?[\d.]+)/ captures bit0/Q, then requires a number, finds (DFCNQD1), and fails. Not for some rows — for all of them, because the format is consistent about putting the cell type there.

The column reading says what the layout actually is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
my $B = qr/^[ \t]+(\S.*?)[ \t]{2,}(-?[\d.]+)[ \t]+(-?[\d.]+)[ \t]*\w?[ \t]*$/;

(\S.*?) is non-greedy (Chapter 11.7), bounded by [ \t]{2,} — a run of two or more spaces, which is the column separator. One space is inside a name; two or more is a gap between columns. That single distinction is the whole parse.

Chapter 23.4 reached the same conclusion about header fields and Chapter 22.3 about cell type names: capture by position or by delimiter, not by assuming the field is one token. The recurring error is that \S+ reads like "a field" and means "a run of non-space characters".

3. Incr, Path, And The Section Restart

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
resetting at each section: 39 of 39 rows reconcile

The two numeric columns are not independent:

Incr is this point's own contribution.

Path is the running total — but only within a section, and a block contains several sections. The measured table shows the restart:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
POINT                            INCR      PATH   sum within section
clock clk (rise edge)          0.0000    0.0000   0.0000
bit0/Q (DFCNQD1)               0.3350    0.3350   0.3350
U20/n11 (INVD1)                0.1050    0.4400   0.4400
data arrival time              0.0000    0.4400   0.4400
clock clk (rise edge)          1.0000    1.0000   1.0000   <- restarted
clock uncertainty             -0.0300    0.9700   0.9700
library setup time            -0.0650    0.9050   0.9050

The arrival derivation ends, and the required derivation begins again from the clock edge. So there are two mistakes available and they are mirror images:

Summing Path instead of Incr double-counts every step, because Path already contains everything before it. This is precisely Chapter 22.4's inclusive-roll-up trap in a different file.

Summing Incr across sections adds the required-time derivation onto the arrival, which is what my first version of this check did. It diverged at every section boundary and the output looked like the report was inconsistent — a misattribution, not just a wrong number.

Resetting at each clock ... (rise edge) line makes all 39 rows reconcile, which is both the correct reading and a cheap proof that the parse is right.

4. Two Lines Say "data required time"

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2, and they are different lines:
  [  data required time                       0.0000   0.9050 ]
  [  data required time                                0.9050 ]

One block, two lines with the same label. The first ends the required-time derivation and has both columns; the second is the summary's operand and has only the total.

A pattern that takes the first match gets 0.9050 and a pattern that takes the last gets 0.9050, so it works — here. The two numbers are equal in this report because the derivation's end is the summary's operand.

They need not be. A block with a path-specific adjustment between the two sections, or a tool version that prints an intermediate, breaks the coincidence — and nothing in the output would show it.

Two defences, and the second is the one that generalises:

Anchor on the column count, not just the label. The summary line has one number; the derivation line has two. /^[ \t]*data required time[ \t]+[\d.-]+[ \t]+([\d.-]+)/ takes the derivation line specifically.

Reconcile, as Chapter 23.1 established. required + arrival == slack catches a wrong required immediately, whichever line it came from. That check costs three patterns and it is the only thing in this chapter that would notice the coincidence breaking.

5. What To Remember

  • The header fields are the easy part; the path table is where a generic parser fails.
  • Measured: three readings — token 0 of 39, column 39 then 0, wrap-aware 39 and 39.
  • Every point name contains a space — the cell type in parentheses, or an English phrase.
  • So (\S+) reads ZERO rows, not most of them — the format is consistent about it.
  • The column separator is two or more spaces: (\S.*?)[ \t]{2,} with a non-greedy name (11.7).
  • One space is inside a name; two is a gap — that single distinction is the whole parse.
  • Capture by delimiter or position, not by assuming one token — 22.3 and 23.4 reached this independently.
  • Incr is this point's contribution; Path is the running total within a SECTION.
  • A block has several sections, and Path restarts at each clock ... (rise edge).
  • Summing Path double-counts every step — 22.4's inclusive trap.
  • Summing Incr across sections is the mirror mistake, and it made my first check look like a report inconsistency.
  • Resetting per section: 39 of 39 rows reconcile — correct, and a free proof the parse is right.
  • PrimeTime wraps long point names by default; -nosplit suppresses it and you cannot assume it was used.
  • Once one name wraps, the column reading gets zero — every following numbers-only line fails too.
  • Handle both with one state variable (18.6, 22.3), and test the complete-row pattern first.
  • Two lines in a block say data required time — one with two columns, one with one.
  • They are equal here and need not be. Anchor on the column count, and reconcile (23.1).

Chapter 23.7 points the same questions at the other tool. Tempus describes the same timing in a different layout with different field names, and the useful output is a concrete list of the differences rather than a second parser nobody maintains.

Where this fits

Part of the Perl curriculum.