Makefile · Part I · Module 1 · Introduction to Makefiles
Writing and Running Your First Makefile
The previous two chapters explained Make from the outside. This one is hands-on: you create a small project, write a Makefile a rule at a time, and run it after every change. The point is not the finished file — it is that by the end you can predict, before pressing Enter, whether Make will run a recipe or tell you there is nothing to do, and you will have been wrong once on purpose in order to see why.
Foundation22 min readMakefileGNU MakePart IHands-OnDependencies
Part I · Module 1 · Chapter 1.3 · Writing and Running Your First Makefile
1. What You Will Build
You are going to build a small thing that a design team genuinely does want: a generated inventory of what is in a set of RTL source files.
The project has two hand-written Verilog files. From them, the Makefile will produce:
modules.txt— the name of every module declared in those sources, one per line;summary.txt— a count followed by that list.
Neither file is ever edited by hand. Both are generated, which is what makes them worth putting behind a Makefile: they have inputs, they go out of date when those inputs change, and regenerating them by hand is the kind of step people forget.
The work is deliberately done with ordinary shell tools — grep, cut, sort, wc — so you can run every command in this chapter today, with no simulator and no licence. Everything you learn transfers unchanged to a flow that runs vcs or xrun instead; §11 shows the mapping once the project is working.
One instruction before you start, and it matters more than it sounds: type the Makefile rather than pasting it, and run make after every single change. This chapter is built around what Make prints, and the printed line is the lesson.
If Chapters 1.1 and 1.2 are not fresh, the one thing to carry in is this: a rule names something to produce, what it is produced from, and the commands that produce it — and Make decides whether to run those commands by asking whether the target exists and whether any prerequisite is newer.
2. Setting Up the Project
Make a directory anywhere outside a repository you care about, and create a src directory inside it:
mkdir -p make-intro/src
cd make-introNow create two source files. They do not need to be synthesisable or even complete — nothing in this chapter compiles them, and Make certainly does not read them. They only need the one module line each.
src/alu.v:
module alu (
input wire clk,
input wire [31:0] a,
input wire [31:0] b,
output reg [31:0] y
);
endmodulesrc/regfile.v:
module regfile (
input wire clk,
input wire wr_en,
input wire [4:0] addr,
output wire [31:0] rdata
);
endmoduleYour directory now looks like this:
make-intro/
├── src/
│ ├── alu.v
│ └── regfile.v
└── (the Makefile goes here, next)Before automating anything, run the work by hand once. This is a habit worth keeping for the rest of your career: a recipe you have never run in a shell is a recipe you cannot debug from inside a Makefile.
grep -h "^module" src/alu.v src/regfile.v | cut -d' ' -f2 | sortalu
regfileRead the pipeline left to right. grep -h "^module" finds the lines beginning with module and -h stops it printing the filename in front of each match. cut -d' ' -f2 splits each line on spaces and keeps the second field, which is the module name. sort puts the names in a fixed order — §6 comes back to why that last stage is not decoration.
You now have a command that works. Automating a command you have not run is how people end up debugging two things at once.
3. Creating the Makefile
In the project root — beside src, not inside it — create a file named exactly:
MakefileCapital M, no extension. Not makefile.txt, not Makefile.mk, not make.txt. When you run make with no arguments it looks for a file with one of a few specific names in the current directory, and Makefile is the conventional one. A file called Makefile.txt is invisible to it, and §9 shows exactly what that failure looks like so you recognise it.
Two things to check in your editor now, because both cause a confusing error later:
- The file must be saved with no extension. Some editors add
.txtsilently on save. - The editor must be able to insert a real tab character. Many editors are configured to convert the Tab key into spaces, which Make rejects. If yours does that, turn the setting off for this file. Chapter 1.4 covers the tab rule and its error message in detail; for now just know that recipe lines need a literal tab.
4. Your First Rule
Open the empty Makefile and type exactly two lines. The second line starts with a tab:
hello:
@echo "Makefile is working."This rule is the smallest thing that proves your setup works: the file is found, the tab is a real tab, and the recipe runs. Ask for the target by name:
make helloMakefile is working.If you got missing separator instead, the indentation is spaces — fix that before going further, because nothing else in the chapter will work.
Now run make with no target at all:
makeMakefile is working.Same result. With no target named on the command line, Make builds the first target in the file, which is currently the only target. That default will matter in a moment, when hello stops being the only rule and still stays first; Chapter 2.4 is where choosing it deliberately belongs.
hello produces no file called hello, so it runs every time you ask — the action-target case from Chapter 1.2.
5. A Target That Produces a File
Now a rule that creates something. Add it below hello, separated by a blank line, and leave out the prerequisites on purpose:
hello:
@echo "Makefile is working."
modules.txt:
@echo "building modules.txt"
@grep -h "^module" src/alu.v src/regfile.v | cut -d' ' -f2 | sort > modules.txtThe target is now the name of a file this rule creates. The recipe is the pipeline you ran by hand in §2, with its output redirected into that file. Two recipe lines, each starting with a tab: the first announces the work so you can see it happen, the second does it.
Build it:
make modules.txtbuilding modules.txtcat modules.txtalu
regfileThat worked. Ask again:
make modules.txtmake: 'modules.txt' is up to date.Also correct, and exactly what you want: the file exists, the rule has no prerequisites, so there is nothing that could be newer than it and no reason to rebuild.
Now the deliberate mistake. Open src/alu.v in your editor and rename the module from alu to alu_core. Save it. Then ask Make to bring modules.txt up to date:
make modules.txtmake: 'modules.txt' is up to date.cat modules.txtalu
regfileYour generated file is stale and Make says everything is fine. src/alu.v says alu_core; modules.txt still says alu. No error, no warning, exit status zero.
Nothing is broken. Make did precisely what it was told: the target exists, the rule declares no prerequisites, so no prerequisite can be newer and there is nothing to do. The information that modules.txt is built from those two source files exists only in the recipe — and Make never reads a recipe to work out dependencies. It runs recipes; it does not analyse them.
This is the single most important failure mode in all of Make, and it is worth having produced it yourself once. In a real flow it is how a regression passes against a design that no longer exists.
6. Declaring the Prerequisites
The fix is to state the dependency where Make actually looks — after the colon. Add the two source files:
hello:
@echo "Makefile is working."
modules.txt: src/alu.v src/regfile.v
@echo "building modules.txt"
@grep -h "^module" src/alu.v src/regfile.v | cut -d' ' -f2 | sort > modules.txtOne line changed. Run it again — remember src/alu.v was edited a minute ago and is therefore newer than modules.txt:
make modules.txtbuilding modules.txtcat modules.txtalu_core
regfileThe same command that reported "up to date" thirty seconds ago now rebuilds, and the content is right. Nothing about the recipe changed; the only difference is that Make has been told what the file depends on.
Run it once more to confirm the cycle is stable, then edit src/regfile.v — rename regfile to regfile_bank — and run again:
make: 'modules.txt' is up to date.
building modules.txtEither prerequisite is enough to trigger the rebuild. Change src/alu.v back to alu and rebuild, so the project is tidy before the next section.
Notice the recipe now names those two files twice: once after the colon and once in the grep command. That duplication is real and it is annoying, and keeping the two lists in step by hand is a genuine source of bugs in large Makefiles. There is a proper fix — Make can hand a rule its own prerequisites — but it needs variables, which are Module 3. Writing it out longhand here is deliberate.
7. A Second Rule That Uses the First Rule's Output
A real flow is a chain: something is generated, and something else is generated from that. Add a second file-producing rule whose prerequisite is not a source file but modules.txt itself:
hello:
@echo "Makefile is working."
modules.txt: src/alu.v src/regfile.v
@echo "building modules.txt"
@grep -h "^module" src/alu.v src/regfile.v | cut -d' ' -f2 | sort > modules.txt
summary.txt: modules.txt
@echo "building summary.txt"
@wc -l < modules.txt | tr -d ' ' > summary.txt
@cat modules.txt >> summary.txtsummary.txt writes the line count first — wc -l counts lines and tr -d ' ' strips the padding it prints — and then appends the names. Note the redirections: > creates the file, >> adds to it. Getting those the wrong way round silently truncates your output, which is a shell mistake rather than a Make mistake, and no less common for it.
Delete the generated files so you start from nothing, then ask for the end of the chain:
rm -f modules.txt summary.txt
make summary.txtbuilding modules.txt
building summary.txtYou asked for one thing and two recipes ran, in the right order, without you stating an order anywhere. Make looked at summary.txt, saw it needed modules.txt, found modules.txt missing, and applied the same procedure to it first — its prerequisites were the two sources, which exist, so it built modules.txt and only then returned to summary.txt.
cat summary.txt2
alu
regfileNow test the chain's reach. Run make summary.txt again with nothing touched:
make: 'summary.txt' is up to date.Then edit src/alu.v — any change to the module name will do — and ask for summary.txt:
building modules.txt
building summary.txtBoth rules ran, from a change to a file that summary.txt's rule never mentions. Editing the source made it newer than modules.txt, which rebuilt; rebuilding modules.txt made it newer than summary.txt, which rebuilt in turn. The change travelled the chain one link at a time, and each link was an independent timestamp comparison.
8. Looking at What You Built
Stop and inspect the directory, because seeing generated files sitting next to authored ones is part of the mental model:
lsMakefile modules.txt src summary.txtFour entries, and they fall into two groups that it is worth learning to separate by eye. Makefile and src/ are authored — you wrote them, they belong in version control, and losing them means losing work. modules.txt and summary.txt are generated — a make summary.txt reproduces them exactly, they normally do not belong in version control, and deleting them costs nothing.
That distinction is what makes a clean target safe to write, and it is the one real decision in the complete Makefile in §10.
9. When It Goes Wrong
Four failures you are likely to hit in this chapter, with the message each one produces. The wording below is from GNU Make 3.81; other versions phrase these slightly differently but the diagnosis is the same.
The file is not named Makefile. Typically an editor appended .txt:
make: *** No targets specified and no makefile found. Stop.Make found no file it recognises. Rename the file to Makefile. If you genuinely want a different name, make -f build.mk hello points Make at a specific file — useful to know, and not what you want here.
A typo in the target name. Asking for modules when the target is modules.txt:
make: *** No rule to make target `modules'. Stop.Make searched for a rule whose target is modules, found none, and found no file by that name either. Check the spelling against the text before the colon.
Spaces instead of a tab. The error names a line but not the cause:
Makefile:2: *** missing separator. Stop.Line 2 looked like neither a rule nor a recipe. Re-indent it with a real tab. If this keeps happening, your editor is converting tabs — fix the setting rather than fighting it.
The recipe fails. Rename src/alu.v temporarily and rebuild, and the failure surfaces rather than being swallowed:
grep: src/alu.v: No such file or directory
make: *** [modules.txt] Error 1Make stopped, reported the target that failed, and exited non-zero. Anything that depended on modules.txt did not run. This is the behaviour you want: a flow that continues after a failed step produces results that look real.
10. The Complete Makefile
Here is the finished file, with one addition — a clean target that removes exactly the two generated files from §8 and nothing else:
hello:
@echo "Makefile is working."
modules.txt: src/alu.v src/regfile.v
@echo "building modules.txt"
@grep -h "^module" src/alu.v src/regfile.v | cut -d' ' -f2 | sort > modules.txt
summary.txt: modules.txt
@echo "building summary.txt"
@wc -l < modules.txt | tr -d ' ' > summary.txt
@cat modules.txt >> summary.txt
clean:
@echo "removing generated files"
@rm -f modules.txt summary.txtclean names the files explicitly. It would be shorter to write rm -rf *.txt or to clear a directory, and that is how people eventually delete something they wanted. The -f suppresses complaints when a file is already gone, so make clean twice in a row is harmless.
Exercise the whole thing:
make clean
make summary.txt
make summary.txtremoving generated files
building modules.txt
building summary.txt
make: 'summary.txt' is up to date.Four commands, four outcomes you can now explain without guessing. clean ran because it names no file. Both rules ran because both outputs were missing. Neither ran the second time because nothing was newer.
One caution to carry forward: clean works here only because no file named clean exists in the directory. Create one and the target stops working — silently, exactly as Chapter 1.2's §9 showed. The declaration that prevents it is .PHONY, and Chapter 2.2 is where it belongs.
11. The Same Shape in a Simulation Flow
Now that the project works, the mapping onto RTL work is short, because the structure is already right.
simv: src/alu.v src/regfile.v
vcs -full64 -sverilog src/alu.v src/regfile.v -o simv
run.log: simv
./simv -l run.logThis block is illustrative — vcs is not installed here, so unlike every other transcript in this chapter it was not run. Compare it with §10 line by line and the correspondence is exact: modules.txt becomes simv, the compiled simulation image; summary.txt becomes run.log, produced from it; the two sources are the same two sources. make run.log after changing nothing runs neither tool, for the same reason make summary.txt did nothing above.
Note what this version does not do. It names the source files directly on the command line, which works for two files and does not scale to the hundreds a real block has. The usual answer is a filelist — and Chapter 1.2 showed the trap that comes with one, because naming the filelist as the prerequisite does not make Make notice an edit to a source inside it. Doing that properly is Chapter 5.2.
12. Exercise
Add a third rule to your Makefile that produces ports.txt: every line in the two sources that declares an input or an output, sorted. Then make summary.txt depend on both modules.txt and ports.txt, and append the port count to the summary.
Before running anything, write down your answer to this: starting from a clean directory, how many recipes run when you type make summary.txt, and in what order?
Then build it and check.
13. Remember
- A Makefile lives in the project root and is named
Makefile. A wrong name is invisible tomakeand producesNo targets specified and no makefile found. - Run the recipe by hand in a shell before putting it in a rule. Automating a command you have never run means debugging two things at once.
- A target with no prerequisites is built once and then never again. §5 produced a stale generated file with a successful exit status, which is the failure mode to remember from this chapter.
- The dependency must be after the colon, not merely inside the recipe. Make runs recipes; it never reads one to work out what a target depends on.
- Asking for the end of a chain builds the whole chain, in dependency order, regardless of the order the rules are written in.
- A change travels one link at a time. Editing a source rebuilt
modules.txt, which madesummary.txtout of date in turn. - Separate authored files from generated ones, and let
cleandelete only the generated ones, named explicitly. - Make generated output reproducible. Sorting a generated list costs one pipeline stage and stops a file that is semantically unchanged from looking changed to everything downstream.
You can now write a working Makefile and explain every line Make prints while running it. What is still missing is the mechanics underneath: why a tab and not spaces, what happens to a recipe line between Make and the shell, why cd on one line does not affect the next, and how to see what Make intends to do without letting it do it. That is the next chapter.
Where this fits
Part of the Makefile curriculum.
