Every diagnostic carries two things: a stable code and an exact source span — the range of characters in your source that the message is about. The message always names the exact construct at fault (an instance, a pin, a trait, a unit), never a bare "type mismatch". Here is a real one, produced by annotating a 3.3 V rail with a capacitance:
$ cohdl check .
error[E110]: net voltage annotation has the wrong unit type: expected `Voltage`, found `Capacitance`
--> src/main.cohdl:67:15
|
67 | net V3V3 [100nF]: reg.VOUT, reg.FB, mcu.IOVDD, mcu.QSPI_IOVDD, mcu.USB_OTP_VDD, mcu.VREG_VIN,
| ^^^^^ `100nF` is a `Capacitance`
= help: annotate with a voltage (e.g. `[3.3V]`), or `[gnd]` for ground
1 error emitted
The registry is governed by a stability rule: a code is
issued once and never repurposed. If a check's behavior changes enough that
the old meaning no longer applies, the code is retired — marked
[DEPRECATED], its row and meaning kept documented — and a new
code is issued. A code's meaning is never edited in place. That is what makes
codes safe to build on: CI gates, editor quick-fixes and any automation that
matches on a code will keep meaning the same thing. The
--json output of
cohdl check and cohdl build carries the same codes
machine-readably.
The organizing principle: a block is chosen by the kind of mistake, not by which compiler pass happens to catch it. A unit mismatch is a unit mismatch even when it is caught at a generic substitution site — so it lives in E1xx, not in the generics block.
Block ownership
Each block of the code space is owned by one language mechanism. Severities:
all Exxx codes are errors (the registry records two explicit
exceptions, covered below);
Dxxx severity is per rule.
| Block | Owner mechanism |
|---|---|
E00x | CLI invocation (pre-pipeline — not a source diagnostic) |
E0xx | Lexing and parsing |
E1xx | Unit system (RFC-001) — all unit-mismatch and unit-literal diagnostics, regardless of call site |
E2xx | Name resolution |
E3xx | Trait satisfaction at impl (RFC-003) |
E4xx | Generics (RFC-007), excluding unit mismatch (that is E1xx) |
E5xx | Sub-circuit fns (RFC-006) |
E6xx | Design assembly and nets |
E7xx | Pin connection obligations (RFC-002) |
E8xx | Designators and parts (RFC-005) |
E9xx | Structural variants (RFC-008) |
E10xx | Layout constraints (RFC-013) |
E11xx | Package resolution (RFC-029) — manifest [dependencies] plus cohdl.lock, pre-pipeline |
E12xx | Registry interaction (RFC-030) — a different kind of mistake from E11xx's local resolution and hash failures |
D00x | Residual DRC (RFC-004) — exactly four, never more |
The registry is enforced mechanically: a completeness test runs in both directions on every build. Every diagnostic code literal in the compiler source must appear as a registry row, and every row that is not retired or reserved must have at least one real call site. The tables below therefore describe the compiler as it is, not as intended.
E00x — CLI invocation
Failures of the invocation itself, before any source is read. These are not
source diagnostics: they exit with code 2, print prose to stderr, and never
appear inside a --json diagnostics array.
| Code | Meaning |
|---|---|
E000 |
Invocation-level failure — bad flags, an invalid flag for the command, a missing path or project, a design-selection failure, or nothing to build. Exit code 2, prose on stderr, never inside a --json diagnostics array; source diagnostics collected before the failure still render to stderr first. CLI-only, so it deliberately has no source-diagnostic call site. (Classifying post-collection selection failures here is a documented deviation from RFC-010's pre-collection wording, pending amendment.) |
E0xx — lexing and parsing
Mistakes the lexer and the parser catch.
| Code | Meaning |
|---|---|
E001 | Unexpected character — includes the targeted °C → C guidance; a standalone Ω is E107. |
E002 | Unterminated string literal. |
E010 | Unexpected token — the message names what was expected and what was found. |
E1xx — unit system (RFC-001)
This block owns every unit-mismatch and unit-literal diagnostic, regardless of where in the pipeline the mismatch is caught — including generic substitution sites.
| Code | Meaning |
|---|---|
E101 | Non-ASCII unit spelling (Ω, °C) directly after a number. |
E102 | Negative bare number — only Temperature and Length literals may be negative. |
E103 | Unknown unit suffix. |
E104 | SI prefix not allowed for this unit, including any prefix on Temperature or Tolerance. |
E105 | Leading - on a unit literal whose type is not signed — only Temperature and Length may carry a sign. |
E106 | Literal not exactly representable — too precise, or out of range. |
E107 | Standalone Unicode Ω with no preceding number — deliberately narrower than E101 so the message can be maximally specific. |
E110 | Unit-type mismatch — always names expected versus actual (e.g. "expected Voltage, found Capacitance"). |
E111 | Bare number where a unit-typed value is required. |
E112 | Unit-type generic argument has the wrong unit type (relocated from the retired E402 — unit mismatch belongs in E1xx). |
E113 | Bare number as a unit-type generic argument (relocated from the retired E404). |
E2xx — name resolution
Resolving names across the project's files, its modules and its declared dependencies.
| Code | Meaning |
|---|---|
E201 | Duplicate top-level declaration. |
E202 | Unknown name. |
E203 | Unknown pin on a device or trait. |
E204 | [RESERVED, not yet implemented] Unknown spec field — no call site yet. |
E205 | Name is the wrong kind — e.g. a trait used where a device is required. |
E206 | Instance and net names beginning with __ are reserved for compiler-generated expansion names. |
E207 | Ambiguous unqualified name (RFC-016) — declared at more than one module path; the message names every candidate and suggests qualifying or a use import. |
E208 | use collision (RFC-016) — one local name imported from two different paths; the message names both. |
E209 | Visibility violation (RFC-016) — a non-pub item referenced from another package; names the item and its declaring package. |
E210 | Unspellable module-path segment (RFC-016) — the package root, a src/ or std/ subdirectory, or a nested-file name is a keyword or not an identifier, so its declarations cannot be referenced by any qualified path. |
E211 | Malformed or misplaced array-typed instance or indexed reference (RFC-024) — an array length below 1 in inst NAME: [Device; N]; a bare unindexed reference to an array-typed instance (NAME alone is never a valid reference — index it, NAME[0]); an index applied to something that is not array-typed; a non-whole-number index; an empty range or a stride below 1; or a range or index list used outside a net's member list — place, nc, fn-call arguments and #[bypass(NAME[i].PIN, …)] each take a single element NAME[i], since "a range at once" has no single meaning there (one capacitor cannot bypass three pins). A well-formed index that is simply outside the array's declared length is E202, not E211. |
E3xx — trait satisfaction at impl (RFC-003)
Whether an impl actually satisfies the trait it claims.
| Code | Meaning |
|---|---|
E301 | impl Trait for Device unsatisfied — names the trait, the device, and the exact missing or mismatched pin role or spec field. |
E302 | Missing sub-trait impl — names the required sibling impl (a chain diagnostic). |
E303 | Duplicate impl for the same (trait, device) pair — points at the earlier one. |
E304 | An impl mapping names a role or field the trait does not require. |
E305 | An impl mapping target is not a pin or spec of the device. |
E306 | Cyclic sub-trait bounds. |
E4xx — generics (RFC-007)
Generics-specific mistakes only. A unit mismatch at a generic site is E112 or E113, not here — see the organizing principle above.
| Code | Meaning |
|---|---|
E401 | Wrong number of generic arguments. |
E402 | [DEPRECATED → E112] Unit-type generic argument has the wrong unit type — retired name, relocated to E1xx by RFC-011. |
E403 | Trait bound not satisfied at instantiation — names the missing trait and the concrete type. |
E404 | [DEPRECATED → E113] Bare number as a unit-type generic argument — retired name, relocated to E1xx by RFC-011. |
E405 | Generic argument is not concrete after substitution. |
E406 | Invalid generic parameter declaration — e.g. a default on a trait-bound parameter. |
E5xx — sub-circuit fns (RFC-006)
Calling and expanding sub-circuit functions.
| Code | Meaning |
|---|---|
E501 | Cyclic fn call chain — the message shows the full cycle. |
E502 | Wrong number of call arguments. |
E503 | Call argument kind mismatch (a pin where an instance is expected, or the reverse). |
E504 | Unknown fn. |
E6xx — design assembly and nets
Assembling the design's nets and nc declarations.
| Code | Meaning |
|---|---|
E601 | [RESERVED, not yet implemented] Floating net — a net resolving to zero instance pins (a planned reclassification of RFC-004's W002); no call site yet. |
E602 | A net or nc member is not a known pin. |
E603 | Contradictory annotations on a merged net — two voltages, or a voltage plus [gnd]. |
E7xx — pin connection obligations (RFC-002)
Every required pin must be accounted for at final assembly: connected in a
net, or declared deliberately unconnected with nc.
| Code | Meaning |
|---|---|
E701 | Required pin unresolved — it appears in neither net nor nc. |
E702 | Required pin contradictory — it appears in both net and nc. |
E8xx — designators and parts (RFC-005)
Everything between the design and the physical part: designator allocation, part binding (provisional syntax), and the pad, footprint, mount-hole and silkscreen checks (RFC-018, RFC-021, RFC-022, RFC-025, RFC-031).
| Code | Meaning |
|---|---|
E801 | Instance not part-bound at build — the netlist and BOM would lie. |
E802 | Invalid part declaration — a missing mpn or footprint on a primary, a missing mpn on an alt, or a non-concrete device. |
E803 | #[designator] override collision. |
E804 | Invalid designator format — must be a prefix plus a number, e.g. U7. |
E805 | Invalid pad declaration (RFC-018) — a missing or unknown field, a non-Length dimension, a non-positive size or drill extent, a size arity that disagrees with the shape, or drill and plated_through_hole appearing without each other (they must appear together or not at all). Also covers the provisional slot form drill: (w, l) — wrong arity, a slot on a circle pad, or a slot larger than the pad on either axis — and bounded SMD controls such as an invalid or mutually exclusive chamfer or corner_radius. |
E806 | Invalid footprint body (RFC-018) — a malformed member, a duplicate courtyard or silkscreen_ref, a non-Length coordinate, or a non-positive courtyard extent. Repeating an electrical pad number is valid: one terminal may have multiple copper, paste or via placements. |
E807 | Footprint/device pad mismatch at build (RFC-018) — the footprint's pad numbers must exactly match the bound device's physical pin numbers; the message names the missing and extra numbers. |
E808 | Malformed IPC-7351 footprint name (RFC-021) — a footprint identifier whose prefix is one of the closed IPC-7351 families (QFP, QFN, SOIC, SOP, SOT, BGA, CHIP, MELF) but does not parse against that family's grammar: a missing or invalid density suffix (N, L, M), a non-numeric or misordered dimension field, or trailing characters. Names the specific parse failure. A name whose prefix is outside the closed set is a free-form identifier and is not checked. |
E809 | IPC-7351 name-versus-geometry mismatch (RFC-021) — the footprint identifier's declared pin count or pitch disagrees with the footprint's own pad placements (pins = the count of distinct electrical pad numbers, minus the _1EP exposed pad; pitch = the closest spacing between distinct pad numbers); names the footprint and both values. |
E810 | Malformed mount_hole (RFC-022, extended by RFC-023) — a footprint's mechanical locating hole is ill-formed: a duplicate mount_hole number within one footprint, a non-Length or out-of-range offset or dimension, a non-positive diameter or size, a plating value outside the closed set {non_plated, plated}, a shape: outside the closed set {rect, circle, oval}, a size: tuple that is not exactly (w, h), or a geometry field that disagrees with the explicit or defaulted shape — circle takes diameter D, while rect and oval take size: (w, h). When no shape: is written the hole defaults to circle, and the mismatch diagnostic says so. mount_hole numbers are their own namespace, never checked against pad numbers or the bound device's pins. |
E811 | Invalid pad-placement rotation (RFC-025) — pad N: Sym at (x, y) rotate ANGLE with ANGLE outside 0..=359, or not a whole number of degrees. Tracks place's own range (see E1007), which under the arbitrary-angle deviation is any whole degree rather than RFC-020's closed {0, 90, 180, 270}. A full turn is 0, so 360 and above is rejected rather than reduced. Checked at declaration, like the rest of the footprint-body checks. |
E812 | Invalid silkscreen graphic (RFC-031) — an unknown statement kind; a malformed primitive (a polygon with fewer than three vertices, a non-Length or non-positive dimension); an invalid closed-set value (fill, marker shape); a marker naming a pad number the footprint does not declare; a polarity_marker on a footprint with fewer than two distinct electrical pad numbers (repeated physical placements of one number are one terminal); or more than one silkscreen block. |
E9xx — structural variants (RFC-008)
Devices that ship in more than one package declare structural variants; these
codes police the variant declarations and the [VARIANT]
selectors that pick one.
| Code | Meaning |
|---|---|
E901 | A device pin has no role annotation — every pin needs an explicit role; the message lists the six valid roles. |
E902 | A declared variant has no pins[VARIANT] block — exhaustiveness at the device declaration, naming the missing variant. |
E903 | A [VARIANT] selector names an undeclared variant — the message lists the valid set. |
E904 | [VARIANT] selector omitted on a device that declares variants — there is no implicit default; the message lists the valid set. |
E905 | [VARIANT] selector on a device with no variants, or on a part (parts already select theirs). |
E906 | Duplicate variant name in variants { } — checked at parse. |
E907 | A pins[X] or spec[X] qualifier names an undeclared variant. |
E908 | An unqualified pins { } block on a device that declares variants. |
pins[VARIANT]", its E905 is "duplicate variant". The compiler
issues the eight codes above instead: they predate the accepted table, they
have real call sites and fixture tests, and the registry's own stability rule
forbids repurposing an already-issued code — renumbering to match the table
would violate that rule on day one. Until the RFC-011 table is amended to the
eight-code assignment, this block is a documented deviation, tracked in the
compliance ledger.
E10xx — layout constraints (RFC-013)
Structural validation of layout {} constraints against their own
closed vocabulary — never a connectivity or DRC check, and never affecting
the netlist bytes.
| Code | Meaning |
|---|---|
E1001 | A layout constraint references a net that is not declared in the design. |
E1002 | Duplicate net_class name. |
E1003 | diff_pair does not name exactly two nets. |
E1004 | length_match names fewer than two nets. |
E1005 | [RESERVED, not yet implemented] net_class referenced before declaration — activates only once a future constraint kind references a net_class by name (the four current kinds reference nets, not classes). |
E1006 | Invalid board_outline: "path.dxf" (RFC-020). Check-time sub-cases: the path is not project-relative (absolute, ..-escaping, a URL, or a drive letter), more than one outline, or an outline inside a called fn rather than the design's own layout {} block. Build-time sub-cases, when the DXF is actually read: the file cannot be read, is not valid DXF, has no closed polyline on the Edge.Cuts layer, the outline polyline is not closed, or it has fewer than three vertices. |
E1007 | Invalid place <inst> at (x, y) [rotate ANGLE] (RFC-020) — the named instance does not exist among the design's own top-level instances, a coordinate is not a Length (mm) value or is out of geometry range, rotate is not a whole number of degrees in 0..=359 (a ledgered deviation from RFC-020's closed set {0, 90, 180, 270}; 360 and above is rejected rather than reduced, since a full turn is 0 and rotate 450 is likelier a slip than a deliberate 90), the instance is placed more than once, or the place appears inside a called fn. Placing an instance declared inside a called fn is a disclosed, deferred gap. |
E1008 | Invalid placement side (RFC-026) — place … side SIDE with SIDE outside the closed set {top, bottom}. side defaults to top when omitted; it is a whole-component placement fact, independent of (and never implemented via) the per-pad layer field. |
E1009 | Invalid physics-constraint attribute (RFC-027) — a #[ground], #[high_current], #[impedance], #[bypass], #[crystal_oscillator], #[switching_converter] or #[bga_fanout] attribute is malformed or misplaced: attached to the wrong declaration kind (the first three are net-only, the rest inst-only, none valid elsewhere), duplicated on one declaration or one merged net, an unknown or duplicate argument name, a missing required argument (switching_converter's inductor:), a ground kind outside {primary, secondary}, more than one #[ground(primary)] net per design, a reference to a non-existent instance or pin, a crystal signal pin that maps to more than one pad, an array-typed instance target, or a malformed diff_pair(...) physics bracket (an unknown or duplicate field). Unit-type mismatches on numeric arguments are E110, per the organizing principle — a documented deviation from RFC-027's literal E10xx reservation, recorded in the compliance ledger. |
E11xx — package resolution (RFC-029)
Enforced at project load, before any .cohdl file is opened: an
invalid dependency declaration, an unresolvable version or a locked-hash
mismatch gates the whole pipeline. These diagnostics anchor to
cohdl.toml and cohdl.lock lines rather than source
spans — nothing has been parsed yet — and in --json mode they
ride the ordinary diagnostics array with a whole-line location. E1105 is CLI
prose on stderr in every mode: deliberately unsuppressable, never part of a
--json diagnostics array (a documented deviation, mirroring
E000's classification).
| Code | Meaning |
|---|---|
E1101 | Invalid [dependencies] entry — a version range (^, ~, >=, <, *, ,), a malformed or non-canonical version (leading zeros), an invalid dependency name, or a duplicate entry. CoHDL requires exact X.Y.Z versions permanently — hardware has no "safe patch" assumption. The help suggests the nearest exact version when one is discoverable. |
E1102 | Unresolvable dependency — no package on disk declares the pinned version (versions come from package manifests, never directory names). The help lists the searched family directories (<project>/deps/<name>, then the registry root's <name>/) and every version actually available. |
E1103 | Locked-hash mismatch — the resolved package content re-hashes differently from its cohdl.lock row. The load-bearing guarantee: a version number is a human label; the hash is the identity. A hard error, never a warning; names both hashes. |
E1104 | Pre-RFC-029 manifest — no [dependencies] section (or no std pin without --no-std). The help carries the exact section to add and names cohdl update as the automatic migration. |
E1105 | (Warning, CLI prose.) std override active — --std or COHDL_STD bypasses the locked std, so the build is not reproducible. Mandatory and unsuppressable on every affected run. |
E1106 | Package identity error — a package under a family directory declares a different name, carries no (or an unparseable) [package] identity, or two packages declare the same (name, version): a version is one immutable identity. |
E1107 | Unparseable cohdl.lock — the machine-generated file was corrupted or hand-edited; the help says to restore it from version control, or delete it and re-resolve. |
E12xx — registry interaction (RFC-030)
CLI-level failures talking to
registry.cohdl.org — deliberately a
separate block from E11xx, because registry-interaction failures are a
different kind of mistake from local resolution and hash failures; E1204 in
particular must never be conflated with E1103. Like E11xx these are
pre-source diagnostics. Human commands surface them as CLI prose;
cohdl search --json instead emits the existing diagnostic JSON
document on stdout for an E1204 registry/protocol failure (successful search
output uses the separate RFC-030 discovery schema).
| Code | Meaning |
|---|---|
E1201 | Authentication missing or rejected — cohdl publish without a stored token, or the registry refused it; the help names cohdl login. |
E1202 | Namespace rejection — a name outside the closed three-tier grammar (bare, @brand/name, @contrib/name), or the server refused a publish: a bare name not owned by the official account, an unverified brand, a version already published, an archive whose own manifest disagrees with the publish, or a version declaring no [package] license. Checked locally pre-flight and server-side (the server is authoritative). |
E1203 | Package or version not published on the registry. |
E1204 | Registry unreachable or response-protocol failure; dependency operations additionally name when no cached copy is available. Explicitly distinct from a hash mismatch (E1103): different kinds of mistake. |
E1205 | cohdl remove of a name not in [dependencies] — the help lists the actual current dependency list; never a silent no-op. |
E1206 | Client/server content-hash disagreement — a warning at publish time (the server's hash is authoritative for what cohdl.lock will verify); a hard error on download (corrupted content is never cached). |
D00x — residual DRC (RFC-004)
The residual design-rule checks: exactly four, never more. Everything a fifth structural rule might catch is the type system's job instead — that is what keeps the DRC list from growing into a checklist.
| Code | Severity | Rule |
|---|---|---|
D001 | error | Voltage-exceed: a part's voltage_rating spec is lower than the annotated net voltage. |
D002 | error | Polarity-mismatch: a Polarized anode pin on a [gnd] net. |
D003 | warning | Single-driver: a net whose only connected pin is a driver (output or power_out) — the driver drives nothing. |
D004 | error | Multi-driver: a net with two or more driver-type pins (output or power_out). |
Warnings and errors
Severity is part of the registry, not a presentation choice.
Exxx codes are errors, with two exceptions the registry records
explicitly: E1105 (the std-override notice) is always a warning, and E1206
(client/server hash disagreement) is a warning at publish time but a hard
error on download. Dxxx severity is per rule — D001, D002 and
D004 are errors; D003 is a warning. The verdict follows severity: any
error-severity diagnostic makes cohdl check fail, while warnings
alone leave the verdict at pass. Plain-text and --json output
always report the identical diagnostic set, field for field.