Perl Part II · Module 19 · RTL Filelist Generation
What a Filelist Must Contain
A filelist is the single piece of plumbing every simulation, every synthesis run and every regression depends on, and it is almost always generated by a script somebody wrote in an afternoon. Before writing that script it is worth knowing exactly what the tool requires, and the only trustworthy way to learn that is to hand a compiler a filelist with one thing missing and read what it says. This chapter does that five times with a real compiler on a real design, and the most useful result is that two quite different mistakes produce exactly the same error message.
Intermediate15 min readPerlPart IIFilelists
Part II · Module 19 · Chapter 19.1 · What a Filelist Must Contain
1. Five Filelists, One Design
The design is five files: two packages, two modules and a testbench. One module includes a header for its data width; both packages are imported. Every filelist below describes the same design and differs in exactly one way.
use strict;
use warnings;
# Five filelists for one design. Each is missing exactly one thing, and the
# tool's reaction is what defines "what a filelist must contain".
my @SRC = (
"rtl/core/chip_pkg.sv", # a package ...
"rtl/core/types_pkg.sv", # ... that this one imports
"rtl/core/counter.sv",
"rtl/core/alu.sv",
"tb/tb_counter.sv",
);
sub write_f { my ($name, @lines) = @_; open my $o, ">", $name or die $!;
print $o "$_\n" for @lines; close $o }
sub try {
my ($label, $file, @flags) = @_;
unlink "out.vvp";
my @cmd = ("iverilog", "-g2012", @flags, "-o", "out.vvp", "-c", $file);
my $log = `@cmd 2>&1`;
my $rc = $? >> 8;
my $art = -f "out.vvp" ? "yes" : "no";
my ($first) = grep { /\S/ } split /\n/, ($log // "");
$first = "(no output)" unless defined $first;
printf "%-22s rc=%-3s artifact=%-4s %s\n", $label, $rc, $art, $first;
return ($rc, $log // "");
}
write_f("complete.f", @SRC);
write_f("misordered.f", @SRC[2,3,4,0,1]); # packages LAST
write_f("nopkg.f", @SRC[2,3,4]); # packages absent entirely
write_f("empty.f"); # nothing at all
write_f("ghost.f", @SRC, "rtl/core/fifo.sv"); # a path that does not exist
print "ONE design, five filelists:\n\n";
my ($rc_ok) = try("complete + incdir", "complete.f", "-I", "rtl/include");
my ($rc_noi) = try("complete, NO incdir","complete.f");
my (undef, $log_bad) = try("packages last", "misordered.f", "-I", "rtl/include");
my (undef, $log_no) = try("packages absent", "nopkg.f", "-I", "rtl/include");
my ($rc_mt) = try("empty filelist", "empty.f", "-I", "rtl/include");
my ($rc_gh) = try("one missing path", "ghost.f", "-I", "rtl/include");
print "\ndoes the complete list actually simulate?\n";
if ($rc_ok == 0) {
unlink "out.vvp";
system("iverilog -g2012 -I rtl/include -o out.vvp -c complete.f") == 0 or die;
my $run = `vvp out.vvp 2>&1`;
printf " %s\n", ($run =~ /PASSED/ ? "yes -- it runs and the testbench prints PASSED" : "no");
my ($c) = $run =~ /count=(\d+)/;
printf " the counter reached count=%s\n", (defined $c ? $c : "?");
}
print "\nthe finding that matters:\n";
printf " 'packages last' and 'packages absent' give the SAME output? %s\n",
($log_bad eq $log_no ? "YES" : "no");
printf " does either message name the package (chip_pkg)? %s\n",
(($log_bad =~ /chip_pkg/ || $log_no =~ /chip_pkg/) ? "yes" : "NO");
print " so the compile error cannot tell you WHICH of the two you did.\n";ONE design, five filelists:
complete + incdir rc=0 artifact=yes (no output)
complete, NO incdir rc=1 artifact=no rtl/core/counter.sv:2: Include file widths.svh not found
packages last rc=6 artifact=no rtl/core/counter.sv:7: syntax error
packages absent rc=6 artifact=no rtl/core/counter.sv:7: syntax error
empty filelist rc=1 artifact=no iverilog: no source files.
one missing path rc=1 artifact=no rtl/core/fifo.sv: No such file or directory
does the complete list actually simulate?
yes -- it runs and the testbench prints PASSED
the counter reached count=4
the finding that matters:
'packages last' and 'packages absent' give the SAME output? YES
does either message name the package (chip_pkg)? NO
so the compile error cannot tell you WHICH of the two you did.Read the table as a specification. Each row is one thing a filelist must get right, stated by what happens when it does not.
The complete list compiles, and then simulates. rc=0, an artifact appears, the testbench runs and prints count=4 and PASSED. That last part matters: a filelist that compiles is not automatically a filelist that describes the design you meant.
Without the include directory, the preprocessor stops at counter.sv:2 — Include file widths.svh not found. The file is in the tree; nothing told the tool where to look.
With the packages last, three files fail to parse. The error points at the import line.
With the packages absent, the output is the same.
An empty filelist gives iverilog: no source files. — a clear message, and worth knowing because an empty filelist is what a broken generator most often produces (Chapter 18.9's whole argument, applied to the generator's output instead of its input).
One bad path names the file that does not exist. This is the friendliest failure in the table and the one a validator can prevent entirely (19.7).
2. The Four Things A Filelist Carries
The measurement gives the list:
Sources. Every .sv and .v file the design needs — and only those. Section 4 is about the "only those".
Include directories. The preprocessor resolves `include "widths.svh" by searching a path list, not the source tree. Commercial tools spell each entry as +incdir+ followed by the directory, inside the filelist itself; Icarus spells it -I <dir> on the command line. Chapter 19.4 derives this list from the files rather than hardcoding it.
Defines. Macros set from outside the source, spelled +define+NAME=value for commercial tools and -D NAME=value for Icarus. These are what make one source tree build several configurations (Chapter 19.6).
Order. Packages and interfaces before whatever imports them. A correct set of files in the wrong order does not compile, as row three shows, and Chapter 19.5 is entirely about getting this right from the source rather than from a hand-maintained list.
3. Absolute Or Relative
Both work. They fail differently, which is the whole basis for choosing.
Absolute paths resolve from any working directory. A filelist full of them can be read by a tool launched from a run directory three levels down, which is how regressions actually run. The cost is that the filelist is tied to one checkout: it cannot be committed, shared between users, or compared between two trees, because every line contains somebody's home directory.
Relative paths are portable and diffable — two filelists from two checkouts compare cleanly — but they are meaningless unless the tool is launched from the directory they are relative to.
The practical answer is the one Chapter 17.1 built: anchor to a project root and generate absolute paths from it.
use FindBin;
use Cwd qw(abs_path);
my $ROOT = $ENV{PROJ_ROOT} // abs_path("$FindBin::Bin/..");
...
print $fh abs_path("$ROOT/$rel"), "\n";The generator is run from anywhere, the filelist is absolute, and it is regenerated rather than committed — so the fact that it is not portable costs nothing. Chapter 17.1 measured the trap in the alternative: $0 and dirname($0) change with how the script is invoked, while FindBin::Bin and abs_path($0) do not.
And a generated filelist should say it is generated, with a comment naming the generator:
// GENERATED by tools/gen_filelist.pl -- do not edit
// regenerate: tools/gen_filelist.pl --config rtlBoth tools accept // comment lines in a command file. Two lines, and the next person stops hand-editing a file that is about to be overwritten.
4. What A Filelist Must Not Contain
The measurement above is about omissions. The more common production failure is the opposite — a list with too much in it — and it is more dangerous because it often still compiles.
Generated files. A generated/ or build/ tree holding a previous run's output. Adding it can give you two modules with the same name, and the tool takes one of them.
Backup and editor files. counter.sv.bak, counter.sv~, .#counter.sv. A .bak of a module is a second definition of that module. Measured on Icarus, that is a hard error naming both paths — 'counter' has already been declared in this scope, exit 2, no artifact — so this particular mistake is caught loudly. What is not caught loudly is an editor lock file or a generated copy that declares a differently named module: that compiles, adds dead code to the design, and shows up only as a name you do not recognise in the hierarchy.
Vendor and third-party trees that the tool is given another way, through a library switch or a precompiled library.
Files for other configurations — the gate-level netlist when you are running RTL, the assertion library when assertions are off.
Chapter 17.4 measured this exact hazard on a source tree: a filename filter kept five files where a pruning walk kept two, and three of the five would have built the wrong design. Chapter 19.2 applies that result to filelist generation, and the fixture tree used throughout this module deliberately contains a generated/ directory, a .bak and an editor lock file so the filtering can be measured rather than described.
5. What To Remember
- A filelist's requirement is not a style question — it is whatever the tool rejects, so measure it.
- Measured with a real compiler, one design, five filelists: complete
rc=0and it simulates (count=4,PASSED). - No include directory →
Include file widths.svh not found,rc=1, no artifact. - Packages last → syntax errors at the
importline, no artifact. - Packages absent → byte-identical output and the same status.
- Neither message names the package. The compiler reports the line that uses it.
- So you cannot debug back from the error to the cause — the generator must be right by construction.
- An empty filelist says
no source files.— and an empty filelist is what a broken generator produces. - One bad path is named explicitly, and is the failure a validator can prevent outright.
- Icarus's exit status is its error count, not a category — 6 errors gives
rc=6. Never read it as a code. - Four things a filelist carries: sources, include directories, defines, and order.
- Order matters because the compiler makes one pass — unlike a C link, there is no second chance.
+incdir+/+define+are the commercial spelling; Icarus uses-Iand-D.- Generate absolute paths from an anchored project root (17.1), and regenerate rather than commit.
- Stamp the filelist as generated, with the command to regenerate it.
- A filelist must not contain generated trees,
.bak/editor files, vendor trees handled another way, or other configurations' files. - The quiet case is a duplicate under a DIFFERENT module name — it compiles and adds dead code; a same-name duplicate is loud.
- A
.bakbeside its source is a second definition of the module — measured on Icarus that is a hard error naming both paths (exit 2), not a silent wrong answer. - Check duplicates by module name, not only by path (19.7) — two paths can define one module.
Chapter 19.2 starts building the generator. The first job is finding the sources, and the measurement there is how many files a naive search collects that must not be in the list.
Where this fits
Part of the Perl curriculum.
