Skip to content
VLSI Mentor

Makefile · Part I · Module 3 · Variables and Make Functions

Automatic Variables: $@, $< and $^

Every rule you have written so far names its files twice — once after the colon, where they drive rebuilding, and once in the command, where they do the work. Two earlier chapters flagged that duplication and promised a fix here. This is it: a small set of variables Make fills in for each rule as it runs, holding the target and the prerequisites it already knows about.

Intermediate20 min readMakefileGNU MakePart IVariablesRecipes

Part I · Module 3 · Chapter 3.3 · Automatic Variables: $@, $< and $^

1. The Duplication Earlier Chapters Left Open

Here is a rule from Chapter 2.1, unchanged:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the names appear twice
build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	@mkdir -p build
	@cat rtl/defines.vh rtl/alu.v rtl/tb_top.v > build/sim.img

Three source files after the colon, the same three in the command. build/sim.img twice, build three times. Both Chapter 1.3 and Chapter 2.1 pointed at this and said the fix was coming in this chapter, because keeping the two lists in step by hand is a real source of bugs:

  • Add a source to the command only, and the rule never rebuilds when it changes — the stale output of Chapter 2.3.
  • Add it after the colon only, and the tool never reads it.

The fix is not a cleverer way to write the list. It is that Make already knows both lists — it had to, in order to decide whether to run the recipe at all. Automatic variables let the recipe ask.

2. What an Automatic Variable Is

An automatic variable is a variable Make sets by itself, freshly, for each rule it runs. You never assign one. Their names are short and punctuation-heavy, which is the only hard part about them:

VariableHolds
$@the target of this rule
$<the first prerequisite
$^all the prerequisites, duplicates removed
$?only the prerequisites that are newer than the target

Two notes before the examples. These are ordinary Make variables as far as the $ rule goes, so they are expanded by Make before the shell sees anything — the boundary from Chapter 1.4. And because they are Make's, you write $@ and not $$@.

They are also per rule. The same $@ means a different thing in every recipe in the file, which is exactly what makes one written rule reusable.

3. $@ — the Target

$@ is the name of the thing being built:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
out.txt: in.txt
	@echo "building $@"
	@cp in.txt $@
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
building out.txt

The usual use is the output redirection or the tool's -o option, so that the file the recipe writes can never disagree with the file the target promised. That was Chapter 2.1's central bug — a recipe writing a different filename than its target — and $@ makes it structurally impossible.

4. $< — the First Prerequisite

$< is the first name after the colon:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
out.txt: in.txt
	@cp $< $@

One line, no filenames. $< is the right choice when a rule has one real input and the other prerequisites are there for ordering or for rebuild correctness — a defines file, a generated directory, a configuration. Chapter 4.1's pattern rules lean on it heavily.

5. $^ — All the Prerequisites

$^ is every prerequisite, space-separated:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
all.txt: first.txt second.txt third.txt
	@cat $^ > $@

This is the one that removes §1's duplication. The command now reads "concatenate everything this target depends on", which is both shorter and impossible to get out of step.

6. Seeing All Four Together

The quickest way to learn these is to print them. This rule does nothing but report:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
out.txt: first.txt second.txt third.txt
	@echo "target        $$@ = [$@]"
	@echo "first prereq  $$< = [$<]"
	@echo "all prereqs   $$^ = [$^]"
	@echo "newer prereqs $$? = [$?]"
	@cat $^ > $@

The $$@ in the labels is a literal dollar for the shell to print, as Chapter 1.4 covered; the [$@] next to it is the real variable.

First build, with no out.txt yet:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
target        $@ = [out.txt]
first prereq  $< = [first.txt]
all prereqs   $^ = [first.txt second.txt third.txt]
newer prereqs $? = [first.txt second.txt third.txt]

Now touch only second.txt and run again:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
target        $@ = [out.txt]
first prereq  $< = [first.txt]
all prereqs   $^ = [first.txt second.txt third.txt]
newer prereqs $? = [second.txt]

Three of the four are identical and $? changed. That is the distinction worth keeping:

  • $^ is structural — what this target is made of. It never changes.
  • $? is situational — what changed this time. On a first build the target does not exist, so every prerequisite counts as newer and $? equals $^; afterwards it narrows to what you touched.

$? is useful for a step that can process just the changed files, such as copying only updated results into a report directory. For a compile that must read everything, $^ is the correct choice — using $? there would compile a design out of one file.

Seeing the substitution itself

Printing the values is one way to learn them. The other is to ask Make what the shell is actually going to receive, using make -n from Chapter 1.4:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	mkdir -p $(@D)
	cat $^ > $@
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -n
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
mkdir -p build
cat rtl/defines.vh rtl/alu.v rtl/tb_top.v > build/sim.img

Two short lines in the Makefile, two fully written-out commands in the output. Nothing with a $ in it survives to the shell — Make has already done the substitution, exactly as it does for an ordinary variable.

This is the single most useful habit for debugging automatic variables. When a recipe does something unexpected, make -n shows you the real command, with every $@ and $^ resolved, before anything runs.

Three rows, each flowing left to right. Top row: the rule's target, build slash sim dot img, is what dollar-at holds. Middle row: the first prerequisite, rtl slash defines dot vh, is what dollar-less-than holds. Bottom row: all three prerequisites together are what dollar-caret holds.build/sim.imgthe target$@holds the targetrtl/defines.vhfirst prerequisite$<holds the first oneall three sourcesevery prerequisite$^holds them all12
Figure 1 — one rule, and the part of it each automatic variable picks up. Make already holds all three pieces because it needed them to decide whether to run the recipe; the automatic variables simply let the recipe read them instead of repeating the names by hand.

7. Rewriting the Rule From §1

Now the payoff, measured. Before, with every name written out:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — before
build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	@mkdir -p build
	@cat rtl/defines.vh rtl/alu.v rtl/tb_top.v > build/sim.img
	@echo "  built build/sim.img"

After, with automatic variables:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — after
build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v
	@mkdir -p $(@D)
	@cat $^ > $@
	@echo "  built $@"

Both print built build/sim.img, and the artifacts they produce are byte-identical — the same md5 on both runs. The recipe now contains no filenames at all: every name comes from the one place that already had to be correct, the rule's own header.

What that buys you is one-line maintenance. Adding a fourth source means editing the prerequisite list and nothing else:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — one edit adds a source
build/sim.img: rtl/defines.vh rtl/alu.v rtl/tb_top.v rtl/regfile.v
	@mkdir -p $(@D)
	@cat $^ > $@
	@echo "  built $@"
	@echo "  newer than the target: [$?]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
touch rtl/regfile.v
make
make
touch rtl/alu.v rtl/regfile.v
make
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output
  built build/sim.img
  newer than the target: [rtl/regfile.v]
make: 'build/sim.img' is up to date.
  built build/sim.img
  newer than the target: [rtl/alu.v rtl/regfile.v]

The new file was picked up by the command automatically, the skip still works, and $? reported exactly what had been touched each time.

8. $(@D) and $(@F) — Splitting a Path

$@ can hold a path, and two modifiers split it: $(@D) is the directory part and $(@F) is the filename part.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
build/sub/out.txt: first.txt
	@echo "whole  $$@    = [$@]"
	@echo "dir    $$(@D) = [$(@D)]"
	@echo "file   $$(@F) = [$(@F)]"
	@mkdir -p $(@D)
	@cp $< $@
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
whole  $@    = [build/sub/out.txt]
dir    $(@D) = [build/sub]
file   $(@F) = [out.txt]

mkdir -p $(@D) is the idiomatic answer to a mistake from Chapter 2.1: Make does not create a target's directory for you. Written this way the recipe creates whatever directory its own target needs, with no hard-coded build anywhere — so moving the output directory is one edit to the target, not two.

9. $^ and $+: What Happens to Duplicates

$^ quietly removes repeated prerequisites. $+ keeps them, in order:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
dup.txt: first.txt second.txt first.txt
	@echo "caret $$^ = [$^]"
	@echo "plus  $$+ = [$+]"
	@touch dup.txt
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
caret $^ = [first.txt second.txt]
plus  $+ = [first.txt second.txt first.txt]

$^ is almost always what you want — a file listed twice should be read once. $+ exists for the rare case where repetition is meaningful, most famously a linker that needs a library named more than once. In a Makefile a duplicate prerequisite is usually a mistake, and $^ silently tidying it up is a convenience rather than something to rely on.

10. One Rule, Several Targets

$@ is set per target, not per rule — which becomes visible when one rule declares more than one:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
.PHONY: all
all: build/a.txt build/b.txt

build/a.txt build/b.txt: rtl/alu.v
	@mkdir -p $(@D)
	@echo "  running for target [$@]"
	@cp $< $@
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  running for target [build/a.txt]
  running for target [build/b.txt]

The recipe ran twice, once per target, with $@ holding a different name each time. Make treats a rule with several targets as several rules that happen to share a recipe, and that only works because the recipe refers to $@ rather than a fixed filename.

This is the first hint of why these variables matter so much later: a recipe written in terms of $@ and $< is no longer tied to one file, so it can be applied to many. Chapter 4.1 takes that to its conclusion with pattern rules, where a single rule serves every file of a kind.

The modifier family

$(@D) and $(@F) are not special cases — the same D and F modifiers work on the others:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
build/out.txt: rtl/alu.v rtl/tb_top.v
	@echo "  $$(@D) = [$(@D)]"
	@echo "  $$(<F) = [$(<F)]"
	@echo "  $$(^D) = [$(^D)]"
	@echo "  $$(^F) = [$(^F)]"
	@touch $@
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  $(@D) = [build]
  $(<F) = [alu.v]
  $(^D) = [rtl rtl]
  $(^F) = [alu.v tb_top.v]

Note $(^D): it gives one directory per prerequisite, so two files in the same directory produce rtl rtl rather than a single rtl. That is consistent — the modifiers apply element by element — but it surprises people expecting a tidy list. In practice $(@D) is the one you will reach for constantly and the rest are occasional.

11. They Only Exist Inside a Recipe

One restriction, and it is the single thing people get wrong with these variables. An automatic variable has a value only while a recipe is running. Used anywhere else in the Makefile it is simply empty:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — WRONG := $@ at file scope
out.txt: in.txt
	@echo "inside the recipe, $$@ = [$@]"

show:
	@echo "WRONG = [$(WRONG)]"

WRONG := $@
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
inside the recipe, $@ = [out.txt]
WRONG = []

WRONG is empty. At the moment that line was read there was no rule being built, so $@ had nothing to refer to — and as Chapter 3.1 established, an empty expansion is not an error. The same applies to using $@ in a prerequisite list: there is no target yet when the list is being read.

The practical rule: automatic variables belong on the tab-indented lines. If you want to compute a name from a target outside a recipe, you need the target's name spelled out, or a pattern rule — which is where Chapter 4.1 picks this up and where these variables become genuinely essential rather than merely convenient.

12. An RTL Flow With No Repeated Filenames

Putting it together with the knobs from Chapter 3.2. This version is portable and was executed, standing in for a simulator:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — portable stand-in, executed as shown
.PHONY: all run clean
.DEFAULT_GOAL := all

SOURCES := rtl/defines.vh rtl/alu.v rtl/tb_top.v
TEST    ?= smoke
SEED    ?= 1

all: build/sim.img

build/sim.img: $(SOURCES)
	@echo "  [compile] $@ from $? "
	@mkdir -p $(@D)
	@cat $^ > $@

build/run.log: build/sim.img
	@echo "  [run] test=$(TEST) seed=$(SEED) -> $@"
	@mkdir -p $(@D)
	@echo "test=$(TEST) seed=$(SEED)" > $@
	@wc -l < $< | tr -d ' ' >> $@

run: build/run.log

clean:
	@rm -rf build

Three things to notice, all of them consequences of this chapter:

  • No filename appears in any recipe. Every one comes from $@, $<, $^ or $(@D).
  • build is never written in a recipe. mkdir -p $(@D) derives it from whichever target is being built, so both rules create their own directory with the same line.
  • $< and $^ are used for different jobs. The compile reads everything ($^); the run step reads only the simulation image ($<), even though its rule could grow more prerequisites later.

The simulator version is illustrative — vcs is not installed here and was not run:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — illustrative; vcs was not executed
.PHONY: all run clean
.DEFAULT_GOAL := all

SOURCES := rtl/defines.vh rtl/alu.v rtl/tb_top.v
SIM     ?= vcs
TOP     ?= tb_top
TEST    ?= smoke

all: build/simv

build/simv: $(SOURCES)
	mkdir -p $(@D)
	$(SIM) -full64 -sverilog -top $(TOP) $^ -o $@

build/run.log: build/simv
	mkdir -p $(@D)
	./$< +UVM_TESTNAME=$(TEST) -l $@

run: build/run.log

clean:
	rm -rf build

Read the compile line: $^ is the source list and $@ is the output. The rule is now a statement of the relationship rather than a command with names in it, and that is the form a pattern rule can generalise. Note also what $^ gives the simulator here — every prerequisite including defines.vh, which a real flow may or may not want on the command line. That is a judgement call about the tool, not about Make, and Chapter 2.3 covered why the prerequisite list and the command line are not automatically the same list.

13. Choosing Between Them, and Reading Someone Else's Recipe

In practice the choice is nearly always between $< and $^, and the question is how many real inputs the rule has:

SituationUseWhy
one input, possibly plus ordering prerequisites$<states that only the first one is read
the command must read every prerequisite$^the list and the command cannot drift apart
the output filename or -o option$@the recipe can never write the wrong name
creating the target's own directory$(@D)no hard-coded output path
a step that can process only what changed$?genuinely incremental work
anything outside a recipenone of themthey are empty there (§11)

The habit that matters more than memorising the table: when a rule has exactly one real input, write $< even if $^ would also work today. $^ keeps working and quietly changes meaning the day somebody adds a second prerequisite; $< says what you meant.

Decoding three real recipe lines

Reading these is a skill of its own, because a recipe written with automatic variables carries no filenames to orient you. Here are three, all portable and all executed:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
build/filelist.f: rtl/alu.v rtl/tb_top.v
	@mkdir -p $(@D)
	@printf '%s\n' $^ > $@

build/first.copy: rtl/alu.v rtl/tb_top.v
	@mkdir -p $(@D)
	@cp $< $@

build/stamp.ok: build/filelist.f
	@mkdir -p $(@D)
	@echo "made from $(<F) in $(<D)" > $@

Line 1 writes each prerequisite on its own line into the target. printf '%s\n' repeats its format once per argument, and $^ supplies the arguments:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — build/filelist.f
rtl/alu.v
rtl/tb_top.v

That is a compile filelist generated from the rule's own dependency list, which is a genuinely useful shape — and it is the honest version of the problem Chapter 1.2 warned about, because here the filelist and the prerequisites come from the same place by construction. Chapter 5.2 takes it further.

Line 2 copies only the first prerequisite, despite two being listed:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — first line of build/first.copy
module alu;

rtl/tb_top.v was never read. Whether that is a bug depends on intent — which is exactly why $< and $^ should be chosen deliberately rather than by habit.

Line 3 uses the modifiers on $< rather than $@:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — build/stamp.ok
made from filelist.f in build

And when a line is genuinely unclear, do not reason about it — expand it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make -n build/filelist.f build/first.copy
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
mkdir -p build
printf '%s\n' rtl/alu.v rtl/tb_top.v > build/filelist.f
mkdir -p build
cp rtl/alu.v build/first.copy

Every variable resolved, and line 2's single-file copy is now impossible to miss.

14. Common Mistakes

Using $? where $^ was meant. $? is only the changed prerequisites. In a compile it would hand the tool one file after an incremental edit, and the full list only on a first build — a bug that works perfectly the first time you test it.

Writing $$@ in a recipe. That is a literal $ followed by @ for the shell. Automatic variables are Make's, so they take a single $.

Using an automatic variable outside a recipe. It expands to empty, silently (§11).

Forgetting that $< is only the first prerequisite. Reorder the prerequisite list and $< changes meaning. If a rule has one real input, put it first and keep it there.

Assuming $@ creates directories. It names the target; it does not make its path exist. That is what mkdir -p $(@D) is for.

Mixing $(@D) up with $(<D). Both exist — $(<D) is the directory of the first prerequisite. Reaching for the wrong one gives a plausible path that is quietly wrong.

15. Check Your Understanding

Question 1. A rule reads report.txt: a.txt b.txt c.txt. You touch only b.txt and run make. What do $^ and $? each contain, and which should a recipe that regenerates the whole report use?

Question 2. A compile rule uses $? instead of $^. It passes its test on a clean machine and produces a broken artifact for a colleague. Explain both outcomes.

Question 3. OUT := $@ appears at the top of a Makefile, outside any rule. What does OUT hold, and why is there no error?

Question 4. A rule builds build/reports/timing.rpt. Write the one recipe line that guarantees the directory exists, without naming build or reports.

16. Exercise

Create rtl/defines.vh, rtl/alu.v and rtl/tb_top.v — one define and one small module each, five lines in total. Then this Makefile, which is meant to build a simulation image and write a report holding the image's line count.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — as given
.PHONY: all
.DEFAULT_GOAL := all

SOURCES := rtl/defines.vh rtl/alu.v rtl/tb_top.v

all: build/report.txt

build/sim.img: $(SOURCES)
	@mkdir -p build
	@cat rtl/defines.vh rtl/alu.v rtl/tb_top.v > build/sim.img
	@echo "  built build/sim.img"

build/report.txt: build/sim.img $(SOURCES)
	@mkdir -p build
	@echo "  summarising $? into build/report.txt"
	@wc -l < $? > build/report.txt

Tasks:

  1. Rewrite build/sim.img so no filename appears in its recipe.
  2. Run make, then look at build/report.txt. Then touch one source, run again, and look at it a second time. Explain both contents.
  3. Give the corrected Makefile.

17. Remember

  • Make sets automatic variables for you, per rule. You never assign them, and the same $@ means something different in every recipe.
  • $@ is the target, and using it for the output is what makes a recipe unable to write the wrong filename.
  • $< is the first prerequisite — the right choice when a rule has one real input.
  • $^ is all of them, duplicates removed, and it is what replaces a hand-written file list.
  • $? is only what changed. It equals $^ on a first build and narrows afterwards, so a compile that uses it is a bug that passes its first test.
  • $(@D) and $(@F) split the target's path, and mkdir -p $(@D) lets a recipe create its own directory without naming it.
  • $+ keeps duplicate prerequisites where $^ drops them. You will rarely want it.
  • They only have values inside a recipe. At file scope or in a prerequisite list they expand to empty, with no error.
  • Single $, not $$. These are Make's variables, not the shell's.

Your recipes no longer repeat the names in their own rule headers. What is still written out by hand is the prerequisite list itself — three sources named one by one, and a real block has hundreds. Chapter 3.4 builds that list from the directory instead, and transforms one list into another.

Where this fits

Part of the Makefile curriculum.