Makefile Contract#
flow/Makefile is written in make, a rich programming language behind a
terse syntax: recursive and simple expansion, = against := against ?=,
the origin of a variable, exports to sub-makes, a graph built in one pass and
run in another. ORFS keeps the Makefile as simple as it can, and over time a
handful of conventions have grown up that keep it that way. Most of them were
never written down.
This page writes them down as rules a review can check a change against: a
change to flow/Makefile, flow/scripts/*.mk, the stage scripts in
flow/scripts/*.tcl, flow/scripts/variables.yaml, or the config.mk of a
platform (flow/platforms/*/config.mk) or a design
(flow/designs/**/config.mk). It is written for
automated review of AI maintenance of these files: an agent that changes one
of them, or reviews such a change, checks it against every rule here.
The rules are hard stops: a change that breaks one is wrong, whatever else it fixes. Each rule says what breaks when it is broken. Any place where the existing code disagrees with a rule is technical debt, whether an expedient solution or older code written before the rule: debt to fix, not a precedent to follow.
Some flows run the Makefile one step at a time, each step in a fresh tree that holds only that step’s inputs, and track the dependencies themselves; bazel-orfs is one. Several of the rules exist for them.
Targets#
A
do-target never builds anything. It runs its one step on the files already in the tree. Its prerequisites are checks (check-kepler-formal), the step’s source files and one-time markers ($(OBJECTS_DIR)/copyright.txt): never an order-only prerequisite, never a file another target writes. Breaks: a flow that runs eachdo-target alone in a fresh tree rebuilds the prerequisite every time.Dependencies live only on the file targets and stage aliases (
$(RESULTS_DIR)/2_1_floorplan.odb,synth,floorplan, …), never on ado-target. The file target checks the dependencies and calls thedo-target to run the step. Breaks: a flow that tracks dependencies itself cannot run one step on existing results.open_*,gui_*,web_*andrunnever build anything.runwrites$(LOG_DIR)/$(RUN_LOG_NAME_STEM).logand nothing underRESULTS_DIR,REPORTS_DIRorOBJECTS_DIR. Breaks: opening or scripting a stage changes the tree it looks at, and a caller that runs severalmake runat once on one tree gets them writing the same files.The graph never depends on a variable. No
ifeqchooses which steps run or which files they write. A step with nothing to do for a design runs anyway and copies its.odband.sdcthrough; the choice is made in its.tcl. Breaks: a flow that names a step’s outputs before running it gets a different set for each design.
Steps#
A step never writes next to its inputs or into one. It writes new files under names of its own, as
2_floorplan.odb→3_place.odb. Breaks: a rerun reads what the last run wrote, and a flow that keeps inputs read-only cannot run the step.A step never skips an output because the file exists. “Up to date” is the file target’s decision. Breaks: a tree that kept a file from an earlier run and a fresh tree give different results.
A step never reads a file it does not declare. Breaks: it works under
make synth, where an earlier target happened to build the file, and fails when the step runs alone.A step never reads an input it does not use. Breaks: every change to that input reruns the step and every step after it.
A step never leaves configuration for a later step. A command gets its options in its own
log_cmdline; a setting it needs is set right before the call and reset right after. State that crosses steps is in the.odbor another result file. Breaks: the later step’s log does not show what it ran with, and a checkpoint changes when the design did not.A step never draws an unseeded random number. The seed comes from a variable and is recorded. Breaks: caching a step’s result, the QoR check against a baseline, and bisecting a regression.
A failing step never calls
exit. It writes<step>-failed.odband callserror, asflow/scripts/global_place.tcldoes. Breaks: the log and the exit status stop saying where the step failed, and there is no checkpoint to inspect.A
PRE_<STEP>_TCLhook never runs before the design is loaded. Hooks run throughsource_step_tclinflow/scripts/util.tcl, afterload_design. Breaks: the hook has no design to act on.
Variables#
A variable a design sets is never assigned with
=in the Makefile. It is assigned with?=.RUN_CMDinflow/scripts/variables.mkis the exception; it can be set only on the make command line. Breaks: a flow that passes a design’s variables inDESIGN_CONFIGasexport VAR?=valuelines is overridden.A variable the environment supplies is never exported with
export VAR := $(VAR). It is exported bare,export VAR, and its default is a?=fallback to the in-tree install. Breaks::=rebinds the variable to origin “file”,UNSET_AND_MAKEunsets it before the sub-make, and the fallback fires there with an in-tree path that a flow supplying its own tools does not have (the “HERE BE DRAGONS” comment inflow/scripts/variables.mk).Only the Makefile sets
RESULTS_DIR,LOG_DIR,REPORTS_DIRandOBJECTS_DIR. It derives them fromWORK_HOME, which a flow sets. A variable a flow adds never reuses one of these names. Breaks: a flow that relocates the tree throughWORK_HOMEfinds results where the Makefile did not put them.A variable a design or platform sets, or a stage script reads, is never missing from
flow/scripts/variables.yaml. Its entry has its default, when it has one, andstages:when not every stage reads it. A variable only the Makefile reads, such asWORK_HOME, or that is internal to it, such asDO_STEP_DEPS_*andUNSET_AND_MAKE, may be documented elsewhere and need not be listed. Breaks: a flow that checks variable names againstvariables.yamlrefuses it, and a flow that passes each stage only its own variables passes it nowhere, or everywhere.A variable that has a default is never read through an existence check. It is read as
$::env(VAR), never throughenv_var_exists_and_non_emptywith a fallback. A boolean is0/1and has a default. Breaks: a misspelled or unpropagated variable takes the other branch quietly, instead of stopping at the line that reads it.A script never overwrites a variable that is set. It sets one only when it is unset. Breaks: the design’s or the user’s value is ignored.
A platform default is never repeated in a design’s
config.mk. It is in the platform’sconfig.mk, assigned with?=. Breaks: a change to the platform default misses the designs that copied it, and a design can no longer override it.A variable never has a negative name (
LEC_CHECK=1, neverDISABLE_LEC_CHECK), never duplicates an existing one (ADDITIONAL_LIBSfor an extra library), and is never a default-off feature toggle that no CI design sets. A variable with no effect is deleted, not deprecated. Breaks: double negatives in settings, two variables for one choice, and code no CI run exercises.A variable’s value is never computed with
$(shell awk ...)or$(shell sed ...). That logic is in Python with unit tests. Breaks: a heuristic nobody can test, read, or run on a machine without that tool.
Directories#
A design’s files are split over four directories so that where a file lands says what it is: the design, a measurement of it, a log, or scaffolding. One directory would work for the flow; four make the tree legible. What a file is decides its directory, not who reads it.
RESULTS_DIRholds the design, in any format: netlists,.odb,.def,.gds,.sdc, routing guides, placement scripts, the checkpoint a failed step leaves, and memory inventories. Breaks: a flow or a user looking for the design finds part of it elsewhere.REPORTS_DIRholds measurements of the design, for tools or people to read. No step’s result depends on it. Breaks: a flow that hands a step only the results has to carry reports along.LOG_DIRholds logs, and each step’s metrics.jsonnext to its log. Breaks:genReport.pyand the metrics tools miss a run’s record.OBJECTS_DIRholds what is not the design: files a step derives from the platform (klayout.lyt), a step’s own intermediate files (abc.constr,rtlmp/,surgery/), caches, and stopgaps that wait for a cleaner implementation. Breaks: the design is mixed with scaffolding.