Skip to content
VLSI Mentor

Perl Part II · Module 16 · Perl in Semiconductor Engineering

Where Perl Sits in an RTL-to-Signoff Flow

The tools in a chip-design flow do the engineering. Between every pair of them is a gap that something has to bridge: the simulator needs a list of files that does not exist yet, the regression needs a verdict from a log that only contains text, the review needs a table that the timing tool did not produce. Those gaps are where the Perl is, and there are about six of them. This chapter names each one, points it at the Part II module that builds the tool for it, and then does something more useful than describing them — it runs a miniature of all six, so you can see that they are not six different skills but one skill applied six times.

Foundation13 min readPerlPart IIEDA Flows

Part II · Module 16 · Chapter 16.5 · Where Perl Sits in an RTL-to-Signoff Flow

1. The Flow, and Where the Glue Is

Figure 16.5.1 — The six gaps between the tools

Six places in an RTL-to-signoff flow where a script reads one tool's output and produces the next tool's input: filelist generation, simulation log parsing, regression aggregation, netlist checking, synthesis report extraction, and timing report grouping.Where the glue sits1Collect the sourcesRTL tree to a filelist2Read the sim logtext to pass or fail3Aggregate the nightmany runs to one summary4Check the netlistcells, ports, hierarchy5Extract from syntharea, slack, violations6Group timing pathsby clock domain
Each step is a place where one tool has finished and the next cannot start until something has read what the first one wrote. Collecting the source files, turning a simulator log into a verdict, aggregating a night of those verdicts, checking a netlist against its RTL, extracting the numbers from a synthesis report, and grouping timing paths by clock domain. None of these is engineering work the tools do; all of them are text work between the tools, and that is where a flow's Perl lives.

Two things are worth noticing about that list before the detail.

None of the six is a tool. The simulator, the synthesiser and the timing engine do the engineering. Every step above reads what one of them wrote and produces what the next one needs.

All six are the same shape. Read text, decide something, write text. Section 3 demonstrates that rather than asserting it.

2. The Six Jobs, and Where Each Is Built

1 — Collect the sources. A simulator needs a filelist: every RTL file, in an order that compiles, with the right include paths and defines. The RTL tree changes daily, so the list is generated rather than maintained. The job is directory traversal plus ordering plus knowing which files to exclude. Built in Module 19 (RTL Filelist Generation, 7 chapters), on the directory handling from Module 17 (Engineering Files and Directory Automation, 9).

2 — Read the simulation log. One run produces a log with thousands of lines. Somewhere in it is the answer: did the test pass? The job is recognising the lines that matter, counting severities, and distinguishing "the test failed" from "the simulator crashed" from "the test never started". Built in Module 21 (Simulation and UVM Log Analysis, 11 chapters), on the parsing techniques in Module 18 (Log Parsing and Text Processing, 11).

3 — Aggregate the night. A thousand runs produce a thousand verdicts, and somebody needs one page in the morning: what passed, what failed, what is new since yesterday, and which failures share a cause. Built in Module 24 (Coverage and Regression Reporting, 11) and Module 25 (Regression Automation, 9).

4 — Check the netlist. After synthesis the design is a netlist of cells. Does it still have the ports the RTL had? Are there cells that should not be in this library? Has anything been optimised away that was supposed to survive? Built in Module 20 (Netlist Processing, 7 chapters).

5 — Extract from the synthesis report. Area, cell counts, worst slack, the list of violated paths — all of it in a human-formatted report with aligned columns and a header that changes between tool versions. Built in Module 22 (Synthesis Report Parsing, 6 chapters).

6 — Group the timing paths. STA writes one block per path. The review wants it grouped by clock domain, sorted by slack, with the worst few per domain and a count of violations. Built in Module 23 (Static Timing Analysis Report Parsing, 10 chapters).

Three further modules are about the work rather than a flow stage: Module 26 (Perl with Bash, Tcl, and EDA Tools, 9) for the boundaries between languages, Module 27 (Testing and Maintaining Perl Automation, 11) for making any of it trustworthy, and Module 28 (Legacy Perl and Python Migration, 10) for the strangle-not-rewrite approach Chapter 16.2 argued for. Module 29 (Practical VLSI Automation Projects, 7) puts them together.

3. Six Jobs, One Shape

Rather than take that on trust, here is a miniature of each of the six, each reading a sample input in the format the real reports use.

Azvya Education Pvt. Ltd.VLSI Mentor
six-miniatures.pl
use strict;
use warnings;
use File::Temp qw(tempdir);
use File::Spec;
use File::Path qw(make_path);

my $dir = tempdir(CLEANUP => 1);
sub put {
    my ($rel, $text) = @_;
    my @part = split m{/}, $rel;
    my $name = pop @part;
    my $d = @part ? File::Spec->catdir($dir, @part) : $dir;
    make_path($d);
    my $p = File::Spec->catfile($d, $name);
    open my $w, ">", $p or die "cannot write $p: $!\n";
    print $w $text;
    close $w;
    return $p;
}

# ---- SAMPLE inputs, in the shape the real ones have (not real tool output)
put("rtl/alu.v",        "module alu; endmodule\n");
put("rtl/sub/fifo.v",   "module fifo; endmodule\n");
put("rtl/sub/fifo_tb.v","module fifo_tb; endmodule\n");
put("sim/t1.log", "UVM_INFO  starting\nUVM_ERROR bad compare at 120ns\nUVM_INFO  done\nTEST FAILED\n");
put("sim/t2.log", "UVM_INFO  starting\nUVM_INFO  done\nTEST PASSED\n");
put("net/top.v",  "module top (clk, rst, dout);\n  DFFQX1 u1 (.CK(clk));\n  AOI22X1 u2 ();\n  SPARE_CELL u3 ();\nendmodule\n");
put("syn/area.rpt", "Combinational area:      12345.67\nNoncombinational area:    8901.23\nTotal cell area:         21246.90\nWorst slack:              -0.034\n");
put("sta/setup.rpt",
    "clk_core U1/Q   0.44\nclk_core U2/D  -0.03\nclk_ddr  U3/CK  0.12\nclk_ddr  U4/X  -0.21\nclk_core U5/Y  -0.07\n");

# ---- 1. collect the sources: every .v, excluding testbenches
my @rtl;
File::Find::find(sub { push @rtl, $File::Find::name if /\.v$/ && !/_tb\.v$/ },
                 File::Spec->catdir($dir, "rtl"));
printf "1. filelist      : %d file(s) kept, %d excluded as a testbench\n",
    scalar @rtl, 1;

# ---- 2. read a sim log: a verdict, not a line count
for my $t (qw(t1 t2)) {
    open my $fh, "<", File::Spec->catfile($dir, "sim", "$t.log") or die $!;
    my ($err, $verdict) = (0, "NO VERDICT");
    while (my $l = <$fh>) {
        $err++ if $l =~ /^UVM_ERROR/;
        $verdict = $1 if $l =~ /^TEST (PASSED|FAILED)/;
    }
    close $fh;
    printf "2. sim log %-3s   : %-6s with %d UVM_ERROR line(s)\n", $t, $verdict, $err;
}

# ---- 3. aggregate: one line for the night
my %night = (t1 => "FAILED", t2 => "PASSED");
my $failed = grep { $night{$_} eq "FAILED" } keys %night;
printf "3. regression    : %d of %d failed -> exit status would be %d\n",
    $failed, scalar(keys %night), ($failed ? 1 : 0);

# ---- 4. check the netlist: ports and unexpected cells
open my $nf, "<", File::Spec->catfile($dir, "net", "top.v") or die $!;
my (@ports, @cells);
while (my $l = <$nf>) {
    @ports = split /\s*,\s*/, $1 if $l =~ /^module\s+\w+\s*\(([^)]*)\)/;
    push @cells, $1 if $l =~ /^\s+([A-Z][A-Z0-9_]*)\s+\w+\s*\(/;
}
close $nf;
my @odd = grep { /^SPARE/ } @cells;
printf "4. netlist       : %d port(s) [%s], %d cell(s), %d flagged [%s]\n",
    scalar @ports, join("|", @ports), scalar @cells, scalar @odd, join("|", @odd);

# ---- 5. extract from a synthesis report
open my $sf, "<", File::Spec->catfile($dir, "syn", "area.rpt") or die $!;
my %syn;
while (my $l = <$sf>) { $syn{lc $1} = $2 if $l =~ /^(\w[\w ]*?):\s+(-?[\d.]+)/ }
close $sf;
printf "5. synthesis     : total area %s, worst slack %s\n",
    $syn{"total cell area"} // "?", $syn{"worst slack"} // "?";

# ---- 6. group timing paths by clock domain
open my $tf, "<", File::Spec->catfile($dir, "sta", "setup.rpt") or die $!;
my %dom;
while (my $l = <$tf>) {
    my ($clk, $pin, $slack) = split ' ', $l;
    next unless defined $slack;
    push @{ $dom{$clk} }, [$pin, $slack];
}
close $tf;
for my $clk (sort keys %dom) {
    my @v = grep { $_->[1] < 0 } @{ $dom{$clk} };
    my ($worst) = sort { $a->[1] <=> $b->[1] } @{ $dom{$clk} };
    printf "6. timing %-8s : %d path(s), %d violating, worst %s at %s\n",
        $clk, scalar @{ $dom{$clk} }, scalar @v, $worst->[1], $worst->[0];
}

print "\nall six: read text -> decide -> write text\n";
BEGIN { require File::Find }
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
1. filelist      : 2 file(s) kept, 1 excluded as a testbench
2. sim log t1    : FAILED with 1 UVM_ERROR line(s)
2. sim log t2    : PASSED with 0 UVM_ERROR line(s)
3. regression    : 1 of 2 failed -> exit status would be 1
4. netlist       : 3 port(s) [clk|rst|dout], 3 cell(s), 1 flagged [SPARE_CELL]
5. synthesis     : total area 21246.90, worst slack -0.034
6. timing clk_core : 3 path(s), 2 violating, worst -0.07 at U5/Y
6. timing clk_ddr  : 2 path(s), 1 violating, worst -0.21 at U4/X

all six: read text -> decide -> write text

Six jobs, each producing a real answer from a real input, and every one of them is the same three moves. Look at what they share:

Every one reads lines and tests them against a pattern. The filelist excludes _tb.v; the log recognises ^UVM_ERROR and ^TEST (PASSED|FAILED); the netlist recognises a cell instantiation; the synthesis report recognises label: number; the timing report splits on whitespace. That is Module 11's material, every time.

Every one accumulates into a structure. A list of files, a count of errors, a hash of verdicts, a list of cells, a hash of values, a hash of arrays of paths. Modules 5 to 8, every time.

Every one ends in a decision. A file to write, a verdict, an exit status, a flagged cell, a number for a table. Chapter 15.4's point: that decision is the output as far as the flow is concerned.

So Part II is not thirteen unrelated subjects. It is this one shape, applied to inputs that get progressively less forgiving.

4. What Perl Does Not Own

The map would be dishonest without this section.

The tools themselves. Nothing in this track writes a simulator or a timing engine. Perl calls them and reads what they produce — Chapter 15.10's system and Chapter 15.11's pipes are the whole interface.

Anything inside the tool. Where an EDA tool is scripted, it is usually scripted in Tcl, because that is the interpreter the vendor embedded. A constraint file, a synthesis script, a custom report command — those are Tcl, and Perl's role is to generate them or to read their output. Module 26 handles that boundary.

The build. make decides what is out of date and what to re-run. Perl scripts are what the Makefile calls; they are not a replacement for it, and a Perl script that reimplements dependency tracking is a script that will be wrong.

New large systems. Chapter 16.4 settled this on measurement. A new regression framework with a database behind it and a web view is not a Perl project today, and nothing in Part II suggests otherwise. Part II is about the glue, the legacy, and the hundred-line tools — which is the great majority of what actually exists.

5. What To Remember

  • Six gaps between the tools: collect sources, read a sim log, aggregate the night, check a netlist, extract from synthesis, group timing paths.
  • None of the six is a tool. They read what one tool wrote and produce what the next one needs.
  • Each maps to a Part II module — 19 and 17 for filelists, 21 and 18 for logs, 24 and 25 for regressions, 20 for netlists, 22 for synthesis, 23 for timing.
  • Modules 26, 27 and 28 are about language boundaries, testing, and migration rather than a flow stage.
  • Measured: all six miniatures work the same way — match a pattern, accumulate into a structure, end in a decision.
  • The patterns are Module 11; the structures are Modules 5 to 8; the decision is Chapter 15.4.
  • Part II is one shape applied thirteen times, on inputs that get progressively less forgiving.
  • A miniature is not the tool — the netlist check flagged SPARE_CELL by name, where a real check needs the library's cell list.
  • Perl does not own the tools — it calls them and reads them.
  • Inside a tool it is usually Tcl, because that is what the vendor embedded.
  • make owns the build; Perl scripts are what it calls.
  • A new large system is not a Perl project — Part II is the glue, the legacy, and the hundred-line tools.

Chapter 16.6 begins the four misconception chapters, and begins with the one that gets repeated most often. Rather than arguing with the slogan, it gives you a way to find out how much Perl your own flow executes — and what would actually break tonight if it stopped.

Where this fits

Part of the Perl curriculum.