Another in a very short "series" about
If you need a small
make refresher, see
this earlier entry.
Here we consider a way to write complex (multi-parameter) "subroutines" for GNU Make; this is important for code reuse.
foo.o : foo.c gcc -O -c foo.c -o foo.o
foo.c is newer than
foo.o by running
gcc -O -c foo.c". Files. Timestamps. Scripts (Bourne shell).
make were nothing but that, it would be useful but
turgidly repetitive. For example, in the
Makefile for a C
program with 100 C files, you would have 100
nearly-identical stanzas like the example above.
If you decided to change from
gcc -O to
gcc -O2, you
would have 100 stanzas to change.
The first way we improve things is to lift out the
make variables; our example stanza might
CC = gcc CFLAGS = -O foo.o : foo.c $(CC) $(CFLAGS) -c foo.c -o foo.o
Now we can change from
-O2 with a single keystroke.
Make also has a bunch of "automatic variables", which take
on certain values in the context of a specific target. The
most important are:
$@ the name of the target $< the name of the first prerequisite
Our example now becomes:
foo.o : foo.c $(CC) $(CFLAGS) -c $< -o $@
Imagine we were concerned with compiling two C files; we might have:
foo.o : foo.c $(CC) $(CFLAGS) -c $< -o $@ bar.o : bar.c $(CC) $(CFLAGS) -c $< -o $@
The two stanzas are identical except for the target-file "stem". Make lets us common-up all such stanzas with a single pattern rule:
%.o : %.c $(CC) $(CFLAGS) -c $< -o $@
The bit of the filename matched by the
% in the pattern is
called the "stem" and is available in the automatic variable
$*; so the rule could also be written as:
%.o : %.c $(CC) $(CFLAGS) -c $*.c -o $*.o
A useful way to think of a pattern rule is as a "make subroutine" with exactly one parameter (the stem), which you have to hide in the target filename. (Pretty horrible programming-language syntax, but...)
Pattern rules are a good idea in 100% of the cases where they apply.
In our EDA world, tool invocation is rarely simple enough to be susceptible to the wiles of a ("single parameter") pattern rule.
Here's how you might compile a bunch of Verilog with Icarus:
a.out : $(SRCS_V) iverilog -o $@ -Idir1 -Idir2 -y dir1 -y dir2 $(SRCS_V)
After we lift out constants, we might get something like (and this is still simplified compared to Real Life...):
IVL = iverilog INCDIRS = dir1 dir2 LIBDIRS = $(INCDIRS) a.out : $(SRCS_V) $(IVL) -o $@ $(INCDIRS:%=-I%) $(LIBDIRS:%=-y%) $(SRCS_V)
$(INCDIRS:%=-I%) means "take the value of the
INCDIRS and put a
-I in front of every word".)
The problem here is that the "rule" for compiling Verilog with Icarus has several "parameters":
- the name of the desired output (
- the include directories required (
- the library directories required (
- the source files to feed in (
There could easily be a few more. There is simply no way to
squeeze all those parameters through a normal
rule at once. (Well, you could... You could encode all
needed information in the target name. You might say:
compile : compile++a.out++INCDIRS=dir1+dir2++LIBDIRS=dir1+dir2++
and then have a pattern rule:
compile% : # now $* includes all the extra info <fancy script that unpicks $* and then does what it likes>
But that's really, really horrible!)
What we want is to be able to vary the value of the
"parameter variables" depending on the target that
trying to rebuild. Happily, GNU Make provides an obscure
feature to do this: target-specific make variables.
I propose the convention that target-specific variables are in lower case, so they stand out from regular variables which are (by convention) in upper case.
You can make a very simple Makefile to see target-specific variables in action:
a : foo = a_value b : foo = b_value a : @echo foo=$(foo) b : @echo foo=$(foo) c : @echo foo=$(foo)
When you run
make a b c, you will see:
foo=a_value foo=b_value foo=
Back to our Icarus Verilog example. We might end up with a rule something like:
%.icarus : $(srcs_v) $(IVL) -o $@ $(incdirs:%=-I%) $(libdirs:%=-y%) $(srcs_v)
This is, in effect, a "subroutine" for GNU Make with three parameters. We can now invoke this "subroutine" for two (or more) different Icarus runs:
foo.icarus : srcs_v = foo_top.v foo.icarus : incdirs = include foo.icarus : libdirs = lib bar.icarus : srcs_v = bar_top.v bar_extra.v bar.icarus : incdirs = wibble wobble bar.icarus : libdirs = $(incdirs)
Nobody said it was pretty, but it does mean that an
arbitrarily-complex script for doing a piece of an EDA flow can
be captured by a pattern rule (using target-specific
variables) -- in exactly one place. This can only be good for
increasing the robustness of any
make-based build system.
[An earlier version of this note appeared in Verilab's internal newsletter.]