Design intent
Declare what a board is supposed to contain, and have every revision checked against the architecture you agreed rather than against itself.
A netlist says what is wired. It cannot say what was meant. “The board has two regulators” is a fact you can read off the file; “the board should have two regulators” is a decision somebody made in a meeting, and nothing in the export records it.
A design-intent declaration writes that decision down as YAML beside the design, and check compares
the board against it. It is the tier that catches a rail quietly moved to the wrong domain, a module
dropped during a cost reduction, or a power-up order that got reversed when the power tree
was redrawn. None of those look wrong in the schematic. They look wrong against what you said you
were building.
Write a declaration ¶
# designs/gateway/intent.yaml
name: gateway intent
modules:
- {name: regulators, class: regulator, count: 2}
- {name: connectors, class: connector, count: 1}
voltage_domains:
- {name: main, nominal: 12.0, rails: [PMIC_MAIN_12V0]}
- {name: io, nominal: 3.3, rails: [PMIC_CORE_3V3]}
- {name: core, nominal: 3.3, rails: [PMIC_IO_1V8]}
subsystems:
- {name: power tree, nets: [PMIC_MAIN_12V0, PMIC_CORE_3V3, PMIC_IO_1V8]}
- {name: can, nets: [CAN1_CANH, CAN1_CANL, CAN1_TXD, CAN1_RXD]}
That last voltage domain is wrong on purpose, and it is the whole point of the file. core is
declared at 3.3 V while the rail it names is PMIC_IO_1V8, an actual 1.8 V rail. Nothing about the
schematic is malformed. The board simply stopped matching the architecture, and only a declaration
can notice.
Where the file goes ¶
Beside the design, named intent.yaml. A design that belongs to a project needs no flag, because the
descriptor defaults that name and the project finds it:
$ agni check designs/gateway --rule intent/voltage-domain-mismatch
findings by rule:
intent/voltage-domain-mismatch 1
first 1:
[warning] intent/voltage-domain-mismatch: PMIC_IO_1V8 (rail "PMIC_IO_1V8" is declared in voltage domain "core" (3.3V) but its name declares 1.8V)
1 finding(s) total
3 subject(s) considered by 1 rule(s) (--verdicts for the detail)
Intent is per-design, unlike naming conventions, interface profiles and seeded parameters, which are per-project. Each board has its own intended architecture, so the file lives with the board rather than with the team.
--intent-path exists for a design that belongs to no project. Reaching for it on a design whose
project already declares intent is an error rather than a silent double-load, so you find out
immediately instead of reading every finding twice.
The nine forms ¶
Each form answers a question the netlist cannot, and each compiles to its own rule so a reviewer signing them off separately gets separate verdicts.
| Form | Declares | Fails when |
|---|---|---|
modules |
the functional blocks the board must contain, by class or MPN, optionally with a count | a declared module is absent, or the count is short |
voltage_domains |
named rails pinned to a nominal voltage | a declared rail is missing, or sits on the wrong domain |
subsystems |
a named architectural block and the nets it must instantiate | its source component is absent, or any declared net is missing |
protections |
a rail that must carry a protection device, by kind | the declared rail carries no device of that kind |
net_properties |
what a net is, rather than that it exists (a reset is active-low) | the design’s structure contradicts the declaration |
rail_budgets |
the peak current a rail draws, with an optional margin_factor |
the supply reaching it is rated below the peak, or below the margin |
sequences |
the power-up order of groups of rails | the gating chain is absent, or runs the other way round |
strap_groups |
several strap nets read together as one binary number, and the value it encodes | the group does not encode the declared value, or two devices collide |
io_map |
which net lands on which pin of which device, and optionally what sits at the far end | the net is on a different pin, the declared net is absent, or the far end is wrong |
io_map is the largest of them in practice and the one most boards already have, usually as a
spreadsheet. On any board carrying a big MCU or SoC, someone decides which peripheral lands on which
pin long before the schematic exists, firmware is written against that decision, and the schematic is
drawn from it. What goes wrong is ordinary: an assignment moves late, the map is updated, and one net
does not get redrawn. Nothing about the resulting board is electrically wrong, so every other rule
passes, and it surfaces at bring-up as a peripheral that does not respond.
io_map:
- net: I2C_SDA
device: U3
pin: '9'
- net: MCU_NRST
device: U3
pin: PTC11 # the datasheet's name works as well as the designator
to: {device: U1, pin: '5'} # optional far end
Write the pin either way. A map is authored in the vocabulary a datasheet and a firmware header use,
and a netlist answers in package designators, so both are resolved: PTE7, PTE07, pte7 and a
name carrying a zero-width space pasted out of a spreadsheet all reach the same pin. A match that
needed any of that says so in the verdict, so a note always means something was inferred.
It compiles to four rules rather than one. Three of them ask whether the design kept the promises the
map made, and the fourth asks the opposite question: which nets the map never mentioned. That number
is usually the important one. A design with sixteen hundred nets and a map declaring two hundred has
two hundred checked and fourteen hundred unexamined, which is not the same as clean, and nothing else
in a run tells those apart. Nets it does not name report as not-considered rather than as failures,
because an undeclared net is a question nobody asked and not a fault in the board.
The three that check the promises are separated because a reviewer acts on them differently, and
because two of them are opposite defects: a net the map declares and the netlist does not have is
usually a real disconnection, where a net the netlist has and the map does not declare is an
incomplete map. function is accepted and NOT yet evaluated, since deciding whether a function is
legal on a pin needs the part’s alternate-function table; every verdict on a row carrying one says so
outright.
rail_budgets is the one that joins two tiers. The declaration supplies the demand, which no design
artifact carries, and a seeded datasheet parameter
supplies the regulator’s capacity. Both halves have to be present or the rule stays quiet rather than
guessing.
With no declaration, nothing passes ¶
There is no built-in intent, and that absence is deliberate. A generic statement of what a board should contain says nothing, and a rule that enumerated its expectations from the design would always agree with the design. So every intent rule iterates the declaration and probes the netlist, never the reverse.
The consequence is worth knowing before you read a report. A design run with no declaration leaves its intent-bound review items reading needs-design-intent, never pass. The mechanism exists and is blocked on an input you have not supplied yet, which is a different thing from a board that was checked and found clean. Checks and reports covers the full outcome vocabulary.
What intent cannot do ¶
It checks presence, property and order, over the netlist. It does not simulate. A declared sequence is verified as a gating chain in the connectivity, not as timing on a scope, so a board that wires the enables correctly and still browns out at power-up is outside what a declaration can see.
It is also only as good as the names. A rail the declaration calls PMIC_CORE_3V3 has to be called
that on the board, which is the same dependency naming conventions exist to
make explicit rather than accidental.
Where to go next ¶
- Checks and reports: what
needs-design-intentmeans beside the other outcomes, and how--fail-ontreats them. - Interface profiles: the other declarative tier, per-project rather than per-design, for the shape of a bus rather than the shape of a board.
- How a rule gets written: where an intent declaration sits among the four ways to author a rule, and why the engine ships none of its own.