Perl Part II · Module 22 · Synthesis Report Parsing
Extracting Constraint and Check Warnings
The synthesis log is the one file in this module that is not a table, and it is where the tool says what it was unhappy about. Most of what it says is the same thing it said last week, repeated several times. The job is therefore not to collect warnings but to collapse them to one entry per condition and then split that list in two — the conditions that are always there, and the ones that turned up today, which are the only ones anybody needs to read.
Intermediate14 min readPerlPart IISynthesis Reports
Part II · Module 22 · Chapter 22.6 · Extracting Constraint and Check Warnings
1. Two Logs, Deduplicated By Code
use strict;
use warnings;
# Two synthesis logs generated by gen_log.pl from the same design, with
# different seeds, so some warnings appear in both and some only in one.
my $LINE = qr/^(Warning|Error|Information):\s*(.*?)\s*\((\w+-\d+)\)\s*$/;
sub read_log {
my ($file) = @_;
open my $fh, "<", $file or die "$file: $!";
my (%by_code, %sev, $total);
while (my $l = <$fh>) {
chomp $l;
next unless $l =~ $LINE;
my ($s, $text, $code) = ($1, $2, $3);
next if $s eq "Information"; # not a complaint
$total++;
$by_code{$code}{count}++;
$by_code{$code}{text} //= $text;
$sev{$code} = $s;
}
close $fh;
return (\%by_code, \%sev, $total);
}
# NOTE: these are deliberately NOT called $a and $b. Perl's sort block
# uses the globals $a and $b, so a hash ref named $a is shadowed inside
# every sort -- which printed HASH(0x...) as a "code" in my first run.
my ($run1, $sev1, $tot1) = read_log("reports/counter_syn.syn.log");
my ($run2, $sev2, $tot2) = read_log("reports/counter_syn_v2.syn.log");
printf "run 1: %d warning/error lines, %d distinct codes\n", $tot1, scalar keys %$run1;
printf "run 2: %d warning/error lines, %d distinct codes\n\n", $tot2, scalar keys %$run2;
print "deduplicated by code, run 1:\n";
printf "%-10s %-6s %-7s %s\n", "CODE", "SEV", "COUNT", "text";
for my $c (sort { $run1->{$b}{count} <=> $run1->{$a}{count} || $a cmp $b } keys %$run1) {
printf "%-10s %-6s %-7d %s\n", $c, $sev1->{$c}, $run1->{$c}{count}, $run1->{$c}{text};
}
printf " %d lines collapse to %d codes (%.1fx)\n",
$tot1, scalar keys %$run1, $tot1 / scalar keys %$run1;
# ---- always-present vs NEW
my %all = map { $_ => 1 } (keys %$run1, keys %$run2);
my (@both, @only_a, @only_b);
for my $c (sort keys %all) {
if ($run1->{$c} && $run2->{$c}) { push @both, $c }
elsif ($run1->{$c}) { push @only_a, $c }
else { push @only_b, $c }
}
print "\ncomparing the two runs:\n";
printf " in BOTH runs (background): %s\n", join(", ", @both);
printf " only in run 1 (went away): %s\n", (@only_a ? join(", ", @only_a) : "none");
printf " only in run 2 (NEW): %s\n", (@only_b ? join(", ", @only_b) : "none");
print "\nthe NEW ones, with their text -- this is the actionable list:\n";
for my $c (@only_b) {
printf " %-10s %-6s %s\n", $c, $sev2->{$c}, $run2->{$c}{text};
}
# ---- severity is in the line, and it is not the same as importance
print "\nseverity as the tool reports it, vs what needs attention:\n";
for my $c (sort keys %$run2) {
my $where = ($run1->{$c} ? "both runs" : "NEW");
printf " %-10s tool says %-8s appears in %-10s %s\n", $c, $sev2->{$c}, $where,
($sev2->{$c} eq "Error" ? "<- an Error, and NEW" : "");
}
print " an Error that appears in every run is a waived condition somebody\n";
print " already decided about; a Warning that is NEW is the one to read.\n";
print " So severity alone does not order the list -- novelty does (21.4).\n";
# ---- the count hazard
print "\nwhy deduplicate by code at all?\n";
my ($top) = sort { $run1->{$b}{count} <=> $run1->{$a}{count} } keys %$run1;
printf " %s appears %d times in run 1 out of %d lines (%.0f%% of the log's\n",
$top, $run1->{$top}{count}, $tot1, 100 * $run1->{$top}{count} / $tot1;
print " complaints). Without dedup it dominates any count-ordered report,\n";
print " and it is one condition, not several.\n";run 1: 6 warning/error lines, 3 distinct codes
run 2: 9 warning/error lines, 6 distinct codes
deduplicated by code, run 1:
CODE SEV COUNT text
OPT-170 Warning 3 Design 'counter_top' has unmapped cell 'FILL1'.
OPT-314 Warning 2 Disabling timing arc between pins 'E' and 'ECK' on cell 'clk_gate'.
TIM-134 Warning 1 The clock 'clk' has no defined uncertainty.
6 lines collapse to 3 codes (2.0x)
comparing the two runs:
in BOTH runs (background): OPT-170, OPT-314, TIM-134
only in run 1 (went away): none
only in run 2 (NEW): ELAB-974, TIM-052, UID-95
the NEW ones, with their text -- this is the actionable list:
ELAB-974 Warning Latch 'clk_gate/en' is inferred; check the enable.
TIM-052 Warning Port 'enable' is unconstrained for max_delay.
UID-95 Error Cannot find port 'scan_en' on design 'counter_top'.
severity as the tool reports it, vs what needs attention:
ELAB-974 tool says Warning appears in NEW
OPT-170 tool says Warning appears in both runs
OPT-314 tool says Warning appears in both runs
TIM-052 tool says Warning appears in NEW
TIM-134 tool says Warning appears in both runs
UID-95 tool says Error appears in NEW <- an Error, and NEW
an Error that appears in every run is a waived condition somebody
already decided about; a Warning that is NEW is the one to read.
So severity alone does not order the list -- novelty does (21.4).
why deduplicate by code at all?
OPT-170 appears 3 times in run 1 out of 6 lines (50% of the log's
complaints). Without dedup it dominates any count-ordered report,
and it is one condition, not several.Six complaint lines collapse to three conditions, and one of those three accounts for half the lines:
OPT-170 appears 3 times in run 1 out of 6 lines (50% of the log's complaints)Without deduplication, OPT-170 dominates any count-ordered report — while being one condition, not three. Chapter 21.4 measured the same shape for UVM warnings: an occurrence count answers a question nobody asked.
2. The Code Is The Identity
Every synthesis complaint carries a code — OPT-170, TIM-134, ELAB-974 — and that code, not the message text, is what identifies the condition:
my $LINE = qr/^(Warning|Error|Information):\s*(.*?)\s*\((\w+-\d+)\)\s*$/;Three reasons the code is the right key, and the third is the practical one:
The text varies, the code does not. has unmapped cell 'FILL1' and has unmapped cell 'FILL2' are two texts and one condition. Chapter 21.9 solved the same problem for UVM errors by normalising values out of the text; here the tool has already done it for you by assigning a code.
The code is stable across versions. Message wording gets rewritten between releases; codes are part of the tool's documented interface, which is exactly what Chapter 18.8's spelling tables were working around the absence of.
The code is searchable. OPT-170 pasted into a vendor's documentation gives the explanation and the recommended action, which a paraphrase of the text does not.
Information: lines are dropped at the input — they are not complaints, and keeping them means the "warning count" includes the tool announcing which library it loaded.
3. Always-Present Versus New
The split is a set comparison over the two runs' codes — Chapter 6.9's hash-as-set:
in BOTH runs (background): OPT-170, OPT-314, TIM-134
only in run 1 (went away): none
only in run 2 (NEW): ELAB-974, TIM-052, UID-95Three codes are background and three are new, and the three new ones are the entire actionable list. The script prints their text, because for a new condition the text is what somebody needs; for a background condition it is noise they have read before.
The three categories each mean something different:
In both runs — a standing condition. Either waive it explicitly in a list somebody owns, or fix it. What it must not be is reported every day, because that is how the whole report becomes unread.
Only in the old run — it went away. Usually good, occasionally a sign that a check stopped running rather than stopped firing, which is worth one line of output.
Only in the new run — the actionable list. This is what a QoR review should open with.
That requires keeping the previous run's codes, which makes this chapter's output the same shape as Chapter 22.5's delta and Chapter 20.7's count comparison: the useful report is a difference, and a difference needs yesterday's data stored somewhere.
4. Severity Is Not Importance
The last measurement is the one that changes the ordering of the report:
ELAB-974 tool says Warning appears in NEW
OPT-170 tool says Warning appears in both runs
UID-95 tool says Error appears in NEW <- an Error, and NEWTwo facts sit side by side there, and they are independent:
An Error that appears in every run is a waived condition. Somebody already looked at it and decided the flow should continue. Reporting it at the top because the tool called it an Error wastes the top of the report.
A Warning that is new is the one to read. Nobody has seen it before. It may be nothing, and it is the only item on the page about which that is not already known.
So the sort key is novelty first, severity second — not severity alone. UID-95 above is both, which makes it unambiguously first; but ELAB-974, a new Warning about an inferred latch, outranks OPT-170, a standing Warning, by a wide margin.
This is Chapter 21.4's finding in a different file: there, a warning in 100% of runs was background and one in 5% was the lead, and ranking by count inverted the useful order. Here the tool hands you a severity that looks like a ranking and is not one.
5. What To Remember
- The log is the one file here that is not a table, and it is where the tool says what it was unhappy about.
- Measured: 6 complaint lines collapse to 3 conditions, and one condition is 50% of the lines.
- Deduplicate by CODE, not by text — the text varies with the object named, the code does not.
- The code is stable across tool versions and searchable in vendor documentation; a paraphrase is neither.
- Drop
Information:lines at the input, or the warning count includes the tool naming its library. - Never name a variable
$aor$b— Perl'ssortblock owns those globals, and a hash ref so named is shadowed inside every sort (5.9). - That bug printed
HASH(0x…)as a warning code and a count of 5 where 3 was right, with the evidence on stderr (14.2, 20.3). - Split the codes three ways: in both runs, only in the old, only in the new (6.9).
- Measured: 3 background codes and 3 new ones — the new ones are the entire actionable list.
- Print the text for new conditions only; for background ones it is noise already read.
- A condition that went away can mean a check stopped running, not just stopped firing.
- A useful report is a difference, so store yesterday's codes (22.5, 20.7).
- Measured: severity and novelty are independent. An
Errorin every run is a waiver; a newWarningis the thing to read. - Sort by novelty first, severity second — the tool's severity looks like a ranking and is not one (21.4).
- Keep a waiver list with code, reason and date, diff it against the log, and report the background set as a count rather than a list.
That closes Module 22. You can take a synthesis run and answer the questions a QoR review asks: pick the report whose subject is your question rather than scanning the log, read the area fields through a table of spellings that survives a tool upgrade, count cell types with a row pattern that copes with the names and wrapping a real report contains, reconstruct the hierarchy from its indentation and divide inclusive roll-ups into the exclusive areas that say where the area actually is, compare two runs and report only what moved under a significance policy that lives in one named place, and reduce the log's complaints to one entry per condition split into the standing ones and the ones that turned up today.
Where this fits
Part of the Perl curriculum.
