4. Your names

Teach the tool which nets are rails, and what a legal name looks like on your boards.

Everything so far used only the shipped rules, which know nothing about your team. This rung is the first of four that change that, and it is deliberately first because it changes what the later ones can see at all.

The file is conventions.yaml, and it carries two halves that are wired very differently.

The problem

This project already carries the file this rung is about, so to see the problem it solves you have to move it out of the way first. The seeded parameter corpus moves aside with it, for a reason worth knowing up front: a datasheet that types a pin as a supply establishes the rail on its own, so with the corpus in place these rails classify whether or not the naming vocabulary reaches them. That is the later rungs’ subject. This one is about names, so both are out of the way. Ask the board which of its nets are power rails:

$ mv conventions.yaml conventions-off.yaml
$ mv params params-off
$ agni query designs/gateway/gateway.edn 'rail(?n) => ?n'
n    provenance
GND  designs/gateway/gateway.edn:GND

1 result(s)

One rail. The board has four. PMIC_MAIN_12V0, PMIC_CORE_3V3, and PMIC_IO_1V8 are all supply rails and the tool does not think any of them is.

That is not a bug. The built-in rail vocabulary is anchored on the names most boards use: VCC, VDD, +3V3, and so on. This project names rails function-first, subsystem before voltage, so none of them start the way the vocabulary expects.

The consequence is quiet and it is the important part. A rule that finds its rails BY NAME simply sees fewer of them. It does not warn you. It does not fail. A rail with no test point is not reported, because as far as that rule is concerned the net is not a rail.

How many rules that is depends on the board, and on this one it is a single rule. decoupling-present finds the same three rails either way, because it reaches them through pin types rather than through names. So the vocabulary matters most to the rules that have no other route to a rail, and you cannot tell which rules those are by reading their descriptions.

The two halves

name: gateway
lexicon:
  net:
    rail:
      patterns: ["_[0-9]+V[0-9]$"]
rules:
  - name: signal-net-naming
    severity: warning
    why: "house style names a clock net CLK_*, so XTAL_IN / XTAL_OUT are off-convention"
    allow: ["^(PMIC|CAN[0-9]+|I2C|MCU|CLK|GND)"]

lexicon teaches the engine which of your names mean what. It is applied when the design is read, before any rule runs, so it changes the input every rule sees. This is the half that matters most and the half people skip.

It is grouped by what is being named, because that is what decides whether a pattern is right. net holds rail, ground and feedback, matched against NET names. pin holds supply, gate, source and drain, matched against a component’s PIN names. A supply pin is called VDD or VIN while a rail net is called 3V3 or +5V, so a pattern that belongs in one group is wrong in the other. There is also a class map, which marks a PART as belonging to a component class.

rules adds checks. They join the catalog namespaced under the config’s name, so this one appears as gateway/signal-net-naming. Most people opening a conventions file expect only this half.

Both halves, visible

Without the file:

$ mv conventions.yaml conventions-off.yaml
$ agni check designs/gateway/gateway.edn
findings by rule:
  copper-clearance       12
  decoupling-present     1
  esd-protection         2
  gateway-profiles/can-esd-missing 2
  gateway-profiles/can-host-incomplete 1
  i2c-pull-up            2
  intent/voltage-domain-mismatch 1
  reverse-blocking-absent 1
  supply-exceeds-abs-max 1
  test-point-coverage    1
  track-width            1

first 25:
  [error] copper-clearance: CAN1_CANH (copper of "CAN1_CANH" and "I2C_SCL" closer than 0.127mm at 1 place(s); worst gap -0.175mm near (5.00, -84.00)mm)
  [error] copper-clearance: CAN1_CANL (copper of "CAN1_CANL" and "I2C_SDA" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -85.50)mm)
  [error] copper-clearance: CAN1_RXD (copper of "CAN1_RXD" and "PMIC_PG" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: CAN1_RXD (copper of "CAN1_RXD" and "PMIC_CORE_3V3" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: CAN1_TXD (copper of "CAN1_TXD" and "PMIC_MAIN_12V0" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: CAN1_TXD (copper of "CAN1_TXD" and "MCU_NRST" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: GND (copper of "GND" and "XTAL_OUT" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -82.50)mm)
  [error] copper-clearance: MCU_NRST (copper of "MCU_NRST" and "PMIC_MAIN_12V0" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: PMIC_CORE_3V3 (copper of "PMIC_CORE_3V3" and "PMIC_PG" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: PMIC_EN (copper of "PMIC_EN" and "PMIC_IO_1V8" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [error] copper-clearance: PMIC_EN (copper of "PMIC_EN" and "XTAL_IN" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [error] copper-clearance: PMIC_IO_1V8 (copper of "PMIC_IO_1V8" and "XTAL_IN" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [warning] decoupling-present: PMIC_MAIN_12V0 (power rail has no decoupling capacitor)
  [info] esd-protection: CAN1_CANH (externally-exposed signal net has no ESD protection)
  [info] esd-protection: CAN1_CANL (externally-exposed signal net has no ESD protection)
  [warning] gateway-profiles/can-esd-missing: CAN1_CANH (CAN signal net CAN1_CANH is exposed on a connector with no ESD protection in reach)
  [warning] gateway-profiles/can-esd-missing: CAN1_CANL (CAN signal net CAN1_CANL is exposed on a connector with no ESD protection in reach)
  [error] gateway-profiles/can-host-incomplete: U4 (CAN host U4 declares the interface but is missing required signal STB)
  [error] i2c-pull-up: I2C_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: I2C_SDA (I2C net has no pull-up resistor to a rail)
  [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)
  [warning] reverse-blocking-absent: PMIC_MAIN_12V0 (connector feeds a power input with no reverse-blocking element in the path)
  [error] supply-exceeds-abs-max: U2 (power-input pin 1 on rail "PMIC_CORE_3V3": nominal 3.3V exceeds absolute-maximum VIN 3V — datasheet "ACME-LDO-1V8 (placeholder, not transcribed)" page 0, "" (mock, confidence 0.3))
  [info] test-point-coverage: GND (rail carries no test point; bring-up and factory test cannot probe it)
  [error] track-width: CAN1_CANH (net has 1 track segment(s) narrower than the 0.127mm fabrication floor)

25 finding(s) total
240 subject(s) considered by 35 rule(s), 7 not considered (--verdicts for the detail)

With it:

$ agni check designs/gateway/gateway.edn
findings by rule:
  copper-clearance       12
  decoupling-present     1
  esd-protection         2
  gateway-profiles/can-esd-missing 2
  gateway-profiles/can-host-incomplete 1
  gateway/signal-net-naming 2
  i2c-pull-up            2
  intent/voltage-domain-mismatch 1
  reverse-blocking-absent 1
  supply-exceeds-abs-max 1
  test-point-coverage    2
  track-width            1

first 28:
  [error] copper-clearance: CAN1_CANH (copper of "CAN1_CANH" and "I2C_SCL" closer than 0.127mm at 1 place(s); worst gap -0.175mm near (5.00, -84.00)mm)
  [error] copper-clearance: CAN1_CANL (copper of "CAN1_CANL" and "I2C_SDA" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -85.50)mm)
  [error] copper-clearance: CAN1_RXD (copper of "CAN1_RXD" and "PMIC_PG" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: CAN1_RXD (copper of "CAN1_RXD" and "PMIC_CORE_3V3" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: CAN1_TXD (copper of "CAN1_TXD" and "PMIC_MAIN_12V0" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: CAN1_TXD (copper of "CAN1_TXD" and "MCU_NRST" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: GND (copper of "GND" and "XTAL_OUT" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -82.50)mm)
  [error] copper-clearance: MCU_NRST (copper of "MCU_NRST" and "PMIC_MAIN_12V0" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -78.00)mm)
  [error] copper-clearance: PMIC_CORE_3V3 (copper of "PMIC_CORE_3V3" and "PMIC_PG" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -79.50)mm)
  [error] copper-clearance: PMIC_EN (copper of "PMIC_EN" and "XTAL_IN" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [error] copper-clearance: PMIC_EN (copper of "PMIC_EN" and "PMIC_IO_1V8" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [error] copper-clearance: PMIC_IO_1V8 (copper of "PMIC_IO_1V8" and "XTAL_IN" closer than 0.127mm at 1 place(s); worst gap -0.250mm near (5.00, -81.00)mm)
  [warning] decoupling-present: PMIC_MAIN_12V0 (power rail has no decoupling capacitor)
  [info] esd-protection: CAN1_CANH (externally-exposed signal net has no ESD protection)
  [info] esd-protection: CAN1_CANL (externally-exposed signal net has no ESD protection)
  [warning] gateway-profiles/can-esd-missing: CAN1_CANH (CAN signal net CAN1_CANH is exposed on a connector with no ESD protection in reach)
  [warning] gateway-profiles/can-esd-missing: CAN1_CANL (CAN signal net CAN1_CANL is exposed on a connector with no ESD protection in reach)
  [error] gateway-profiles/can-host-incomplete: U4 (CAN host U4 declares the interface but is missing required signal STB)
  [warning] gateway/signal-net-naming: XTAL_IN (net name matches no allowed naming pattern)
  [warning] gateway/signal-net-naming: XTAL_OUT (net name matches no allowed naming pattern)
  [error] i2c-pull-up: I2C_SCL (I2C net has no pull-up resistor to a rail)
  [error] i2c-pull-up: I2C_SDA (I2C net has no pull-up resistor to a rail)
  [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)
  [warning] reverse-blocking-absent: PMIC_MAIN_12V0 (connector feeds a power input with no reverse-blocking element in the path)
  [error] supply-exceeds-abs-max: U2 (power-input pin 1 on rail "PMIC_CORE_3V3": nominal 3.3V exceeds absolute-maximum VIN 3V — datasheet "ACME-LDO-1V8 (placeholder, not transcribed)" page 0, "" (mock, confidence 0.3))
  [info] test-point-coverage: GND (rail carries no test point; bring-up and factory test cannot probe it)
  [info] test-point-coverage: PMIC_MAIN_12V0 (rail carries no test point; bring-up and factory test cannot probe it)
  [error] track-width: CAN1_CANH (net has 1 track segment(s) narrower than the 0.127mm fabrication floor)

28 finding(s) total
258 subject(s) considered by 36 rule(s), 7 not considered (--verdicts for the detail)

Two things changed, one from each half.

gateway/signal-net-naming appears, with two findings. That is the rules half:

[warning] gateway/signal-net-naming: XTAL_IN (net name matches no allowed naming pattern)
[warning] gateway/signal-net-naming: XTAL_OUT (net name matches no allowed naming pattern)

And test-point-coverage went from 1 finding to 2. Nothing about that rule changed. A net that was invisible to it became a rail, and that rail has no test point:

[info] test-point-coverage: GND (rail carries no test point; bring-up and factory test cannot probe it)
[info] test-point-coverage: PMIC_MAIN_12V0 (rail carries no test point; bring-up and factory test cannot probe it)

That second change is the lexicon half doing its job. It is worth sitting with, because it is the shape of the whole tier system: supplying a tier does not just add its own findings, it can change what the rules you already had are able to see.

Reading the lexicon directly

Inferring the lexicon from a finding count that moved is indirect. You can ask the fact base itself, putting the same question this rung opened with:

$ agni query designs/gateway/gateway.edn 'rail(?n) => ?n' --conventions conventions.yaml
n               provenance
GND             designs/gateway/gateway.edn:GND
PMIC_CORE_3V3   designs/gateway/gateway.edn:PMIC_CORE_3V3
PMIC_IO_1V8     designs/gateway/gateway.edn:PMIC_IO_1V8
PMIC_MAIN_12V0  designs/gateway/gateway.edn:PMIC_MAIN_12V0

4 result(s)

Four rails, where the same query without the flag found one. Nothing was added to the design and no rule ran. The lexicon changed what the engine believes a rail is, and every relation derived from that role now answers differently.

This is the loop to write a lexicon in: ask, compare against the rails you know the board has, adjust the pattern, ask again. --conventions on query reads only the lexicon half, since a query runs no rules.

Writing your own

Start with the lexicon, not the rules. Run agni query <design> 'rail(?n) => ?n' --conventions <your file> on a real board and compare the list against the rails you know it has. Whatever is missing tells you the pattern you need. Repeat until the list is right, and only then write naming rules.

A rule’s allow is a list of patterns, and a net name passes if it matches any of them. Getting this backwards is easy: allow describes what is legal, so a name matching none of them is the finding.

Distinct name spaces need distinct lexicon entries. Rail net names and supply pin names are usually named differently, so rail: and supply_pin: are separate dimensions rather than one shared pattern list.

Next

Your interfaces, which declares a bus once and checks every board against it.