Complex Subroutines in GNU Make

| No Comments

Another in a very short "series" about make. 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.

Introduction

foo.o : foo.c
    gcc -O -c foo.c -o foo.o

"If foo.c is newer than foo.o, rebuild foo.o by running gcc -O -c foo.c". Files. Timestamps. Scripts (Bourne shell).

If 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 constants, using make variables; our example stanza might then become:

CC = gcc
CFLAGS = -O

foo.o : foo.c
    $(CC) $(CFLAGS) -c foo.c -o foo.o

Now we can change from -O to -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 $@

Simple Subroutines

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.

Complex Subroutines

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)

(Note: $(INCDIRS:%=-I%) means "take the value of the variable 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 (a.out)
  • the include directories required (INCDIRS)
  • the library directories required (LIBDIRS)
  • the source files to feed in (SRCS_V)

There could easily be a few more. There is simply no way to squeeze all those parameters through a normal make pattern 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 make is 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 make 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.]

Leave a comment

About this Entry

This page contains a single entry by Will Partain published on September 22, 2009 1:23 AM.

The Humble Make Stanza was the previous entry in this blog.

Avoid the Puppet SVN pre-commit script is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.