Attention
This documentation is a work in progress. Expect to see errors and unfinished things.
build-tools makefile
Makefiles
Introduction
The Unix “make” program and its associated Makefiles are old, dating to around 1976. It’s four decades later, and many of today’s practical Makefiles, including our own, seem enormously complicated. It helps to see how they are built up from simpler ideas.
The way we use Makefiles for testing HDL and building artifacts is slightly different from traditional pure-software projects, but everything is still based on the same original concepts.
There are worse places to start learning about Makefiles than Wikipedia. Quoting that article, a Makefile consists of “rules” in the following form:
target: dependencies
system command(s)
This gives the system command(s) needed to “make” target, whenever any of the dependency files change. Note that the line with system command(s) on it must start with a tab character.
Example
Our code base is based in large part on test benches written in Verilog and simulated using Icarus Verilog, so that’s what our examples will cover. For concrete discussion purposes, consider the following Makefile:
all: b2d_check fib_check
# self-checking
b2d_check: b2d_tb
vvp -N b2d_tb
b2d_tb: b2d_tb.v b2d.v
iverilog -o b2d_tb b2d_tb.v b2d.v
# check against golden output file
fib_check: fib.out fib.gold
cmp fib.out fib.gold
@echo PASS
fib.out: fib_tb
vvp -N fib_tb > $@
fib_tb: fib_tb.v fib.v
iverilog -o fib_tb fib_tb.v fib.v
This covers two cases: one where b2d_tb is a self-checking testbench, i.e., where execution of b2d_tb ends with $stop(0) on failure, but $finish(0) on success. In the other case, fib_tb creates an output file that needs to match fib.gold.
Normally, the make program displays each command before it is executed. This is considered a good thing, because it helps isolate the cause of any failures that occur. Referring to the fib_check rule above, the second command
echo PASS
is only executed if the first one succeeds. It is not helpful to see this command, only its result; the prefix @ prevents the usual echo in this case.
Generalization
The above Makefile can be generalized using pattern rules and special Makefile macros. Quoting Wikipedia again:
$@ is a macro that refers to the target
$< is a macro that refers to the first dependency
$^ is a macro that refers to all dependencies
The previous Makefile turns into:
%_check: %_tb
vvp -N $<
%_tb: %_tb.v
iverilog -o $@ $^
%.out: %_tb
vvp -N $< > $@
all: b2d_check fib_check
fib_check: fib.out fib.gold
cmp $^
@echo PASS
b2d_tb: b2d.v
fib_tb: fib.v
Configurability
The next layer of indirection allows command-line overrides of strings used in the Makefile. In particular,
VERILOG = iverilog
%_tb: %_tb.v
$(VERILOG) -o $@ $^
will allow the user to change versions of iverilog on-the-fly, without editing the Makefile, with the command line
make VERILOG=iverilog-0.10
In fact, our setup normally specifies
VERILOG = iverilog$(ICARUS_SUFFIX)
VVP = vvp$(ICARUS_SUFFIX)
allowing a simple
make ICARUS_SUFFIX=-0.10
to configure both the iverilog and vvp version used.
Makefile includes
Finally, we normally take all the generic definitions and put them in a single file called top_rules.mk, which can be shared by the individual Makefiles scattered around in various directories. So a stripped down version of top_rules.mk looks like
VERILOG = iverilog$(ICARUS_SUFFIX)
VVP = vvp$(ICARUS_SUFFIX)
GTKWAVE = gtkwave
VFLAGS =
%_check: %_tb
$(VVP) -N $<
%_tb: %_tb.v
$(VERILOG) $(VFLAGS) -o $@ $^
%.out: %_tb
$(VVP) -N $< > $@
%.vcd: %_tb
$(VVP) -N $< +vcd
%_view: %.vcd %.gtkw
$(GTKWAVE) $^
and the Makefile that references it looks like
include top_rules.mk
all: b2d_check fib_check
fib_check: fib.out fib.gold
cmp $^
@echo PASS
b2d_tb: b2d.v
fib_tb: fib.v
clean:
rm -f *_tb *.vcd *.out
Here we have added two new features: rules to create and display timing diagrams using gtkwave, and the traditional “clean” rule to remove generated files.
Dependencies
What’s not covered here is dependency derivation and/or management, something that gets increasingly complicated as the number of files involved climbs. One approach we use is based on combining the -y and -M options to iverilog. The -y flag will cause iverilog to search directories for files that match unresolved module names. The -M flag emits a list of files used, which can be converted to a dependency list for make.
One more trick
The $@ symbol (target name) can be used to pull in extra configuration for commands. The setup starts by adding an extra element to the generic VFLAGS definition:
VFLAGS = ${VFLAGS_$@}
That gives us a hook to let a specific testbench target add additional parameters, like this:
VFLAGS_b2d_tb = -m ./udp-vpi
This example causes an extra module to be loaded when building b2d_tb, without perturbing the commands to build other _tb targets.
Discussion
There have been many attempts, continuing today, to build on, enhance, or replace make. Two in particular attempt to address the needs of an HDL environment.
Our experiments with these tools have generally been frustrating. We use the flexibility of make to support many combinations of target hardware and application code, as well as manage generated code and self-tests. Such capabilities are not priorities of these more specialized tools.
Cross-check
A demo “project” using the example Makefiles above produces the following output:
iverilog -o b2d_tb b2d_tb.v b2d.v
vvp -N b2d_tb
result x for input x
result 123 for input 123
result 123 for input 123
result 60875 for input 60875
result 13604 for input 13604
result 24193 for input 24193
result 54793 for input 54793
result 22115 for input 22115
result 31501 for input 31501
result 39309 for input 39309
result 33893 for input 33893
result 21010 for input 21010
12 tests passed
PASS
iverilog -o fib_tb fib_tb.v fib.v
vvp -N fib_tb > fib.out
cmp fib.out fib.gold
PASS
The three Makefiles above are all equivalent, in the sense that they produce the same output (when whitespace is ignored).