Perl Part II · Module 17 · Engineering Files and Directory Automation
RTL, Verification and Run-Directory Trees
Every project names its directories slightly differently, which makes a script written against one project's names useless on the next. The way out is to stop thinking about names and think about roles, because there are only four and every project has all of them: things you read and must never write, things you create fresh and delete afterwards, things you write and keep, and things you must walk past without looking inside. Once a script classifies a directory by role, the names become a short configuration rather than assumptions scattered through the code — and a script that knows a directory is disposable can safely delete it, which Chapter 17.6 shows is the difference between cleanup and disaster.
Intermediate10 min readPerlPart IIProject Structure
Part II · Module 17 · Chapter 17.2 · RTL, Verification and Run-Directory Trees
1. Four Roles, Not Seven Names
Figure 17.2.1 — What a script may do with each kind of directory
The four roles, stated as rules a script can follow:
Read-only. rtl/, tb/, scripts/, lists/. A script reads these and never writes into them. If a tool wants to put a generated file somewhere, it does not go here.
Disposable. runs/<something>. Created for one run, deleted when nobody needs the evidence. This is the only role a script may delete, and Chapter 17.6 is about proving a path really has this role before removing it.
Kept. reports/. Written by the flow and compared against previous runs. Never deleted automatically, because the comparison is the point.
Skipped. third_party/, rtl/generated/, .git/, .svn/. Present in the tree and not part of the design you are collecting. Chapter 17.4 is entirely about walking past these.
2. Measure a Tree's Shape
use strict;
use warnings;
use File::Find;
use File::Temp qw(tempdir);
use File::Spec;
use File::Path qw(make_path);
# ---- a SAMPLE project tree (no claim that any project uses these exact names)
my $root = File::Spec->catdir(tempdir(CLEANUP => 1), "proj");
my @file = qw(
rtl/alu.v rtl/core/cpu.sv rtl/sub/fifo.v rtl/generated/regs.sv
tb/env/env.sv tb/tests/t1.sv
runs/alu_1/sim.log runs/alu_2/logs/sim.log
reports/setup.rpt scripts/run.pl lists/nightly.list
third_party/uvm/uvm.sv
);
for my $rel (@file) {
my @part = split m{/}, $rel;
my $name = pop @part;
make_path(File::Spec->catdir($root, @part));
open my $w, ">", File::Spec->catfile($root, @part, $name) or die "cannot write: $!\n";
print $w "// $rel\n";
close $w;
}
# ---- the roles, as a table a script can consult instead of guessing
my %role = (
rtl => [ "sources", "read, never write" ],
tb => [ "sources", "read, never write" ],
scripts => [ "tools", "read, never write" ],
lists => [ "inputs", "read, sometimes generate" ],
runs => [ "per-run", "create and delete" ],
reports => [ "outputs", "write, keep" ],
third_party => [ "external", "SKIP when walking sources" ],
);
opendir my $dh, $root or die "cannot read $root: $!\n";
my @top = sort grep { !/^\.\.?$/ && -d File::Spec->catdir($root, $_) } readdir $dh;
closedir $dh;
printf "%-14s %-10s %-27s %s\n", "DIRECTORY", "ROLE", "WHAT A SCRIPT MAY DO", "FILES";
for my $d (@top) {
my ($r, $what) = @{ $role{$d} // [ "unknown", "ask before touching" ] };
my $n = 0;
find(sub { $n++ if -f $_ }, File::Spec->catdir($root, $d));
printf "%-14s %-10s %-27s %d\n", "$d/", $r, $what, $n;
}
print "\nthe sub-shapes that matter:\n";
for my $p (["rtl/generated", "generated sources — skip, they are rebuilt"],
["runs/alu_1", "one directory per test and seed"],
["runs/alu_2/logs", "logs live under the run directory"]) {
printf " %-18s %-7s %s\n", $p->[0],
(-d File::Spec->catdir($root, split m{/}, $p->[0]) ? "exists" : "absent"), $p->[1];
}DIRECTORY ROLE WHAT A SCRIPT MAY DO FILES
lists/ inputs read, sometimes generate 1
reports/ outputs write, keep 1
rtl/ sources read, never write 4
runs/ per-run create and delete 2
scripts/ tools read, never write 1
tb/ sources read, never write 2
third_party/ external SKIP when walking sources 1
the sub-shapes that matter:
rtl/generated exists generated sources — skip, they are rebuilt
runs/alu_1 exists one directory per test and seed
runs/alu_2/logs exists logs live under the run directoryNotice that the table is sorted and the readdir result is passed through sort explicitly. That is not tidiness — readdir returns entries in whatever order the filesystem gives them, which is not alphabetical and not stable between machines. Chapter 17.8 measures what that does to a report.
Notice too that an unrecognised directory gets unknown — ask before touching, not a guess. A script that assumes an unfamiliar directory is disposable is one bad assumption away from deleting somebody's work.
3. The Sub-Shapes That Matter
Three patterns inside those directories come up on every project.
runs/<test>_<seed>/ — one directory per test and seed, because the same test at a different seed is a different run with its own log. A flow that reuses one directory per test cannot keep two seeds' evidence, and the second run silently overwrites the first. Chapter 17.5 builds these.
runs/<run>/logs/ and runs/<run>/waves/ — the run directory has internal structure, created before the tool starts. A simulator told to write logs/sim.log into a directory that does not exist fails in a way that looks like a tool problem.
rtl/generated/ — sources produced by another step: register maps from a spec, configuration packages, wrappers. They are real inputs to compilation and must not be collected by a source walk, because the walk runs before they are regenerated. Chapter 17.4 handles the skipping; Chapter 17.9 handles noticing they are stale.
4. Surviving a Project That Names Things Differently
The point of roles is that the names become data. Put them in one place:
my %TREE = (
sources => [ "rtl", "tb" ], # read-only, walked for files
tools => [ "scripts", "bin" ], # read-only
inputs => [ "lists" ],
runs => [ "runs" ], # the ONLY deletable role
outputs => [ "reports" ],
skip => [ "third_party", "generated", ".git", ".svn", "backup" ],
);Three properties that gives you:
One place to change for a new project. The code asks "which directories are sources?" and gets an answer; it never contains the string rtl.
Several directories per role. sources is a list, which handles the tb/ versus tests/ problem directly.
A skip list the walker consults, which is exactly what Chapter 17.4 needs and means the skip rules are visible rather than buried in a return if inside a callback.
And one check worth running at startup, per Chapter 15.3's argument for validating before working:
for my $role (qw(sources tools inputs)) {
for my $d (@{ $TREE{$role} }) {
my $p = File::Spec->catdir($ROOT, $d);
warn "warning: $role directory not found: $d\n" unless -d $p;
}
}A warn rather than a die, because a project legitimately may not have every directory — but a warn that names the missing one turns "no files found" into "tb does not exist here", which is a much shorter conversation.
5. What To Remember
- Names change between projects; the four roles do not.
- Read-only:
rtl/,tb/,scripts/,lists/. A script that writes here has a bug. - Disposable:
runs/<...>. The only role a script may delete — and only after proving it (17.6). - Kept:
reports/. Never deleted automatically, because comparison against last week is the point. - Skipped:
third_party/,rtl/generated/,.git/,.svn/. In the tree, not part of the design (17.4). - Measured: a classifier reports the role and file count per directory, and an unrecognised one gets
unknown — ask before touchingrather than a guess. readdirorder is not alphabetical and not stable — alwayssortit (17.8).runs/<test>_<seed>/— per test and seed, or two seeds overwrite each other.- Create
logs/andwaves/before the tool runs, or the failure looks like a tool problem. rtl/generated/is a real input that a source walk must skip, because the walk runs before it is rebuilt.tb/versustests/is not reliable — take a list of source roots.- The run area is often a separate filesystem, so give it its own override:
$ENV{RUN_AREA} //. - Put the names in one
%TREEtable so the code never contains the stringrtl. - Warn, do not die, for a missing directory — but name it, so "no files found" becomes "
tbdoes not exist here". - Never infer disposability from a name.
work/andscratch/are sometimes neither.
Chapter 17.3 starts using the tree. Finding every source file sounds like a solved problem until you measure what glob actually does — and one widely-used pattern turns out not to recurse at all.
Where this fits
Part of the Perl curriculum.
