Linux & Bash · Part II · Module 26 · Compile and Simulation File Lists
Include Directories, Defines and Options Inside a List
An include path or a define that lives on one tool's command line is a fact about that tool, not about the design. Moving it into the filelist makes it a fact about the design — which is the whole point, and which is only useful if the options really do take effect from inside the list. This chapter measures that they do.
Intermediate20 min readLinuxPart IIFilelistsDefinesInclude Paths
Part II · Module 26 · Chapter 26.2 · Include Directories, Defines and Options Inside a List
Chapter 24.5 found a -DFAST_SIM in a build script that changed a timeout by a factor of a hundred, and nothing in the HDL said so. This chapter is the fix: put it in the filelist, where every tool sees it.
1. The Options, and What They Do
| Line | Means |
|---|---|
+incdir+<dir> | add <dir> to the `include search path |
+define+NAME | define NAME with no value |
+define+NAME=value | define it with a value |
-y <dir> with +libext+.sv | resolve unknown module names from <dir> |
-sv, -g2012, --std=... | language version |
-f <list> / -F <list> | read another list here |
-timescale=1ns/1ps | the default timescale |
The +-prefixed forms are the Verilog convention and are accepted by essentially every simulator; the --prefixed ones are per-tool. A filelist that uses only the + forms is portable between simulators, which is the main practical reason to prefer them.
2. They Really Do Take Effect — Measured
This is worth establishing rather than assuming, because the whole argument of the chapter rests on it.
A header and a file that uses it:
// rtl/include/opt_defs.svh
`ifndef OPT_DEFS_SVH
`define OPT_DEFS_SVH
`define WIDTH 16
`endif// dut26.sv
`include "opt_defs.svh"
module dut26;
initial begin
$display("WIDTH = %0d", `WIDTH);
`ifdef FROM_FILELIST
$display("FROM_FILELIST is defined");
`else
$display("FROM_FILELIST is NOT defined");
`endif
end
endmoduleThe include can only be found through a search path, and FROM_FILELIST is defined nowhere in the source. Both come from the filelist:
$ cat f26.f
+incdir+/home/engineer/chip_a/rtl/include
+define+FROM_FILELIST
/tmp/dut26.sv
$ iverilog -g2012 -o /tmp/d26.vvp -f f26.f
$ echo $?
0
$ vvp /tmp/d26.vvp < /dev/null
WIDTH = 16
FROM_FILELIST is definedBoth options took effect from inside the list, with empty stderr. And the command-line equivalent gives the identical output:
$ iverilog -g2012 -I/home/engineer/chip_a/rtl/include -DFROM_FILELIST -o /tmp/d26b.vvp /tmp/dut26.sv
$ vvp /tmp/d26b.vvp < /dev/null
WIDTH = 16
FROM_FILELIST is definedTwo routes, one result. So the choice between them is about where the fact lives, not about what it does — which is the entire argument.
3. Where the Fact Should Live
| Put it in | When |
|---|---|
| The filelist | it is a property of the design: an include path, a define the RTL reads, the language version |
| The command line | it is a property of this invocation: the output file, the top module, a seed, verbosity |
| Neither | it can be derived — a path from $PROJECT_ROOT, a top module from the test name |
The test is a question: would a different tool compiling this design need it? An include path, yes — lint needs it as much as simulation. An output file name, no.
Three consequences:
Lint and simulation stop disagreeing. Chapter 24.5 §5 measured two invocations of one tool in one project passing different flags — a shell script with -DADDR_W=32 -Irtl/include -f filelists/chip.f and a Python driver with only -DFAST_SIM. Whatever the second compiled, it was not the design the first compiled. Moving the two options into the list makes that impossible.
A new tool is cheap to add. Pointing a formal tool at an existing filelist costs one command; reconstructing someone's command line from a four-hundred-line wrapper costs a day.
And the options are reviewed. +define+FAST_SIM appearing in a committed filelist is a diff with an author. The same flag in a wrapper script is also a diff — but one nobody reads, because wrapper scripts change constantly and filelists do not.
4. Scope: Does an Option Apply to the Whole List?
The question that decides how a filelist can be composed, and the answer is tool-dependent in a way worth being careful about.
+define+ and +incdir+ are generally global to the compilation, not positional — a define placed at the bottom of a list still applies to files above it, because the preprocessor state is built before parsing proper. That is why putting them at the top is a convention rather than a requirement.
-timescale and language-version flags are frequently positional, applying from that point onward. This is the dangerous category, because it means where a line sits changes behaviour.
And -y library paths are global, which is why they are so hard to audit: a four-line filelist with a -y can compile four hundred files, as Chapter 23.3 §4 noted.
Rather than rely on a belief about a particular tool, put the global options at the top and keep them there:
# ---- options -------------------------------------------------
-g2012
+incdir+$PROJECT_ROOT/rtl/include
+incdir+$PROJECT_ROOT/verif/inc
+define+FROM_FILELIST
+define+ADDR_W=32
# ---- design --------------------------------------------------
$PROJECT_ROOT/rtl/include/chip_defs.svh
$PROJECT_ROOT/rtl/core/chip_top.sv
$PROJECT_ROOT/rtl/axi/axi_master.sv
# ---- testbench -----------------------------------------------
$PROJECT_ROOT/tb/tests/tb_chip.svFour things that layout buys: the options are findable, a reviewer can see the whole configuration without reading four hundred paths, a positional option cannot accidentally apply to half the design, and the section comments survive because Chapter 26.1 §4's reader strips # lines.
5. Variables in a Filelist
$PROJECT_ROOT above is doing real work — it is what makes the list readable and machine-independent, per Chapter 26.1 §5. It also has a cost, and it is worth being precise about who expands it.
The tool expands it, if the tool supports environment-variable expansion in filelists — most simulators do, and Icarus does not document it as a feature, so this course does not claim it. A filelist with a $ in it is therefore tool-dependent.
Or the flow expands it, which is portable and explicit:
# expand a filelist's variables into a concrete list the tool can read
envsubst < filelists/chip.f.in > "$WORK_DIR/chip.f"envsubst is part of GNU gettext and is not universally installed, so the shell-only version is worth having:
expand_filelist() { # stdin -> stdout, expanding $VAR and ${VAR}
while IFS= read -r line || [ -n "$line" ]; do
eval "printf '%s\n' \"$line\""
done
}And that eval is a real hazard, not a stylistic one. A filelist line containing $(rm -rf /) or a backtick would be executed. Chapter 19.5 §6 measured command injection through exactly this construct. The safe version restricts what it will expand:
expand_filelist() {
while IFS= read -r line || [ -n "$line" ]; do
case $line in
(*'$('*|*'`'*) printf 'filelist: refusing to expand: %s\n' "$line" >&2; return 1 ;;
esac
eval "printf '%s\n' \"$line\""
done
}Refusing is the right behaviour: a filelist should contain paths and options, and a command substitution in one is either a mistake or an attack. Chapter 20.4's argument for a positive rule applies — and this is the weaker deny-list version, which is honest about being a second line of defence rather than a guarantee.
6. Auditing the Options in a Filelist
#!/usr/bin/env bash
# flopts — show the options a filelist sets, following nested lists
# usage: flopts <filelist>
# exit: 0 ok · 1 something to look at · 2 unusable
set -o nounset
[ $# -ge 1 ] || { printf 'usage: flopts <filelist>\n' >&2; exit 2; }
[ -r "$1" ] || { printf 'flopts: cannot read %s\n' "$1" >&2; exit 2; }
warn=0
scan() {
local fl=$1 depth=$2 line path anchor
anchor=$(cd -- "$(dirname -- "$fl")" && pwd -P) || return 2
local n=0
while IFS= read -r line || [ -n "$line" ]; do
n=$(( n + 1 ))
line=${line%%#*}
line=${line%$'\r'}
[ -n "${line//[[:space:]]/}" ] || continue
case $line in
(-[fF]*)
path=${line#-[fF]}; path=${path# }
case $path in (/*) : ;; (*) path=$anchor/$path ;; esac
printf '%*s-- nested: %s\n' "$depth" "" "$path"
if [ -r "$path" ]; then scan "$path" $(( depth + 2 )) || return $?
else printf '%*s ** cannot read it\n' "$depth" ""; warn=1; fi ;;
(+incdir+*)
printf '%*sincdir %s\n' "$depth" "" "${line#+incdir+}" ;;
(+define+*)
printf '%*sdefine %s\n' "$depth" "" "${line#+define+}" ;;
(-y*|*+libext+*)
printf '%*slibrary %s ** a -y path compiles files this list never names\n' "$depth" "" "$line"
warn=1 ;;
(*timescale*)
printf '%*stimescale %s ** positional in some tools: line %s of %s\n' \
"$depth" "" "$line" "$n" "${fl##*/}"
warn=1 ;;
(-*|+*)
printf '%*soption %s\n' "$depth" "" "$line" ;;
(*) : ;; # a source path: not this tool's business
esac
done < "$fl"
}
scan "$1" 0 || exit 2
[ "$warn" -eq 0 ] || printf '\nflopts: items marked ** need a human\n' >&2
[ "$warn" -eq 0 ]$ cat -n filelists/top.f
1 rtl/core/chip_top.sv
2 +incdir+rtl/include
3 -y ip/vendor_axi/rtl
4 # a comment
5 -f filelists/sub.f
$ flopts filelists/top.f
incdir rtl/include
library -y ip/vendor_axi/rtl ** a -y path compiles files this list never names
-- nested: /home/engineer/chip_a/filelists/filelists/sub.f
** cannot read it
flopts: items marked ** need a human
$ echo $?
1Look at the nested path: filelists/filelists/sub.f, and cannot read it. That doubled component is exactly what the tool produces too — Chapter 26.6 §1 measures iverilog failing on the same list with unable to read nested command file (filelists/filelists/sub.f). So the script is right and the fixture's filelist is wrong.
Six decisions:
Source paths are deliberately not printed. This tool answers "what is the configuration", and a four-hundred-line path dump would bury it. Chapter 24.6's point about choosing the shape of the answer.
-y and +libext+ are flagged rather than listed, because they mean the filelist is not a complete statement of what gets compiled — the most important thing this tool can tell you.
timescale is flagged with its line number and file, because it is the positional option, so where it is matters and the reader needs to find it.
The nested-list anchor is the containing list's directory, which Chapter 26.6 §1 measures as iverilog's own behaviour for nested -f paths — even though source paths in the same tool are current-directory-relative. Two base directories in one tool, and the transcript above shows the script agreeing with the tool: both produce filelists/filelists/sub.f and both fail.
So the output is telling you the fixture's top.f is wrong, not that the script is. And that is only visible because the script prints the resolved path. A version that silently skipped unreadable nested lists would have printed a clean report of an incomplete configuration, which is the failure mode Chapter 25.1's callout was about.
The ambiguity is still real for other tools, which resolve nested paths against the current directory and which a filelist does not distinguish — so the honest extension is the -b cwd|filelist option Chapter 23.3 §4's resolver grew:
case $base in (cwd) anchor=$root ;; (filelist) anchor=$here ;; esacwarn is a flag, not an early exit, so the whole configuration is printed before the summary.
And the exit status distinguishes "clean" from "needs a human" rather than "worked" from "crashed" — Chapter 21.4, and it makes the tool usable in a gate.
Practice
- Find every
+incdir+and+define+in your project's filelists. How many, and are they at the top? - Find every
-Dand+define+on a command line in your build scripts. Should any of them be in the list? - Reproduce §2: a header reachable only via
+incdir+, and a define used only via`ifdef. Confirm both work from inside the list. - Do the same on the command line and confirm the output is identical.
- Apply §3's test to five options in your flow: would another tool compiling this design need it?
- Find an unguarded
`definein your headers and confirm a+define+for it is ignored. - Add the
`ifndefguard and confirm the override now works. - Use
iverilog -E(or your tool's preprocessor flag) to find out which copy of a header was actually read. - Find a
-timescaleor`timescalein your flow. Is its position load-bearing? - Write a
.f.inwith$PROJECT_ROOTand expand it two ways — withenvsubstif you have it, and with the shell function. - Put
$(echo hi)in a.f.inand confirm the safe version refuses it. - Write
floptsand run it on a filelist with a nested list and a-y. Does it exit 1?
Check Your Understanding
- Why prefer the
+-prefixed option forms? - What did §2 establish, and why does the chapter's argument depend on it?
- What is the test for whether an option belongs in the filelist?
- What did two invocations of one tool in one project turn out to be passing?
- Does moving a define into the filelist make it authoritative?
- Which options are global and which are positional?
- Why put the global options at the top anyway?
- Who expands
$PROJECT_ROOTin a filelist? - What is the hazard in the shell expansion function, and what does the safe version do?
- Why does the expanded filelist belong in the run directory?
- Why does
floptsnot print source paths?
7. Remember
+incdir+,+define+,+libext+are portable;--prefixed options are per-tool.- Options inside a
-flist take effect — measured, with the command line giving identical output. - The test: would another tool compiling this design need it? If yes, the filelist; if it is about this invocation, the command line.
- Moving a define into the list makes it visible, not authoritative. An unguarded header
`definestill wins, silently. `ifndefin the header is what makes an override possible.+define+and+incdir+are global;-timescaleand language flags are often positional;-yis global and invisible.- Put global options at the top, in a commented section.
- A
$in a filelist is tool-dependent. Expand it in the flow if you need portability. - Never
evala filelist line without refusing$(and backticks. - Commit
chip.f.in, generatechip.finto the run directory — machine-independent in the repository, resolved in the record. - Write the expanded list before the compile and never touch it again.
- An audit tool should print the configuration, not the paths, and flag
-yandtimescalefor a human. - A nested-list path has two possible bases and the file does not say which. Make it an option, and always print the resolved path so a wrong base is visible.
8. What Is Next
Chapter 26.3 produces a list from the repository rather than maintaining one by hand — and is explicit about exactly what the generation rule will miss, including a measured case where the find on your PATH is not the find in your script.
Where this fits
Part of the Linux & Bash curriculum.
