Perl Part II · Module 22 · Synthesis Report Parsing
Parsing report_area
An area report holds three numbers anybody wants and they sit on three labelled lines, so the parser looks like four lines of Perl and is finished. The two things that break it are both measurable: the labels are not the same in every tool or every release, and the arithmetic relationship between the three numbers that everybody assumes holds does not hold — on a perfectly correct report, for a reason worth knowing.
Intermediate13 min readPerlPart IISynthesis Reports
Part II · Module 22 · Chapter 22.2 · Parsing report_area
1. Four Reports, One Set Of Numbers
All four reports below carry the same three numbers. Only the labels differ — which means any disagreement between them is the parser's fault, not the data's.
use strict;
use warnings;
# Four area reports carrying the SAME three numbers under different labels.
# counter_syn.area.rpt is generated from the real netlist; variants b/c/d
# are the same numbers relabelled, so any disagreement is the PARSER's.
my @REPORTS = qw(
reports/counter_syn.area.rpt
reports/variant_b.area.rpt
reports/variant_c.area.rpt
reports/variant_d.area.rpt
);
my %TRUTH = (total => 34.580000, comb => 12.502000, seq => 21.280000);
# ---- A: one spelling, the one the first report happens to use
my %A = (
total => qr/^\s*Total cell area:\s+([\d.]+)/m,
comb => qr/^\s*Combinational area:\s+([\d.]+)/m,
seq => qr/^\s*Noncombinational area:\s+([\d.]+)/m,
);
# ---- B: a table of accepted spellings per field (18.8), each labelled
my %B = (
total => [
[ "Total cell area", qr/^\s*Total cell area:\s+([\d.]+)/m ],
[ "Total Area (cells)",qr/^\s*Total Area \(cells\)\s*:\s*([\d.]+)/m ],
[ "cell_area_total", qr/^\s*cell_area_total\s*=\s*([\d.]+)/m ],
],
comb => [
[ "Combinational area", qr/^\s*Combinational area:\s+([\d.]+)/m ],
[ "Combinational Area", qr/^\s*Combinational Area\s*:\s*([\d.]+)/m ],
[ "area_combinational", qr/^\s*area_combinational\s*=\s*([\d.]+)/m ],
],
seq => [
[ "Noncombinational area", qr/^\s*Noncombinational area:\s+([\d.]+)/m ],
[ "Sequential Area", qr/^\s*Sequential Area\s*:\s*([\d.]+)/m ],
[ "area_sequential", qr/^\s*area_sequential\s*=\s*([\d.]+)/m ],
],
);
sub slurp { open my $fh, "<", $_[0] or die "$_[0]: $!"; local $/; my $t = <$fh>; close $fh; return $t }
sub read_a {
my ($text) = @_;
my %v;
for my $k (keys %A) { ($v{$k}) = $text =~ $A{$k} }
return %v;
}
sub read_b {
my ($text) = @_;
my (%v, %via);
for my $k (keys %B) {
for my $cand (@{ $B{$k} }) {
my ($x) = $text =~ $cand->[1];
if (defined $x) { $v{$k} = $x; $via{$k} = $cand->[0]; last }
}
}
return (\%v, \%via);
}
printf "%-26s %-26s %s\n", "REPORT", "A (one spelling)", "B (a table of spellings)";
for my $r (@REPORTS) {
my ($short) = $r =~ m{([^/]+)\.area\.rpt$};
my %a = read_a(slurp($r));
my ($b, $via) = read_b(slurp($r));
my $a_ok = (defined $a{total} && defined $a{comb} && defined $a{seq}) ? "3 of 3" :
sprintf("%d of 3", scalar grep { defined $a{$_} } qw(total comb seq));
my $b_ok = sprintf("%d of 3", scalar grep { defined $b->{$_} } qw(total comb seq));
printf "%-26s %-26s %s%s\n", $short, $a_ok, $b_ok,
(defined $via->{total} ? " (total via '$via->{total}')" : " -- NO spelling matched");
}
print "\ndo the values match the truth where they were found?\n";
for my $r (@REPORTS) {
my ($short) = $r =~ m{([^/]+)\.area\.rpt$};
my ($b) = read_b(slurp($r));
my @bad = grep { defined $b->{$_} && abs($b->{$_} - $TRUTH{$_}) > 1e-9 } sort keys %TRUTH;
printf " %-26s %s\n", $short,
(@bad ? "MISMATCH on " . join(",", @bad)
: (grep { defined $b->{$_} } keys %TRUTH) ? "all found values correct"
: "nothing found");
}
print "\nthe unrecognised report must be an ERROR, not a zero (18.8):\n";
my ($d) = read_b(slurp("reports/variant_d.area.rpt"));
if (!defined $d->{total}) {
print " variant_d: no spelling of 'total' matched. Tried:\n";
printf " %s\n", $_->[0] for @{ $B{total} };
print " reporting 0 here would publish an area of zero for a real design.\n";
}
# ---- the internal consistency check the report itself allows
print "\nthe report's own arithmetic, as a check on the parse:\n";
my ($one) = read_b(slurp("reports/counter_syn.area.rpt"));
printf " comb %.3f + seq %.3f = %.3f ; total says %.3f ; agree? %s\n",
$one->{comb}, $one->{seq}, $one->{comb} + $one->{seq}, $one->{total},
(abs($one->{comb} + $one->{seq} - $one->{total}) < 1e-9 ? "YES" : "NO");
printf " the difference is %.3f\n", $one->{total} - $one->{comb} - $one->{seq};
# Is that difference REALLY the physical cells? Check it against the cells
# report and the area table rather than asserting it.
my %unit;
open my $al, "<", "lib/areas.txt" or die $!;
while (my $l = <$al>) { next if $l =~ /^\s*#/; $unit{$1} = $2 if $l =~ /^(\w+)\s+([\d.]+)/ }
close $al;
my %PHYSICAL = map { $_ => 1 } qw(FILL1 TIEHI TIELO ANTENNA DCAP);
my $phys = 0;
my $cells = slurp("reports/counter_syn.cells.rpt");
while ($cells =~ /^(\w+)\s+\S+\s+([\d.]+)\s+(\d+)\s/mg) {
my ($type, $ua, $n) = ($1, $2, $3);
$phys += $ua * $n if $PHYSICAL{$type};
}
printf " physical-only area, summed from the cells report: %.3f\n", $phys;
printf " does it account for the difference exactly? %s\n",
(abs($phys - ($one->{total} - $one->{comb} - $one->{seq})) < 1e-9 ? "YES" : "no");
print " so the obvious invariant comb+seq==total is FALSE on a correct report,\n";
print " and the residue is a quantity worth reporting rather than an error.\n";REPORT A (one spelling) B (a table of spellings)
counter_syn 3 of 3 3 of 3 (total via 'Total cell area')
variant_b 0 of 3 3 of 3 (total via 'Total Area (cells)')
variant_c 0 of 3 3 of 3 (total via 'cell_area_total')
variant_d 0 of 3 0 of 3 -- NO spelling matched
do the values match the truth where they were found?
counter_syn all found values correct
variant_b all found values correct
variant_c all found values correct
variant_d nothing found
the unrecognised report must be an ERROR, not a zero (18.8):
variant_d: no spelling of 'total' matched. Tried:
Total cell area
Total Area (cells)
cell_area_total
reporting 0 here would publish an area of zero for a real design.
the report's own arithmetic, as a check on the parse:
comb 12.502 + seq 21.280 = 33.782 ; total says 34.580 ; agree? NO
the difference is 0.798
physical-only area, summed from the cells report: 0.798
does it account for the difference exactly? YES
so the obvious invariant comb+seq==total is FALSE on a correct report,
and the residue is a quantity worth reporting rather than an error.The single-spelling parser gets 0 of 3 on two of the four reports. Not two of three fields — none of them. And 0 of 3 is what a silent total failure looks like: three variables left undef, no error, no warning.
Chapter 18.8 measured this same shape on a tool-version field rename, and the consequence is the same: undef becomes 0 in arithmetic, and an area of zero is a plausible number that a dashboard will display.
2. The Spellings Table
The fix is Chapter 18.8's structure — a list of accepted spellings per field, each with a human label:
my %B = (
total => [
[ "Total cell area", qr/^\s*Total cell area:\s+([\d.]+)/m ],
[ "Total Area (cells)", qr/^\s*Total Area \(cells\)\s*:\s*([\d.]+)/m ],
[ "cell_area_total", qr/^\s*cell_area_total\s*=\s*([\d.]+)/m ],
],
...
);Chapter 7.7's hash of arrays, with a [label, pattern] pair in each slot. Four properties, and the second is the one people leave out:
One table per field, so a tool that renames only one label does not break the other two.
Each spelling keeps its label, and the parser reports which matched — total via 'Total Area (cells)' in the measured output. Chapter 18.8's argument: when a parser later finds nothing, knowing which forms it tried and which it has matched before is most of the diagnosis.
defined, not truth. A legitimate area of 0.000000 — an empty block, a report generated before mapping — is false. Chapter 2.7's distinction, and here it is not hypothetical: a report for a block that synthesised to nothing has exactly that.
The patterns are anchored and /m. ^\s* against a multiline string, so Total cell area: mentioned inside a comment or a nested report section does not match. The /m is what makes ^ mean "start of a line" rather than "start of the string".
3. Unrecognised Is Not Zero
variant_d is a report whose labels nobody has written a pattern for:
variant_d: no spelling of 'total' matched. Tried:
Total cell area
Total Area (cells)
cell_area_total
reporting 0 here would publish an area of zero for a real design.The tolerant parser returns nothing, and nothing is the correct answer. The three outcomes, exactly as Chapter 18.8 established them:
A number — a spelling matched.
A documented absence — the field is legitimately not in this report. An area report for a pre-mapping netlist has no cell area at all, and that is not an error.
Unrecognised — the field should be there and no spelling matched. This one has to be loud, and it has to name the spellings tried, because that list is what turns a one-hour investigation into a one-minute fix.
The exit status follows Chapter 15.4's third value: a report you could not parse is not "area grew" and not "area shrank", it is "I could not tell", and a QoR flow that maps it onto either of the first two reports a change that did not happen.
4. The Invariant That Is False
Now the finding that is worth the chapter. The three numbers look like they should add up, so the obvious self-check is comb + seq == total:
comb 12.502 + seq 21.280 = 33.782 ; total says 34.580 ; agree? NO
the difference is 0.798It does not add up, and the report is correct. The residue is the physical-only cells — a filler and a tie cell — which the tool counts in the total and in neither category, because they are neither combinational nor sequential logic.
And the script does not assert that; it checks it, by summing the physical-only cells independently out of the cells report (Chapter 20.5's family classification):
physical-only area, summed from the cells report: 0.798
does it account for the difference exactly? YESTwo things follow, and the second is the general lesson:
Do not write comb + seq == total as a validation. It would fire on every correct report that contains a single filler cell — which is every post-layout report in existence. Chapter 20.7 measured what a check that fires when nothing is wrong does to a report: it is not 25% wrong, it means nobody reads it.
Report the residue instead. total - comb - seq = 0.798 (physical-only cells) is a quantity worth printing. It is small on a good netlist, and a residue that is suddenly 15% of total area means something has appeared in the netlist that belongs in neither category — which is a real finding the strict check would have buried under false positives.
5. What To Remember
- Three numbers, three labelled lines — and the labels differ between tools and releases.
- Measured: a single-spelling parser found 0 of 3 fields on two of four reports.
0 of 3is a silent total failure — threeundefs, no error, andundefbecomes0in arithmetic (18.8).- Carry a table of accepted spellings per field — a hash of arrays with
[label, pattern]pairs (7.7, 18.8). - One table per field, so renaming one label does not break the others.
- Report which spelling matched — it is most of the diagnosis when a later report fails.
- Test
defined, not truth: an area of0.000000is legitimate for an empty block (2.7). - Anchor with
^\s*and use/mso a mention inside a comment or nested section does not match. - Slurp a report, stream a log (22.1, 13.3, 18.10) — two file types, two treatments, in one program.
- Unrecognised is a third outcome, not zero: a number, a documented absence, or unrecognised (18.8).
- Name the spellings you tried in the failure message, and exit 3 — "I could not tell" (15.4).
- Measured:
comb + seq != totalon a CORRECT report. The residue is 0.798. - The residue is the physical-only cells, verified by summing them independently from the cells report (20.5).
- Do not ship
comb + seq == totalas a check — it fires on every report containing a filler (20.7). - Report the residue as a quantity instead — small normally, and a sudden jump is a real finding.
- An invariant derived from reading labels is a hypothesis. Run it on data you trust and see whether it fires.
- A range check is weaker than an invariant, and weaker survives contact with real reports (18.8).
Chapter 22.3 takes the second file. The cells report has one row per cell type, which makes counting easy and makes the interesting question a different one: how to group a long list of types into the few families a reviewer actually asks about.
Where this fits
Part of the Perl curriculum.
