CoHDL
  1. Docs
  2. Error codes

Error codes

Every diagnostic the compiler can emit, grouped by the kind of mistake it names. Codes are issued once and never repurposed, so a tool that keys on a code today can trust it in every future release.

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
E00xCLI invocation (pre-pipeline — not a source diagnostic)
E0xxLexing and parsing
E1xxUnit system (RFC-001) — all unit-mismatch and unit-literal diagnostics, regardless of call site
E2xxName resolution
E3xxTrait satisfaction at impl (RFC-003)
E4xxGenerics (RFC-007), excluding unit mismatch (that is E1xx)
E5xxSub-circuit fns (RFC-006)
E6xxDesign assembly and nets
E7xxPin connection obligations (RFC-002)
E8xxDesignators and parts (RFC-005)
E9xxStructural variants (RFC-008)
E10xxLayout constraints (RFC-013)
E11xxPackage resolution (RFC-029) — manifest [dependencies] plus cohdl.lock, pre-pipeline
E12xxRegistry interaction (RFC-030) — a different kind of mistake from E11xx's local resolution and hash failures
D00xResidual 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.

CodeMeaning
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.

CodeMeaning
E001Unexpected character — includes the targeted °CC guidance; a standalone Ω is E107.
E002Unterminated string literal.
E010Unexpected 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.

CodeMeaning
E101Non-ASCII unit spelling (Ω, °C) directly after a number.
E102Negative bare number — only Temperature and Length literals may be negative.
E103Unknown unit suffix.
E104SI prefix not allowed for this unit, including any prefix on Temperature or Tolerance.
E105Leading - on a unit literal whose type is not signed — only Temperature and Length may carry a sign.
E106Literal not exactly representable — too precise, or out of range.
E107Standalone Unicode Ω with no preceding number — deliberately narrower than E101 so the message can be maximally specific.
E110Unit-type mismatch — always names expected versus actual (e.g. "expected Voltage, found Capacitance").
E111Bare number where a unit-typed value is required.
E112Unit-type generic argument has the wrong unit type (relocated from the retired E402 — unit mismatch belongs in E1xx).
E113Bare 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.

CodeMeaning
E201Duplicate top-level declaration.
E202Unknown name.
E203Unknown pin on a device or trait.
E204[RESERVED, not yet implemented] Unknown spec field — no call site yet.
E205Name is the wrong kind — e.g. a trait used where a device is required.
E206Instance and net names beginning with __ are reserved for compiler-generated expansion names.
E207Ambiguous unqualified name (RFC-016) — declared at more than one module path; the message names every candidate and suggests qualifying or a use import.
E208use collision (RFC-016) — one local name imported from two different paths; the message names both.
E209Visibility violation (RFC-016) — a non-pub item referenced from another package; names the item and its declaring package.
E210Unspellable 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.
E211Malformed 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.

CodeMeaning
E301impl Trait for Device unsatisfied — names the trait, the device, and the exact missing or mismatched pin role or spec field.
E302Missing sub-trait impl — names the required sibling impl (a chain diagnostic).
E303Duplicate impl for the same (trait, device) pair — points at the earlier one.
E304An impl mapping names a role or field the trait does not require.
E305An impl mapping target is not a pin or spec of the device.
E306Cyclic 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.

CodeMeaning
E401Wrong number of generic arguments.
E402[DEPRECATED → E112] Unit-type generic argument has the wrong unit type — retired name, relocated to E1xx by RFC-011.
E403Trait 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.
E405Generic argument is not concrete after substitution.
E406Invalid generic parameter declaration — e.g. a default on a trait-bound parameter.

E5xx — sub-circuit fns (RFC-006)

Calling and expanding sub-circuit functions.

CodeMeaning
E501Cyclic fn call chain — the message shows the full cycle.
E502Wrong number of call arguments.
E503Call argument kind mismatch (a pin where an instance is expected, or the reverse).
E504Unknown fn.

E6xx — design assembly and nets

Assembling the design's nets and nc declarations.

CodeMeaning
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.
E602A net or nc member is not a known pin.
E603Contradictory 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.

CodeMeaning
E701Required pin unresolved — it appears in neither net nor nc.
E702Required 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).

CodeMeaning
E801Instance not part-bound at build — the netlist and BOM would lie.
E802Invalid 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.
E804Invalid designator format — must be a prefix plus a number, e.g. U7.
E805Invalid 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.
E806Invalid 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.
E807Footprint/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.
E808Malformed 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.
E809IPC-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.
E810Malformed 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.
E811Invalid 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.
E812Invalid 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.

CodeMeaning
E901A device pin has no role annotation — every pin needs an explicit role; the message lists the six valid roles.
E902A declared variant has no pins[VARIANT] block — exhaustiveness at the device declaration, naming the missing variant.
E903A [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).
E906Duplicate variant name in variants { } — checked at parse.
E907A pins[X] or spec[X] qualifier names an undeclared variant.
E908An unqualified pins { } block on a device that declares variants.
A documented deviation, pending amendment RFC-011's accepted text proposes a five-code E9xx block (E901–E905) with different assignments — its E902 is "missing selector", its E904 is "missing 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.

CodeMeaning
E1001A layout constraint references a net that is not declared in the design.
E1002Duplicate net_class name.
E1003diff_pair does not name exactly two nets.
E1004length_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).
E1006Invalid 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.
E1007Invalid 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.
E1008Invalid 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.
E1009Invalid 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).

CodeMeaning
E1101Invalid [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.
E1102Unresolvable 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.
E1103Locked-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.
E1104Pre-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.
E1106Package 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.
E1107Unparseable 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).

CodeMeaning
E1201Authentication missing or rejected — cohdl publish without a stored token, or the registry refused it; the help names cohdl login.
E1202Namespace 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).
E1203Package or version not published on the registry.
E1204Registry 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.
E1205cohdl remove of a name not in [dependencies] — the help lists the actual current dependency list; never a silent no-op.
E1206Client/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.

CodeSeverityRule
D001errorVoltage-exceed: a part's voltage_rating spec is lower than the annotated net voltage.
D002errorPolarity-mismatch: a Polarized anode pin on a [gnd] net.
D003warningSingle-driver: a net whose only connected pin is a driver (output or power_out) — the driver drives nothing.
D004errorMulti-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.