Where does each file go, and which files should never be saved?
You open the course example and find ten kinds of files spread over eight folders. Every one of them has a reason to sit exactly where it sits. Learn the reasons once and every hardware project you meet will look familiar, because they all answer the same two questions: what did a human write, and what did a tool produce?
The example is mini_controller, a 4-bit event counter: it counts input events up to 15, then wraps to 0. You get it as a folder named mini-controller/ from the course download, described below. Its folders are rtl, tb, model, docs, scripts, tools, mutants, and github-workflows, plus a Makefile and a .gitignore at the root. The build/ folder does not ship with the project: make creates it on your machine when you compile.
Download start-here-examples.zip and unpack it. On macOS, double-click it in Finder, or in a terminal run unzip start-here-examples.zip; on Ubuntu and WSL, install unzip first if it is missing (sudo apt install unzip). You get a folder start-here-examples/ holding mini-controller/ and pulse-counter-starter/. Then cd start-here-examples/mini-controller and run pwd to confirm where you stand. Every make command in the course runs from that folder.
Source files are the project. A human wrote them, a human reviews them, and version control saves them. The design in rtl/, the testbench in tb/, the golden model in model/, the specification in docs/, the helper scripts, the tool versions, the mutants, and the CI workflow are all source. If the disk lost everything except these, a fresh machine could rebuild the rest.
Generated files are the exhaust. The simulator writes compiled output, logs, and waveforms; synthesis writes its own logs. These files matter for today debugging, and they are worthless next week, because any machine can reproduce them bit for bit from source. Saving them in version control bloats every download and guarantees stale copies: your teammate opens your old waveform and mistakes it for the current behavior.
The rule between the two is one way. Source flows into tools, tools emit generated files, and generated files never flow back. You never edit a generated file to fix the design, the way you never edit a printed page to fix a book. When generated output looks wrong, you fix the source or the command and regenerate.
Each folder owns one role, and the path of a file states that role before you open it. rtl/ holds the design, the register-transfer level description of the hardware: registers and the logic between them. tb/ holds testbenches, the scaffolding that drives the design and checks it. model/ holds the golden model, an independent description of correct behavior the testbench compares against. docs/ holds the specification the design was written against.
The remaining folders support the loop: scripts/ holds helper commands, tools/ records the tool versions, mutants/ holds deliberately broken design copies for testing the testbench, and github-workflows/ holds the continuous integration (CI) workflow, the automation that reruns the loop on every change. The Makefile at the root is the control panel: one short name per task, such as make sim, reruns a whole chain of commands.
The figure below draws the full layout as two columns. The left column is everything a fresh checkout contains; the right column is everything the tools add on your machine. Dashed boxes and made tags mark the difference twice, so it never depends on shade alone.
The .gitignore file at the project root lists the paths version control must never save. It is source itself: a human wrote it, and it ships with the project so every machine ignores the same files. The listing below is the complete file, read from the example project.
# Generated by make: never commit these
build/
*.vcd
*.vvp
*.logThis listing is the whole ignore file: the build/ folder, waveform dumps (VCD, which means value change dump: the recorded file of signal changes), compiled simulation binaries, and logs.
Each line is a pattern, not a single file. build/ ignores the whole folder wherever make created it, and *.vcd ignores every waveform file in any folder. The comment on the first line speaks for all four: these files are generated by make, so committing them would save exhaust alongside the engine.
The ignore file only protects files that are not saved yet. If a generated file was committed before the rule existed, ignoring it afterwards does not remove the saved copy. Check git status --short after your first build: the right column of the figure should stay invisible there.
One awareness-level warning before you write any hardware. The order in which files are handed to the compiler can matter: definitions must exist before the code that uses them. SystemVerilog packages, which are shared collections of types and constants, come first, then the modules that use them. You do not manage this by hand: the Makefile lists the files on each tool command line in a working order, for example the design file before the testbench on the simulation line.
Take this as a habit. When you add a file, put it in the folder of its role and add it to the matching Makefile line in the established order. A later lesson on the design loop shows those tool commands running.
make clean in the project root. Which files disappear, and what rebuilds them?Only the contents of build/ disappear: the compiled simulation, the logs, and any waveforms the tools wrote there. Every source file stays untouched. The next make sim recreates build/ and everything in it from source, which is exactly why the right column of the figure is safe to lose.
// snippet
clean:
rm -rf $(BUILD)This snippet is the clean target at the end of the project Makefile: it removes the build/ folder and nothing else.
Saving generated files. You commit build/sim.vvp or a waveform because it felt like progress. Every later change silently disagrees with the saved copy. Fix: delete the file, confirm .gitignore covers its pattern, and commit the deletion so the file leaves history going forward.
One folder for everything. The design, the testbench, and last week waveform sit side by side, so no path states any file job. Fix: move each file to the folder of its role using the figure as the map, and update the Makefile lines that name moved files.
Two copies of one testbench. tb_mini_controller.sv and tb_mini_controller_old.sv disagree, and nobody knows which one the last green run used. Fix: compare the two, rescue anything the old one knows, keep a single copy in tb/, and let version control remember the rest.
Rule: source is saved and generated is rebuilt, so each file lives with its role and never with its exhaust.
A teammate sends you this folder. Sort it: which files stay, which move, and which go? The listing below is illustrative: a made-up messy folder, not the course example. Propose the clean layout before reading the answer.
# Illustrative messy folder, not the course example
mini_controller.sv
tb_mini_controller.sv
tb_mini_controller_old.sv
sim.vvp
wave.vcd
spec.md
notes.txtMove mini_controller.sv to rtl/ and the newest testbench to tb/ as the single tb_mini_controller.sv. Compare the old copy against it, rescue anything worth keeping, then delete it: version control already remembers it. Move spec.md to docs/. Delete sim.vvp and wave.vcd: they are generated and make rebuilds them. Put notes.txt in docs/ if the team needs it, or delete it.
git status --short shows build/sim.log as a new file after your first simulation. What went wrong, and what is the fix?The build/ pattern is missing or misspelled in .gitignore, so version control sees generated output as new source. Fix: restore the build/ line in .gitignore, delete nothing by hand beyond that, and confirm the next git status --short no longer lists anything under build/.
Source is written by humans and saved; generated output is rebuilt by tools and ignored, never the reverse.
One folder per role makes every path state its job, and the Makefile owns task names and compile order.
The .gitignore file lists generated patterns once so every machine ignores the same exhaust.