Electrical rule check
erc.check(netlist) runs before anything is placed or routed. The rules are deliberately the kind of check that catches the mistakes a language model actually makes — a wrong pad name, missing decoupling, an LED wired straight across a rail — rather than a box-ticking exercise. Everything found is reported; nothing is silently fixed. Errors make the command line exit non-zero and light the ERC pill red; warnings are shown and do not block.
| Rule | Severity | Fires when |
|---|---|---|
dup-refdes | error | a reference designator is used by more than one part |
dup-net | error | a net name is declared more than once |
unknown-ref | error | a net references a part that does not exist |
bad-pin | error | a pin name is not a pad on the part's footprint — the message lists the pads it has |
no-ground | error | no net is declared ground |
multi-ground | warning | more than one ground net |
orphan | error | a part is on no net at all |
floating-pin | warning | an IC, connector, switch or transistor has unconnected pins |
no-bulk-cap | warning | a power net has no capacitor of at least 1 µF |
ic-no-rail | warning | an IC touches no power net |
ic-no-gnd | error / warning | an IC's symbol names a ground pin (GND, VSS, VEE, V-, VS-, with any prefix or suffix) and that pin is on no ground net and no negative rail. A part whose symbol names no such pin — a relay, a split-supply amplifier whose V- sits on −5 V — has nothing to tie and gets no finding; a part with no symbol gets a warning saying it could not be checked. Until 13 Sep 2026 this was an error on any IC touching no ground net, which failed four relays twice, a TL072 and an INA128 on the 11 Sep suite |
no-decoupling | warning | an IC's rail has no capacitor of 100 nF class between that rail and ground |
rail-conflict | error | more than one power_out pin drives the same net (needs symbol pin typing) |
output-conflict | error | more than one output or tristate pin is shorted on a net |
undriven-rail | error | a net feeds power_in pins and nothing reaches it: no connector, no power_out pin, no IC pin whose symbol leaves it untyped — on that net, or on any net linked to it through a resistor, inductor, diode, switch or transistor. A rail fed from a battery connector through a slide switch and a load-switch FET is driven by the connector; until 13 Sep 2026 each net was read alone and that rail was an error |
led-no-resistor | error | an LED shares no net with any resistor — it would be driven straight from a rail |
led-resistor-too-small | error | the series resistor passes more than 30 mA — the message gives the current and the resistor that would give 20 mA |
led-resistor-too-large | warning | under 1 mA: the LED will be very dim |
led-resistor-power | error/warning | the series resistor dissipates more than its package is rated for |
resistor-power | error/warning | a resistor between two declared voltages dissipates (V₁−V₂)²/R, over its package rating |
regulator-power | error/warning | a linear regulator burns (V_in−V_out)×I_out, over what its package can shed |
cap-over-voltage | error | a capacitor sees more than its rated voltage |
cap-voltage-derating | warning | over 70 % of rating, where a class-2 ceramic has lost most of its capacitance |
The checks that needed a voltage
The seven rules above the fold came free with a netlist. The seven below it did
not: Ohm's law needs a voltage, and until nets carried voltage_v a net had a
name, a kind and a list of connections. led-no-resistor is the tell — it has
checked since the beginning that an LED has a series resistor and has never
once read that resistor, so 10R on a 5 V rail passed while putting 320 mA
through a part rated for 20.
They are computed in electrical.py and every one of them stays silent when
the voltage it needs is missing, which is most boards. Silence that looks like a
pass is the failure mode this pipeline exists to avoid, so the nets that
declared nothing are named in the board's notes instead:
2 power net(s) declare no voltage (+12V, +3V3) — so Ohm's law is unavailable on them: LED series resistors, resistor and regulator dissipation and capacitor ratings go unchecked rather than checked and passed
Package ratings are the industry nominals for chip parts (0402 62.5 mW, 0603 100 mW, 0805 125 mW, 1206 250 mW) and, for three-terminal packages, what they shed on ordinary 2-layer copper with no deliberate heatsinking — which is what every board here has. A part over half its rating warns; over its rating is an error. A package with no figure in the table is left alone rather than guessed at, and a regulator that is neither recognisably linear nor recognisably switching is left alone too: a switcher does not burn the difference, so flagging one would be wrong.
What the parts on a net can push through it
A 5.7 A MOSFET had its drain on a 0.25 mm trace and nothing anywhere said so. 0.25 mm carries 0.88 A; IPC-2221 asks for 3.313 mm at that current, thirteen times what was laid.
The silence was the bug, and it was in the check written to prevent exactly this. undeclared() reports a power net that never said what it carries — but it reads net.kind, and on the low-side switch board LOAD_DRAIN is typed signal. The design named the part "30V 5.7A N-Ch" and that never became a net current, so width_for_net_mm() fell to the 0.25 mm median with nothing to raise it and no note.
So a net now carries current because of what is soldered to it. rating_a() reads the part's own continuous rating and says where the number came from; carriers() maps it onto the nets that reach a conducting terminal; and carrier_gaps() reports any net routed narrower than the parts on it can drive. Both build sequences call it beside declaration_gaps(). On the board that started it:
net LOAD_DRAIN declares no current, so it is routed at 0.25mm, which carries 0.88A — but Q1 on it is rated 5.7A, 7× more. IPC-2221 asks for 3.31mm.
Where the rating comes from is measured, not assumed
The catalogue's typed attributes are the good source where they exist — 7,924 regulators carry Output Current, 5,234 pin headers Rated Current, 3,432 USB connectors Power Current Rating. They do not exist for the class that matters most: zero of 400 MOSFETs sampled carry a value for Continuous Drain Current (Id), an attribute the catalogue names on the part and leaves empty while the description carries the figure in plain sight. So MOSFETs, and only MOSFETs, fall back to the largest ampere figure in the description — sound there and nowhere else.
The attribute allowlist is split by what the number is evidence of, which is the distinction the check turns on:
- A driver's rating is a claim about the net. A regulator pushes its output current, a MOSFET passes its drain current, a fuse holds its hold current.
- A contact's rating is a claim about the contact — "this pin will not melt below 3 A" — and says nothing about what is on the wire.
Surge and quiescent figures are named in SURGE_ATTRS and excluded on the record rather than by oversight: each is a real current in unit='current' and none is a conduction rating. A surge figure sizes nothing, and a quiescent one would size a rail at microamps.
Three things it refuses to say
Each refusal is why the check is worth reading.
- A MOSFET's gate is not sized for the drain current, or
GATEwould want 3.31 mm on a net whose job is to carry charge to a resistor. - A connector's contact rating speaks only on a net the design already typed
powerorground. Read as a net current everywhere, it asked for 2.77 mm of copper onUSB_DP,I2C_SDAand four PWM lines — 13 false findings against 1 true one on the drone board, which is how a check stops being read. - Ground is left out entirely.
tests/test_electrical.py is 70 tests, up from 53, and pins the gate, the contact rating, the surge refusal, and a board of passives saying nothing. The bug it nearly shipped with is the one it exists to catch: a live Netlist carries the design's connections ({ref, pin, function}) and a stored board carries board_json()'s pins ({ref, pad, fn}). The first version read only the second, so it found three real problems on a board read off disk and nothing at all on the build path meant to prevent them. _terminals() reads both.
Every finding is shown in the workbench under Details → Not guaranteed with its rule name, in board.json under erc, and on the command line under ERC n errors, m warnings.
What ERC is not
It reads the netlist and asks whether the design is sane. It does not read the copper: whether the board can be built is a DRC question, which KiCad answers when it is installed — see KiCad and 3D models.