Skip to content
VLSI Mentor

Makefile · Part I · Module 1 · Introduction to Makefiles

What Is GNU Make and Why Use It?

Every engineering project reaches a point where the same five or ten commands get typed again, in the same order, several times a day — and where getting the order wrong produces a result that looks right. GNU Make exists for exactly that point. This chapter explains what Make is, what problem it was built to solve, and what you can expect it to do for you, then proves it with a Makefile you can run in under a minute.

Foundation14 min readMakefileGNU MakePart IFundamentalsBuild Automation

Part I · Module 1 · Chapter 1.1 · What Is GNU Make and Why Use It?

1. The Problem Make Was Built For

Start with a situation you have probably already been in, in some form.

You are working on a project with a few source files. To see the result of a change you have to do three things in order: generate a list of the files that make up the project, hand that list to a tool that turns them into something runnable, then run the thing it produced. Three commands. You type them, read the output, edit a file, and type them again.

That is tolerable for an afternoon. It stops being tolerable for four reasons, and they arrive in this order.

You have to remember the sequence. Not just the commands but the order, the options, and the exact paths. After a week away from the project, the sequence lives in your shell history and nowhere else.

You have to know what to re-run. You changed one file. Does the list need regenerating? Does everything need rebuilding, or just the part that changed? Guessing costs either time (rebuild everything) or correctness (rebuild nothing and test a stale result).

Nobody else can run it. A colleague who joins the project cannot reconstruct the sequence from the source files. It is not written down anywhere that they can read.

The sequence grows. Six months in it is not three commands, it is eleven, with two of them conditional on what you are trying to do.

A shell script fixes the first and third problems. Write the commands in a file, run the file. That is a genuine improvement and you should reach for it when it is enough.

What a plain script does not fix is the second problem. A script runs every line every time. It has no way to know that one of its eleven steps produced a file that is still newer than all of its inputs and therefore does not need doing again. If you want that, you have to write the comparison logic yourself, for every step, and keep it correct as the project grows.

That is the gap GNU Make fills.

2. What GNU Make Is

GNU Make is a program. It is installed on the machine like any other command, it has a version, and you run it by typing its name:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make --version
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output — your version will differ
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.

That is the version this chapter's transcripts were recorded on. A current Linux machine will report a 4.x version instead; nothing in Part I depends on which of the two you have.

What that program does, stated precisely:

Make reads a file of rules. Each rule says what can be produced, what it is produced from, and the commands that produce it. You ask Make for something by name; it works out everything that thing depends on, runs the commands needed in the right order, and skips any step whose output is already newer than its inputs.

Three parts of that sentence are worth separating out, because they are the whole value of the tool.

Make takes a request by name. You type make run and Make looks up what run means in your rules. The sequence is no longer in your head or your history; it is in a file, under a name.

Make works out the order. You do not list the steps in execution order. You state which thing needs which other thing, and Make derives the order from that. This is why Make is often described as declarative: you describe relationships, not a procedure.

Make skips finished work. This is the capability a plain script does not have, and it is the historical reason Make exists. Make compares file timestamps — the time a file was last modified — and does not re-run a step whose result is already up to date.

One point of vocabulary, so a web search does not confuse you later. Make is a family of programs with a long history; GNU Make is the specific, overwhelmingly common implementation, and it is the one this track teaches. Other implementations exist and differ in their advanced features. Everything in Part I is standard GNU Make behaviour.

3. GNU Make and a Makefile Are Not the Same Thing

Beginners mix these two words constantly, and the confusion is worth clearing up before you write anything.

  • make is the program — an executable file on the machine, written by somebody else, that you never edit.
  • A Makefile is a plain text file — written by you, living in your project directory, containing the rules for that project.

The relationship is the same one you already know from any other interpreter: python is a program, your .py file is the input you write; bash is a program, your .sh file is the input you write. make is a program, the Makefile is the input you write.

When you run make with no file specified, it looks in the current directory for a file to read. The conventional name is Makefile with a capital M, and that is what this track will always use.

A left-to-right chain of four boxes, each feeding the next. First, Makefile: a text file of rules that you write. Second, make: the program that reads those rules and decides what needs running. Third, the shell: it receives each recipe line as a command and runs it. Fourth, your tools: compilers, simulators and scripts, the programs that do the actual work.Makefilerules you writemakedecides what runsShellruns each recipe lineYour toolscompilers, simulators12
Figure 1 — what you write, what runs it, and what it ultimately drives. You author only the leftmost box. Make does not execute your recipe lines itself: it hands each one to a shell, which runs the actual tools. That is why a recipe line is written in shell syntax and why a tool's own error messages reach you unchanged.

Reading Figure 1 left to right also reads the authoring effort left to right: all of your work is in the first box, and the three boxes to its right are machinery you are borrowing.

4. Your First Makefile

Here is the smallest useful thing. Make an empty directory, change into it, and create a file named Makefile containing exactly this:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
hello:
	@echo "Make ran this recipe."

Two lines, and each one has a job.

Line 1 — hello: declares a target named hello. A target is a name you can ask Make for. The colon is required, and it is where the things this target depends on would be listed — there are none yet.

Line 2 — the command is the recipe: what Make should run when you ask for hello. It must begin with a tab character, not spaces. This is the single most common beginner error with Make and §9 shows you exactly what it looks like when you get it wrong.

Now ask Make for it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make hello
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
Make ran this recipe.

That is the whole loop: you named a sequence of commands, and now you invoke it by name. Three things just happened that are worth making explicit.

Make read Makefile from the current directory, without being told to. It found the rule for hello. It handed the line @echo "Make ran this recipe." to a shell, which ran it.

The leading @ suppressed the echo of the command itself. By default Make prints each command before running it, so that a log shows you exactly what ran. Remove the @ and you see both lines:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output without the @
echo "Make ran this recipe."
Make ran this recipe.

Neither form is more correct. Printing the command is useful in a long build log and noise in a short one.

Typing make with no target also works. With no target named on the command line, Make builds the first target in the file — here, hello. Which target that is, and how to control it deliberately, is Chapter 2.4's subject.

5. Connecting Two Tasks

A single target is a named shortcut. The thing a shortcut cannot express is this must happen before that — and that is what the space after the colon is for.

Replace your Makefile with this:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
compile:
	@echo "1. compiling the design"

run: compile
	@echo "2. running the simulation"

The only new syntax is the word after run:. It makes compile a prerequisite of run: a thing that must be up to date before run's own recipe may execute.

Ask for run, and you get both, in order:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make run
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
1. compiling the design
2. running the simulation

You never told Make to run compile. You stated that run needs it, and Make worked out the rest. Ask for compile on its own and only it runs — run is not involved, because nothing says compile needs run:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make compile
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
1. compiling the design

That is the dependency relationship doing its job in the smallest possible example. The formal anatomy of a rule — target, prerequisites, recipe, and how to read any rule you encounter — is Chapter 1.2, and Chapter 2.3 covers what happens when prerequisites are real files.

One behaviour you should see now, because it is what makes Make safe to put a real flow behind. If a recipe fails, Make stops and does not continue to the steps that depended on it. Add a deliberately failing command to compile:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — compile now fails on purpose
compile:
	@echo "1. compiling the design"
	@false

run: compile
	@echo "2. running the simulation"
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output — the exact wording varies by Make version
1. compiling the design
make: *** [compile] Error 1

run's message never appears, and Make itself exits with a non-zero status. A plain script written without care would have carried on and run the simulation against a design that never compiled. Chapter 4.3 covers failure handling properly, including the cases where a tool fails without saying so.

6. Where the Skipping Shows Up

Both examples so far re-run every time, because neither target produces a file. Here is the smallest example that shows Make's actual decision.

Put two lines of text in a file named data.txt, then write this Makefile:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile
report.txt: data.txt
	@echo "Building report.txt from data.txt"
	@sort data.txt > report.txt

This rule says: the file report.txt is produced from the file data.txt, by sorting it. Run it once, then run it again without touching anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make
make
Azvya Education Pvt. Ltd.VLSI Mentor
Representative output — Make 3.x quotes the name differently
Building report.txt from data.txt
make: 'report.txt' is up to date.

The second run did no work at all. Make compared the timestamp of report.txt against the timestamp of data.txt, found the output newer than the input, and declined to rebuild it. No part of that comparison was written by you — stating report.txt: data.txt was enough.

Multiply that decision across a few hundred files and you have the reason Make has survived for decades. The exact timestamp rule, including the cases where it surprises you, is Chapter 2.3.

7. Where Makefiles Appear in RTL Design and Verification

Now the engineering context, and you do not need to know how a simulator works to follow it.

A digital design project has source files — the design itself, and a testbench that exercises it. To see whether a change works, an engineer repeats a sequence very close to the one in §1:

  1. collect the list of source files that should be read;
  2. compile them, which checks them and produces a runnable simulation image;
  3. run that image, selecting which test to execute, and capture the output to a log;
  4. optionally view the waveform, a recording of how the signals changed over time;
  5. read the log to find out whether the test passed.

That sequence is long, the options are detailed, and it is repeated many times a day by several engineers across different machines. It is exactly the situation §1 described, with higher stakes: step 3 against a stale step 2 produces a result that looks like a pass.

So essentially every real design and verification project keeps its flow in a Makefile. Written out, the shape is one you can already read:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — illustrative; the tool names are examples, not a runnable flow
compile:
	vcs -full64 -sverilog -f filelist.f -o simv

run: compile
	./simv +UVM_TESTNAME=axi_smoke_test -l run.log

waves: run
	verdi -ssf waves.fsdb &

This block is illustrative. vcs and verdi are commercial tools that are not installed here, there is no filelist.f in front of you, and the real flow needs more than this. Treat it as a picture of the structure, not a recipe to type. Part II of this track builds the real version step by step, starting with filelists in Chapter 5.2 and the compile and run targets in Chapter 5.3.

What you can read from it is the structure, which is identical to §5's toy example:

  • three targets named after the things an engineer actually asks for — compile, run, waves;
  • run requires compile, and waves requires run, so make waves performs the whole flow in order;
  • the sequence, the tool options, and the log file name are now text in the repository — something a colleague can read, a reviewer can comment on, and version control can track.

One honest limitation of the shape above, so it does not mislead you later: because compile names an action rather than a file, Make has nothing to compare and will re-run the compile on every make run — the skipping from §6 is not happening here. Written this way the Makefile buys reviewability and a single shared command, not saved compile time. The next chapter rewrites the same flow with file targets, which is what brings the skipping back.

8. What Make Does, and What It Does Not

This is the section to come back to when Make surprises you. Nearly every surprise is a case of expecting something from the right-hand column.

Make does this for youMake does not do this
Runs the commands in a target's recipeUnderstand what those commands mean
Works out execution order from the prerequisites you statedDiscover dependencies you did not state
Compares file timestamps and skips up-to-date workNotice a change that does not alter a file's timestamp
Stops when a command reports failureNotice a tool that prints errors and still reports success
Accepts a target name on the command lineGuess which target you meant

The left column is genuinely useful and the right column is not a criticism. It is the division of labour: Make schedules commands and compares timestamps, and that is all it was ever built to do.

For this track the consequence is worth stating bluntly, because it is where beginners expect magic:

Make does not understand SystemVerilog, Verilog, or any EDA tool. It does not know what a module is, it cannot read a file list inside your source code, and it has no idea that a simulator exists. When a Makefile compiles a design, every piece of knowledge about how to compile it came from a recipe line that an engineer wrote. Make supplied the ordering and the skipping, not the expertise.

Make does not know your dependencies unless you declare them. This is the one that produces real bugs. If a target reads a file and you forget to name that file as a prerequisite, Make will cheerfully report the target up to date after you edit that file, and you will test a stale result. Chapter 2.3 covers this failure directly.

9. Common Beginner Misunderstandings

"The recipe is indented with four spaces." It is indented with a tab. This is Make's oldest and sharpest edge, it is not configurable in the normal case, and editors that helpfully convert tabs to spaces cause it constantly. The failure is immediate and specific:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output when the recipe is indented with spaces
Makefile:2: *** missing separator.  Stop.

"Missing separator" is Make telling you that line 2 looked like neither a rule nor a recipe. When you see it, check the indentation first. Chapter 1.4 covers the syntax mechanics, including how to see whether your file really contains a tab.

"A Makefile is a shell script with extra steps." It is not, in two ways that matter. A script is read top to bottom and every line runs; a Makefile is a set of rules, and which ones run is decided by what you asked for and what is already up to date — order in the file is not execution order. And each recipe line is handed to its own shell, so a Makefile is a collection of small shell fragments rather than one continuous script. Chapter 4.3 is where that second point earns its own treatment.

"Make rebuilds everything, so it is slow." The opposite is the entire point: §6's second run did nothing. If you are seeing a full rebuild every time, it usually means your targets are action names rather than files Make can compare — which is legitimate for clean and run, and a mistake for a compile step.

"Make knows what changed." It knows what timestamps say, about the files you named. It is not watching your editor and it is not reading your source code. Everything it knows, you told it.

"Make is only for C programs." The association is historical — Make came from the Unix C world and its built-in conveniences are still C-flavoured. But a recipe is just a command line, so Make drives simulators, synthesis tools, document generators and report scripts just as readily. Part II of this track is entirely that.

10. Check Your Understanding

Question 1. You run make in a directory and get Make ran this recipe. without having named a target. Which target ran, and why that one?

Question 2. A colleague says "the Makefile runs top to bottom like a script". Give the one-sentence correction, using the compile and run example from §5.

Question 3. A rule produces report.txt from data.txt. You edit data.txt and run make, and Make rebuilds. You run make again immediately and it reports the target up to date. What did Make compare to reach that second answer?

Question 4. A Makefile compiles a design correctly. Does Make therefore understand the design language? Say what Make contributed and what the engineer contributed.

Question 5. You add a recipe line, run make, and get *** missing separator. What is the first thing to check, before reading anything else?

11. Remember

  • Make is a program; a Makefile is the text file of rules you write for it. Same relationship as python to a .py file. You never edit Make; you only ever write the Makefile.
  • A rule names something you can ask for, what it depends on, and the commands that produce it. You state relationships; Make derives the order.
  • The capability a plain script does not have is skipping. Make compares file timestamps and does not redo work whose output is already newer than its inputs.
  • A recipe line must start with a tab, and it is handed to a shell to run. missing separator almost always means spaces where a tab belongs.
  • A target can name an action or a file. hello and run are actions and always re-run; report.txt is a file, and that is what makes the skipping visible.
  • A failing command stops the chain, so the steps that depended on it do not run against a broken result.
  • Make schedules commands and compares timestamps — nothing more. It does not understand SystemVerilog, it does not know your tools, and it does not discover a dependency you did not declare.
  • In design and verification, Make is where the flow lives. Putting compile, run and waves behind named targets makes the flow reviewable, repeatable and identical for everyone on the team.

The next chapter takes the rule apart properly — target, prerequisites and recipe — so that you can read any rule in any Makefile you are handed, including ones far larger than these. From there, Chapter 1.3 builds one with you from an empty file.

Where this fits

Part of the Makefile curriculum.