What goes in the folder you hand to someone else, so they can rebuild your result without calling you?
Picture a colleague on another machine, with no access to you, asked to rebuild your result from scratch. Which files do they need, which command do they run first, and how do they know their rebuild matches yours? This lesson answers with the project's own release folder, file by file.
A release package is the folder you hand over when the design leaves your hands: everything a stranger needs to rebuild your result, and nothing they must guess. Sources go in, because sources rebuild. Generated files stay out, because the build recreates them and stale copies only confuse.
Two habits separate a real package from a zip of a working directory. First, a manifest: a list of what the package contains and the job each item does, usually kept as a file table in the README. A manifest lets the receiver check that nothing fell out in transit. Second, known issues written down honestly: limits you already know about, stated plainly. A limit you name is a limit the receiver can plan around; a limit you hide becomes their lost week.
The rest of this lesson walks the example project's own README, names each package part and its job, and ends with the one command that proves the package rebuilds.
The listing below is the README of the example project, read from the repository. It is short on purpose: a release README tells the stranger what the design is, then how to run it.
# mini_controller
A 4-bit event counter: counts accepted events, wraps 15 to 0. The specification is in
`docs/spec.md`; the testbench and the Python golden model are written from it.
## Run it
make check-tools # print tool versions (exit 1 if one is missing)
make all # lint, simulate, synthesize
make clean # remove build/
`make sim` passes only if the testbench prints exactly one `PASS:` line.
## Files
| Path | What it is |
|---|---|
| `rtl/mini_controller.sv` | the design (synchronous active-low reset; priority reset, clear, count, hold) |
| `rtl/mini_controller_sva.sv` | assertions (Verilator only) |
| `tb/` | self-checking testbench, random and coverage testbenches, assertion wrapper |
| `model/` | Python golden model |
| `mutants/` | deliberately broken copies used to check the testbench |
| `constraints/` | an illustrative timing constraints file |
| `scripts/check_tools.sh` | prints tool versions |
## Tested with
See `tools/versions.md`. Not tested: x86-64 Linux, Windows with WSL, the CI workflow.
## Known limits
- The constraints file uses example numbers, not a product requirement.
- GTKWave is optional; waveforms are also shown in the lessons.This listing is the release's front door: identity, run commands, file table, tested versions and known limits, each in its own section.
Walk through its parts in order. The title and first lines say what the design is: a 4-bit event counter that wraps from 15 to 0, with the testable behavior contract in the spec and the checks written from it. The Run-it section gives the exact commands: print tool versions, rebuild everything, remove generated files. The Files table is the manifest: each path next to the job it does. The Tested-with section points at the version record instead of repeating it. The Known-limits section admits what the package does not cover: example constraint numbers, an optional waveform viewer, and platforms nobody has tried yet.
Tools differ between machines, so a result without versions is a rumor. The listing below is the version record the README points at: one row per tool, one column per tested platform.
# Tool versions this project was tested with
Result of `make check-tools && make all` plus `python3 model/mini_controller_model.py`, run 2026-10-21.
| Tool | macOS (Homebrew) | Ubuntu 24.04.5 (apt, aarch64 container) |
|---|---|---|
| Icarus Verilog | 13.0 | 12.0 |
| Verilator | 5.048 | 5.020 |
| Yosys | 0.69+post | 0.33 |
| GNU Make | 3.81 | 4.3 |
| Git | 2.54.0 | 2.43.0 |
| Python | 3 | 3 |
| Result | `make all` passes | `make all` passes |
Not yet tested: Ubuntu on x86-64 (the GitHub runner architecture), Windows with WSL, the CI workflow itself.This listing says exactly which tool versions the passing result came from, so a fresh machine can compare before trusting a rebuild.
Read it as a comparison table, not a trophy shelf. The macOS column carries Homebrew versions and the Ubuntu column carries distribution versions, and both end with the same verdict: the full loop passes. The last line is as important as the table: it names what nobody has tried, so the next platform surprise arrives as a known risk instead of an ambush.
A package earns trust when a single command rebuilds everything from sources. The transcript below runs that command in the example project: lint, simulate and synthesize, each printing its own evidence.
# captured terminal output
$ make all 2>&1 | grep -E '^(PASS|iverilog|vvp|verilator|yosys)' | cut -c1-110
verilator --lint-only -Wall --top-module mini_controller rtl/mini_controller.sv
iverilog -g2012 -Wall -s tb_mini_controller -o build/sim.vvp rtl/mini_controller.sv tb/tb_mini_controller.sv
PASS: mini_controller
yosys -Q -T -p 'read_verilog -sv rtl/mini_controller.sv; hierarchy -check -top mini_controller; proc; opt; cheThis output came from macOS with Icarus Verilog 13.0, Verilator 5.048, Yosys 0.69+post, GNU Make 3.81 and Git 2.54.0. Each line earns its place: the lint line checks the design, the compile line builds the simulation, the verdict line reports the test result, and the synthesis line starts the gate-level check.
That command is the whole reproducibility contract in one line. If it passes on a fresh machine with matching versions, the package is complete. If it fails there, the failure points at a missing piece: a file the manifest forgot, a version the record did not pin, or a step the README never wrote down.
A release needs an identifier and a way to prove no file changed. The identifier is a version tag in Git, such as v0.1.0, placed on the commit the green run came from; the tag, not a file name, tells the receiver exactly what they hold. The proof is a checksum list: one line per file with its SHA-256 hash, so any change to any file shows up when the list is verified.
# on the sender's machine, from the project folder (macOS: shasum -a 256; Ubuntu: sha256sum)
git tag v0.1.0
git rev-parse --short HEAD
find rtl tb model docs constraints -type f | sort | xargs shasum -a 256 > MANIFEST.sha256
# on the receiver's machine, in a fresh unpacked copy
shasum -a 256 -c MANIFEST.sha256Your hashes will differ from anyone else's: they describe your files. A clean-checkout acceptance checklist closes the release:
Unpack or clone the release into an empty folder on a machine that did not build it.
Run make check-tools and compare the printed versions with the version record.
Verify the checksum list: every line reports OK.
Run make all and read the single verdict line.
Record the tag, the date and the result next to the release.
The picture below draws the package as a tree. Each item carries the job it does: sources that rebuild, documents that explain, and records that pin the result. Look for what is absent as well as what is present: no generated files, no duplicates, nothing a stranger must guess.
Package item | Why it is there |
|---|---|
RTL source | The design itself; everything else checks or translates it. |
Spec | The behavior contract every test checks against. |
Tests | Self-checking testbenches that fail loudly on bugs. |
Golden model | An independent model of the spec for cross-checking. |
Scripts | The version check and any step too long to retype. |
Tool versions | The exact versions the passing result came from. |
Constraints | The timing target, marked illustrative in this course. |
Known issues | Limits stated honestly so the receiver can plan around them. |
Reproduce command |
|
The first one. Sources plus commands rebuild anywhere the tools match; generated files are results, not ingredients. The second folder cannot even answer which sources produced it, and the build command would overwrite its contents anyway.
A README that says what the design is but not how to run it. Loving paragraphs help nobody at midnight before a deadline. The fix is a Run-it section with commands retyped from a clean checkout, in run order.
No tested-versions record. Without it, a failure on a new machine is a mystery: a real bug or a tool difference. The fix is a versions file naming every tool, updated on each proven platform.
Generated files shipped as source. A committed build folder goes stale the moment sources change. The fix is to leave generated files out, ignore them in version control, and let the reproduce command recreate them.
Rule: hand over everything needed to rebuild, and write down what you know is imperfect.
Sources and checks: rtl/, tb/, model/ and mutants/ is optional. Contract and target: docs/spec.md and constraints/ marked illustrative. Reproducibility: README.md, Makefile, scripts/check_tools.sh and tools/versions.md. Out: build/ and any waveform files, which the reproduce command recreates.
They know what the design is and still cannot rebuild it: which command runs first, what a pass looks like, and how to clean up are all guesses. Add the exact commands, retyped from a clean checkout, and the package becomes runnable instead of merely readable.
make clean end that story?Cleaning removes generated files, so the shipped outputs vanish and nothing remains that can recreate them. Sources survive cleaning by design; generated files survive only until someone tidies up. Ship what survives.
A release package rebuilds without you: sources, spec, tests, model, scripts, versions and constraints, plus a README that says how to run it.
A manifest lists what the package holds and each item's job; known issues are written down honestly instead of hidden.
One reproduce command proves completeness: when it passes on a fresh machine, the package is done.