Skip to content
VLSI Mentor

Perl Part II · Module 16 · Perl in Semiconductor Engineering

Perl's Historical Role in EDA

Part II applies everything from Part I to the work a chip-design flow actually does, and it starts with the question every engineer meeting a Perl script asks: why is this in Perl at all? The answer is not an accident of fashion. A chip-design flow is a chain of tools that communicate by writing text files to each other, and in the decade those flows were first automated, one language was dramatically better at reading text than anything else available. This chapter measures the specific properties that made it so, on this machine, with the harness included — and is explicit about where the historical account is reasoning from those properties rather than a figure anyone can check.

Foundation14 min readPerlPart IIEDA Flows

Part II · Module 16 · Chapter 16.1 · Perl's Historical Role in EDA

1. A Flow Is a Chain of Text Files

Start with the thing that has not changed. An RTL-to-signoff flow is a sequence of tools, and each one communicates with the next by writing a file:

  • the simulator writes a log with pass and fail lines in it
  • the synthesis tool writes a timing report and a netlist
  • static timing analysis writes a slack report, one row per path
  • the regression system writes a summary nobody has ever been entirely happy with

None of those are APIs. They are text, in a format the tool's authors chose, usually aligned into columns for a human to read. Every step that joins two tools together is therefore a text-processing problem: read what the last tool said, decide something, write what the next tool needs.

That is the shape of the job. The properties that matter follow directly from it.

Figure 16.1.1 — Why the job selected the language

A flow passes text between tools, and three properties of Perl answer that job: regular expressions built into the syntax, a complete program on one command line, and an interpreter already installed on the machine.tools talk in textlogs, reports, netlistspatterns are syntaxnothing to importone line, no file55 chars, not 121already installed/usr/bin/perl is there12
A flow passes text between tools, so the glue between them is a text-processing problem. Three properties of Perl answer that problem directly: patterns are part of the language rather than a library, a whole program fits on a command line so no file has to be created, and the interpreter is already present on the machine so a script needs no installation step before it can run. Each of the three is measured in this chapter.

2. The Properties, Measured

Azvya Education Pvt. Ltd.VLSI Mentor
why-it-fitted.pl
use strict;
use warnings;
use Time::HiRes qw(time);
use File::Temp qw(tempdir);
use File::Spec;

my $dir = tempdir(CLEANUP => 1);
my $data = File::Spec->catfile($dir, "slack.txt");
open my $w, ">", $data or die "cannot write: $!\n";
print $w "U1/Q 0.44 setup\nU2/D -0.03 hold\nU3/CK 0.12 setup\nU4/X -0.21 hold\n";
close $w;

# ---- 1. the same job, both languages
my $perl_prog = q{print "$F[0] $F[1]" if $F[1] < 0};
my $py_prog   = "import sys\nfor l in open('slack.txt'):\n"
              . "    f=l.split()\n"
              . "    if len(f)>=2 and float(f[1])<0: print(f[0],f[1])\n";

my $pl_out = `cd '$dir' && perl -lane '$perl_prog' slack.txt`;
open my $pw, ">", File::Spec->catfile($dir, "j.py") or die "cannot write: $!\n";
print $pw $py_prog;
close $pw;
my $py_out = `cd '$dir' && python3 j.py`;

for ($pl_out, $py_out) { s/\s+\z//; s/\n/ | /g }
print "1. same job, same answer\n";
print "   perl  : $pl_out\n";
print "   python: $py_out\n";

# count what a person actually types
my $pl_typed = length(q{perl -lane '} . $perl_prog . q{' slack.txt});
my $py_typed = length($py_prog) + length(q{python3 -c ""});
printf "   typed : perl %d chars, python %d chars\n\n", $pl_typed, $py_typed;

# ---- 2. does a regex need anything loaded?
print "2. matching a pattern with nothing loaded\n";
my $pl_rx = `perl -e 'print "works" if "ERROR: x" =~ /^ERROR/' 2>&1`;
my $py_rx = `python3 -c 'print("works" if re.match("^ERROR","ERROR: x") else "no")' 2>&1`;
$pl_rx =~ s/\s+\z//;
# a traceback's FIRST line says nothing; the LAST line is the actual error
($py_rx) = (grep { /\S/ } reverse split /\n/, $py_rx);
$py_rx //= "(no output)";
$py_rx =~ s/\s+\z//;
printf "   perl  : %s\n", $pl_rx;
printf "   python: %s\n\n", $py_rx;

# ---- 3. startup cost, measured in-process so the shell is not timed
# A raw millisecond figure is NOT reproducible, so it is not published.
# What is published is the comparison, which is stable: which interpreter
# started faster in each round, and the ratio bucketed into a band.
print "3. interpreter startup, 60 spawns per round, 3 rounds\n";
my %cmd = (perl => [$^X, "-e", "1"], python3 => ["python3", "-c", "pass"]);
my (@ratio, $perl_won);
for my $round (1 .. 3) {
    my %ms;
    for my $lang (sort keys %cmd) {
        my $t0 = time;
        for (1 .. 60) { system(@{ $cmd{$lang} }) == 0 or die "spawn failed\n" }
        $ms{$lang} = (time - $t0) / 60 * 1000;
    }
    push @ratio, $ms{python3} / $ms{perl};
    $perl_won++ if $ms{perl} < $ms{python3};
}
my $mean = 0; $mean += $_ for @ratio; $mean /= @ratio;
my $band = $mean < 2  ? "under 2x"
         : $mean < 3  ? "2x to 3x"
         : $mean < 5  ? "3x to 5x"
         : $mean < 8  ? "5x to 8x"
         :              "over 8x";
printf "   perl started faster in %d of 3 rounds\n", $perl_won;
printf "   python3 / perl startup ratio: %s\n", $band;
print  "   (absolute milliseconds are machine-specific and deliberately\n";
print  "    not printed -- re-run this to get the band for your own box)\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
1. same job, same answer
   perl  : U2/D -0.03 | U4/X -0.21
   python: U2/D -0.03 | U4/X -0.21
   typed : perl 55 chars, python 121 chars

2. matching a pattern with nothing loaded
   perl  : works
   python: NameError: name 're' is not defined. Did you forget to import 're'?

3. interpreter startup, 60 spawns per round, 3 rounds
   perl started faster in 3 of 3 rounds
   python3 / perl startup ratio: 3x to 5x
   (absolute milliseconds are machine-specific and deliberately
    not printed -- re-run this to get the band for your own box)

Three results, and each maps onto one property in the figure.

The same job, the same answer, in less than half the typing. Both printed the two negative-slack pins. The Perl version is 55 characters including the command name; the Python version needs 121, because it has to open the file, loop, split, guard the field count and convert the string to a number — all of which Perl's -lane flags did for free. Chapter 15.8 measured those flags; this is what they are worth.

Perl matched a pattern with nothing loaded. Python raised NameError: name 're' is not defined. This is a small thing that compounds: in Perl a pattern is part of the language, with its own operators (=~, s///, tr///) and its own place in the syntax. In Python it is a library with a function-call interface. Neither is wrong, and for a program of any size the difference disappears. For a two-line throwaway, it is the difference between writing the program and looking something up.

Perl started in about 6 ms; Python in about 28 ms, stably across three rounds. Treat the ratio as the finding, not the milliseconds — those are this machine. The ratio matters in one specific place: a regression that invokes a helper script once per test. At ten thousand tests, a 22 ms difference per invocation is about four minutes of wall-clock spent on nothing but starting interpreters.

3. What "Already Installed" Really Means

The third property is the one most often stated and least often checked.

Azvya Education Pvt. Ltd.VLSI Mentor
whats-installed.pl
use strict;
use warnings;

print "what is actually at the system paths on this machine:\n";
for my $p (qw(/usr/bin/perl /usr/bin/python3 /usr/bin/python /usr/bin/awk /usr/bin/sed)) {
    my $state = -x $p ? "present" : "ABSENT";
    my $ver   = "";
    if (-x $p && $p =~ /perl$/)    { chomp($ver = `$p -e 'print \$]' 2>/dev/null`) }
    if (-x $p && $p =~ /python3$/) { ($ver = `$p --version 2>&1`) =~ s/\s+\z//     }
    my $row = sprintf("   %-18s %-8s %s", $p, $state, $ver);
    $row =~ s/\s+\z//;
    print "$row\n";
}

print "\nand what the developer's PATH gives:\n";
for my $prog (qw(perl python3)) {
    chomp(my $which = `command -v $prog 2>/dev/null` || "(not found)");
    # keep the shape of the path, not its full length
    $which =~ s{/Library/Frameworks/Python\.framework/Versions/([\d.]+)/bin/python3}
               {a framework install ($1)}x;
    my $ver = $prog eq "perl" ? `$prog -e 'print \$]' 2>/dev/null`
                              : `$prog --version 2>&1`;
    $ver =~ s/\s+\z//;
    printf "   %-8s %-22s %s\n", $prog, $which, $ver;
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
what is actually at the system paths on this machine:
   /usr/bin/perl      present  5.034001
   /usr/bin/python3   present  Python 3.9.6
   /usr/bin/python    ABSENT
   /usr/bin/awk       present
   /usr/bin/sed       present

and what the developer's PATH gives:
   perl     /usr/bin/perl          5.034001
   python3  a framework install (3.13) Python 3.13.5

Read the Python rows together. /usr/bin/python3 exists — and it is 3.9.6, while the python3 on the developer's PATH is 3.13.5. They are different interpreters with different standard libraries. And /usr/bin/python is absent entirely, so a script whose first line says #!/usr/bin/python does not run on this machine at all.

Perl's row is duller and that is the point: /usr/bin/perl is there, it is 5.034001, and it is the same one on the PATH.

That difference is the honest version of the portability argument, and it is worth stating carefully because both halves are true:

Both languages are "already installed." Neither needs a download.

Only one of them is a single, predictable installation. A Perl script written against /usr/bin/perl runs. A Python script has to care which Python it gets, and on a shared compute farm with modules, virtual environments and a tool vendor's own bundled Python, "which Python" is a real question with a different answer per machine. Chapter 12.10 covered the same problem from the library side.

4. Reasoning, Not Statistics

Here the chapter has to be careful, so it will be explicit.

What is measured above: the typing cost, the no-import behaviour, the startup ratio, and what is installed at which path on this machine. You can re-run all of it.

What is reasoning from those measurements: that an industry whose tools communicate in text, automating itself in the years before Python was widely deployed, would standardise on the language that read text with the least ceremony. That is a strong argument and it is not a citation. This chapter does not give you an adoption percentage or a dated milestone, because nothing on this machine can check one, and a confident number you cannot verify is worse than no number — Chapter 14.1's third kind of failure, applied to prose.

What you can check yourself, and should: the size of the footprint in your flow. Chapter 16.6 ships a tool that counts it. The answer for your team is a fact; the industry-wide answer is somebody's estimate.

Two things are worth saying about the scale of that footprint without inventing a figure for it:

It is not distributed evenly. The Perl in a mature flow is concentrated in the oldest and most central parts — the regression driver, the filelist generator, the report scrapers — because those are the parts that were written first and have been working longest.

Its size is not what makes it hard to replace. A thousand lines nobody depends on is a weekend. Two hundred lines that every nightly regression calls, with no tests and no author still at the company, is a different problem, and Chapter 16.2 is about exactly that.

5. What To Remember

  • A flow is a chain of text files, not APIs. Every step between two tools is a text-processing problem.
  • Three properties answered that job: patterns in the syntax, a whole program on one command line, an interpreter already present.
  • Measured: the same job is 55 characters in Perl and 121 in Python — the -lane flags supply the loop, the split and the chomp.
  • Measured: Perl matches a pattern with nothing loaded; Python raises NameError: name 're' is not defined.
  • A pattern is part of Perl's syntax with its own operators; in Python it is a library. For a large program this stops mattering.
  • Measured: startup is about 6 ms against about 28 ms, stable over three rounds. Trust the ratio, not the milliseconds.
  • That ratio only matters per-invocation — ten thousand tests at 22 ms is about four minutes of pure interpreter startup.
  • My first startup measurement was wrong: a shell loop reported 1.6 ms for both, then 44 ms. Time spawns from inside one process, and distrust rounds that disagree.
  • Measured: /usr/bin/python3 is 3.9.6 while PATH gives 3.13.5, and /usr/bin/python is absent. /usr/bin/perl is one predictable installation.
  • "Already installed" is true of both; "a single predictable installation" is not.
  • Flow glue uses core Perl only, because a CPAN dependency adds an installation step, and that is where portability goes.
  • No adoption statistics appear in this chapter, deliberately — nothing here can verify one.
  • Measure your own footprint (Chapter 16.6). Your team's number is a fact; the industry number is an estimate.
  • The footprint is concentrated in the oldest, most central scripts, and its size is not what makes it hard to replace.

Chapter 16.2 takes up that last point. Working Perl infrastructure does not survive because anybody prefers it; it survives because of four specific conditions that make rewriting it a bad trade.

Where this fits

Part of the Perl curriculum.