crystal-load-caps
A passive crystal has an oscillator terminal with no load capacitor to ground.
Remedy ¶
Fit a load capacitor from each oscillator terminal to ground, sized from the crystal’s specified load capacitance and the stray capacitance of the layout rather than copied from another design.
What it means ¶
A passive quartz crystal (a two-terminal resonator, ref-des Y/XTAL) needs a load
capacitor from each of its two oscillator terminals to ground. This rule flags a crystal
terminal that carries no capacitor.
Why engineers want it ¶
A crystal is specified for a nominal LOAD CAPACITANCE (C_L). The two load caps and the
board’s stray capacitance together present that load. With a load cap missing the
oscillator sees the wrong load and may fail to start, start only sometimes across
temperature, or run off its rated frequency. Everything clocked from it drifts with it, so
the defect surfaces as flaky UART/USB/CAN timing rather than an obvious “no clock”.
Impact ¶
Oscillator no-start or off-frequency operation; timing-dependent field failures that pass bench bring-up.
Scope note ¶
The rule quantifies over crystal COMPONENTS, not nets, because “which terminals” and “is
this an active oscillator” are both cross-net facts about the one part. Imagine a crystal
connected to a non-ground POWER RAIL. The rule treats it as an ACTIVE oscillator and skips
it entirely, so the rule never demands load caps of the wrong device. An active oscillator
is a packaged XO with a Vdd pin, which supplies its own load internally and takes no
external caps. A part classed as an oscillator or a ceramic resonator is excluded the same
way. A part reports not-considered when it has anything other than exactly two non-ground
signal terminals, since the count cannot tell it from an active oscillator whose supply net
was not recognized as a rail. The grounded case pins of a 3- or 4-pin crystal are
ground-named, so they are not signal terminals and are excluded. An unresolved external net
is skipped (the cap may live on an unread sheet), matching the decoupling-present / bulk-cap
external-skip convention. This rule checks PRESENCE and not value. Whether the load-cap
VALUE matches the crystal’s spec, as 2*(C_L - C_stray), is a datasheet-joined refinement
(WS10) and out of scope here.
When the oscillator carries its own load capacitance ¶
Many RTCs and low-power MCUs build the load capacitors into their oscillator pins, often
selectable in a register, so the crystal sits straight across two pins with nothing else on
the board. The MNT Reform motherboard’s PCF8523T and the RoyalBlue54L Feather’s nRF54L15 are
both wired this way. Suppose the part on a terminal net has a datasheet in the run, and the
datasheet binds an integrated load capacitance to that PIN. Such a row is printed CL(itg) or
C_INT, or named “integrated” or “internal load capacitance”. The terminal then passes, and
the pass names the part, the value and the datasheet row (agni issue 1247). A row stated for
the whole part credits nothing, because an MCU often integrates capacitors on one oscillator
and not the other.
Without that datasheet the terminal still fails, since the rule cannot tell an RTC that
integrates its capacitors from one that does not. The verdict then says which IC on the net it
could not read, so a reader knows the finding may be a design choice that --params would
settle. The datalog twin reads no datasheet, so it keeps reporting the terminal either way.
For software readers ¶
Think of the crystal as a component with a hard runtime dependency, one the type system can’t express. It only works when two specific companion parts, the load caps, are wired to it. This is a static “is the required dependency present on this node” check, the same shape as decoupling-present (“does this power rail have its bypass cap”). The active-XO skip is a guard clause. An active oscillator is a different type that satisfies the dependency internally, and we detect it structurally (it has a power pin) rather than by trusting a name.
Query structure ¶
select crystals; for each, gather non-ground terminal nets and whether it has a power pin; skip if powered, decline unless there are exactly two terminals, then require a capacitor on each terminal net.
for Y in components where class(Y) == clock
and class(Y) not in {oscillator, ceramic_resonator}:
terms = nets(Y) where not ground(net) and not power_rail(net)
powered = any net(Y) is a non-ground power rail
if powered: continue
if count(terms) != 2: NOT_CONSIDERED(Y); continue
for N in terms:
if external(N): NOT_CONSIDERED(Y.pin on N)
elif not exists P in N.connections where class(P) == capacitor: FIRE(Y, N)
Reads: component.class, net.attributes (external), net.names (the ground / power-rail skip), on_net. Tier R.