Make for simulation flows
- Describe a simulation flow as Make targets and prerequisites
- Use variables, automatic variables and pattern rules
- Mark phony targets
- Build only what changed
- Provide
lint,sim,regressandcleantargets that behave the same for every engineer and in CI
Explanation
Why Make for RTL work
A shell script runs every step every time. Make records how each output is produced from its inputs and rebuilds an output only when an input is newer. For a design with dozens of testbenches, that turns a five-minute full rebuild into a few seconds after a one-line change. A Makefile is also documentation: make sim TEST=fifo_full is a command anyone can repeat.
Rules
target: prerequisites
recipe
The recipe lines must start with a tab, not spaces. Make runs the recipe when the target is missing or older than any prerequisite. Each recipe line runs in its own shell, so cd dir on one line does not affect the next.
Variables
| Form | Meaning |
|---|---|
SIM ?= iverilog |
Set only if not already set (environment or command line can override) |
RTL := $(wildcard rtl/*.v) |
Expand immediately, once |
TEST = tb_counter |
Expand each time it is used |
make TEST=tb_fifo |
Override on the command line |
Automatic variables and pattern rules
| Variable | Value in a recipe |
|---|---|
$@ |
The target being built |
$< |
The first prerequisite |
$^ |
All prerequisites, without duplicates |
$* |
The stem matched by % in a pattern rule |
A pattern rule describes a family of targets. This rule says how to build any build/X.vvp from tb/X.v plus the RTL:
build/%.vvp: tb/%.v $(RTL) | build
iverilog -g2012 -Wall -s $* -o $@ $^
The | build part is an order-only prerequisite: the directory must exist, but its timestamp changing does not force rebuilds.
Phony targets
Targets like sim and clean are names of actions, not files. Declare them .PHONY so a stray file called clean cannot stop the recipe from running.
Failing correctly
Make stops at the first recipe line that exits non-zero. Keep that property: do not hide failures with - prefixes or || true unless a later step checks the result. Add .DELETE_ON_ERROR: so a half-written output from a failed recipe is removed instead of looking up to date on the next run.
Worked example
A small project has rtl/counter.v, rtl/fifo.v, and one testbench per test in tb/. Save this as Makefile:
SIM ?= iverilog
RUN ?= vvp -n
LINT ?= verilator --lint-only -Wall
TEST ?= tb_counter
SEED ?= 1
RTL := $(wildcard rtl/*.v)
TESTS := $(basename $(notdir $(wildcard tb/tb_*.v)))
LOGS := $(addprefix build/,$(addsuffix .log,$(TESTS)))
.PHONY: all sim regress lint clean
.DELETE_ON_ERROR:
.SECONDARY:
all: regress
build:
mkdir -p $@
build/%.vvp: tb/%.v $(RTL) | build
$(SIM) -g2012 -Wall -s $* -o $@ $^
build/%.log: build/%.vvp
$(RUN) $< +seed=$(SEED) > $@ 2>&1
grep -q 'TEST PASSED' $@
sim: build/$(TEST).log
@cat $<
regress: $(LOGS)
@echo "regression passed: $(words $(LOGS)) tests"
lint:
$(LINT) $(RTL)
clean:
rm -rf build
Typical use:
make lint
make sim TEST=tb_fifo
make -j4 regress
touch rtl/fifo.v && make regress # rebuilds and reruns every test
make regress # nothing to do: all logs are up to date
build/%.log depends on the compiled .vvp, which depends on the testbench and every RTL file. Editing one testbench reruns only that test; editing RTL reruns them all. If grep -q 'TEST PASSED' fails, .DELETE_ON_ERROR removes the log, so the failing test runs again next time rather than being treated as done. Without .SECONDARY:, Make treats the .vvp files as intermediate files of a rule chain and deletes them after the logs are built; keeping them lets you rerun vvp by hand while debugging. make -j4 runs up to four independent tests at once because each log is a separate target. Verilator lint usually needs a top module per invocation in larger designs; for a project with several tops, lint each with --top-module.
Check yourself
Why must recipe lines start with a tab? What do $@, $< and $^ expand to in the .vvp rule? Why is build an order-only prerequisite? What would go wrong without .DELETE_ON_ERROR: when a test fails? Why are sim and clean declared .PHONY?
Practice: Make for simulation flows
Add a waves target that runs one test with +dump so the testbench writes build/$(TEST).vcd, and make it depend on the same .vvp rule. Add a SEEDS ?= 1 2 3 variable and a seeds target that runs $(TEST) once per seed into build/$(TEST)_seed<N>.log. Prove that make regress does nothing on a second run and reruns only one test after you touch its testbench.
Practice work is non-certified and is not stored by ASIC Campus.