Files
rpicarrierboard/PORT_STATUS.md
2026-07-04 09:42:57 -04:00

18 KiB
Raw Permalink Blame History

CM5 Carrier Port — Status & Handoff

Handoff doc for whoever (human or LLM) continues this work. Read this first, then .claude/skills/kicad-port/SKILL.md (the mandatory workflow/verify-loop), then CM5_Carrier_Design.md (frozen rev-A spec).

▶ RESUME HERE (session handoff, 2026-07-04 — close-out passes COMPLETE, ready for layout prep)

State: branch port/strip-phase (merged → main), working tree clean. Sheets in carrier/: root CM5IO, CM5_GPIO, CM5_HighSpeed (reference, stripped), M2_Ekey, Power (stages 14), GPS_PPS. All rev-A blocks captured; power-budget review + datasheet confirms + hygiene pass done (see "Power-budget review" section below). Baseline anchor: analysis/baseline/carrier_stripped.json. Whole-design ERC = 133 (all pre-existing types; a good edit adds zero new); kicad-cli annotation warning GONE; BOM sane (105 components, all with value+footprint); SPICE 24 pass / 2 benign warn / 0 fail.

Close-out fixes landed (2026-07-04):

  • Removed orphaned U12 (RT9742) + C13 — fed the stripped HDMI_5v rail (ref-netlist-proven).
  • Cut J11 (USB-C prog) VBUS from +5v + dropped R9 bleed — host VBUS would have paralleled U_BUCK5's output (reference is USB-C-powered; carrier is not). VBUS pins NC; CC1/CC2 stay wired to CM5 (module implements the PD sink per SC1466 — supersedes the spec's discrete 5.1 k Rd); D± unchanged → rpiboot unaffected (CM5 has no VBUS-sense pin).
  • C_BULK1 100 µF 25 V→35 V (sits on VIN_PROT where SMBJ18A clamps ≤29.2 V).
  • Refdes hygiene: all generated refs digit-terminated (J_EJ_E1, C_VCAPC_VCAP1, …, #FLG15IN#FLG15IN1); generator emit-helpers now normalize tags. Title blocks: carrier identity, rev A, CM5IO derivation credited.

NEXT ACTION → layout prep: copy DF40 (CM5 connector) placement from the reference PCB, then floorplan: Power stage entry edge, RF keepout around M.2, PPS trace hygiene. Open items (decide at layout/BOM time, none block layout start — details in the review section): per-port USB-A ILIM vs shared 1.19 A (spec deviation), ESD array on J11 D± (spec BOM item 12, reference shipped without), M.2 socket contact rating ≥0.9 A/pin, J8 header draw guidance (≤1 A on 5 V, ≤400 mA on 3.3 V), L33A1 Isat 2.5 A vs TPS54202 fault current (hiccup-protected).

How to build/verify (every change):

  • Sheets are generated wholesale — edit tools/build_ekey.py / tools/build_power.py, then python3 tools/build_power.py --apply. Custom IC symbols go in carrier/CM5IO.kicad_sym (bare name) AND embed in the sheet as CM5IO:<name> — else ERC lib_symbol_issues.
  • Verify loop (judge by the integrated root run, not standalone sub-sheets): kicad-cli sch erc --format json -o /tmp/e.json carrier/CM5IO.kicad_sch (track delta vs 133, zero new types) → analyze_schematic.py carrier/CM5IO.kicad_sch + diff_analysis.py analysis/baseline/carrier_stripped.json … (zero regressions) → for bucks, simulate_subcircuits.py.
  • Gotchas (full list in memory port-progress): grid-snap all coords to 1.27 mm or labels don't bind; PWR_FLAG/GND must sit on a real pin coord; pre-existing +5v is hierarchical (not global); type feedback pins passive; connector/module pin types may need remodeling for cross-part ERC.

Power-budget review (2026-07-04, all datasheet-backed)

Tree: 15 V (J_PWR1, Phoenix MKDS-1.5 ≈13.5 A) → LM74700+CSD18540 ideal diode → VIN_PROT → 3 bucks. Worst-case input ≈ 40 W / 15 V ≈ 2.9 A — huge connector/FET margin.

Rail Source Rating Worst-case load Verdict
VIN_PROT LM74700 + CSD18540 (60 V, ~2 mΩ) 2.9 A (≈20 mW in FET)
+5v U_BUCK51 LM61460 (6 A; HS ILIM 8.911.5 A) 6 A CM5 (0.4 idle/0.9 typ/~3 A heavy, incl. its 3V3+1V8 outputs) + USB-A ≤1.53 A (U6 ILIM) + fan ~0.3 A + J8 5 V user if J8 draw documented ≤1 A
+3V3_RF U_BUCK33R1 LM61460 (6 A) 4 A design AW7915 33.5 A TX bursts ~2.5 A headroom
+3V3_AUX U_BUCK33A1 TPS54202 (2 A) ~1 A design GPS ~0.1 A + 74LVC1G17 ~mA
CM5_3.3V (module out) CM5 600 mA total LEDs+magjack+U5 ≈50 mA + J8 3.3 V user if J8 draw documented ≤400 mA
CM5_1.8V (module out) CM5 600 mA R4 = nf → unloaded

Datasheet confirms (closes all "confirm vs datasheet" leftovers):

  • LM74700: C_VCAP min 0.1 µF, rec ≥10×Ciss(FET) → our 1 µF ✓; EN→ANODE = documented always-on ✓.
  • LM61460: RT 33.2 k→400 kHz table row; our 31.6 k ≈ 420 kHz, in 200 k2.2 M range ✓. Ipk @5 A ≈ 5.8 A < HS ILIM min 8.9 A ✓.
  • L5/L33 (Bourns SRP1245A-4R7M): Isat 15 A > ILIM max 11.5 A ✓✓; Irms ~11 A ✓.
  • AP22653 (U6, R12=15 k): ILIM 0.941.53 A (typ 1.19 A, best-fit eqn ILIMIT_typ=30321/R^1.055), shared across both USB-A ports — spec §5 wanted 0.91.2 A per port. Deviation kept (reference-proven; protects the 5 V budget). Rev-B option: one AP22653 per port.
  • TPS54202: VIN abs-max 30 V vs SMBJ18A clamp ≤29.2 V — 0.8 V margin, accepted + documented. L33A1 (10 µH/2.5 A): Ipk @1 A ≈ 1.26 A ✓; fault current before hiccup can brush ~3 A > Isat — acceptable (hiccup-protected), consider Isat ≥3 A part at BOM time.

Sequencing: module drives its own 3.3/1.8 rails after PMIC_EN; +3V3_RF EN gated by CM5 PCIE_PWR_EN (up only after boot) ✓; +3V3_AUX EN = UVLO divider (start 10.1 V / stop 8.6 V) so GPS runs whenever 15 V is healthy ✓.

What this repo is

Two intertwined goals:

  1. Improve Claude's EDA tooling — the kicad-happy skill suite (kicad, datasheets, spice, …), wired in as Claude Code skills via ~/.claude/skills/{kicad,datasheets,spice} -> kicad-happy/skills/* symlinks. The toolkit clones (kicad-happy/, kicad-claude-toolkit/, MCP-KiCad/) are gitignored — re-clone if missing.
  2. Port the Raspberry Pi CM5IO reference into a custom CM5 carrier board — the real test bed (PTP/timing instrumentation node: 15V→5V front end, GbE magjack, M.2 E-key Wi-Fi (AW7915/MT7915), 2×USB-A, USB-C programming, RTC, GPS-PPS distribution).

Method is diff-from-reference: refs/ holds the untouched CM5IO reference; carrier/ is the working copy. Every edit is verified against analysis/baseline/cm5io.json.

Environment / tooling state (macOS, Homebrew)

  • kicad-cli (/opt/homebrew/bin/kicad-cli) — ERC + netlist export
  • poppler (pdftotext) — datasheet page selection
  • ngspice spice skill; full verify loop validated on the reference (8 subcircuits pass)
  • Python ≥3.10 (Homebrew 3.14)

The verify loop (run after EVERY schematic edit)

SCH=carrier/CM5IO.kicad_sch ; KSCR=~/.claude/skills/kicad/scripts
kicad-cli sch erc --exit-code-violations --format json -o /tmp/erc.json "$SCH"   # hard gate
python3 $KSCR/analyze_schematic.py "$SCH" -o /tmp/head.json                       # structural
python3 $KSCR/diff_analysis.py analysis/baseline/cm5io.json /tmp/head.json --text # vs baseline
  • Track the ERC DELTA, not the absolute — the reference already has 182 pre-existing violations (mostly unconnected_wire_endpoint). A good edit introduces zero new types.
  • The analyzer diff is the semantic gate (names broken design intent); ERC is the hygiene gate.
  • For kept-block integrity, also diff kicad-cli sch export netlist before/after — it caught a mis-inventoried part (see R8 below).

Datasheet extraction (done)

CM5 module extracted to datasheets/extracted/SC1466_43a9ec.json (200 pins, score 8.7/10, queryable via ~/.claude/skills/datasheets/scripts/datasheet_features.py). Drove the NC pin lists for every strip (e.g. HDMI Module1 pins = all pins whose datasheet name starts HDMI).

The strip tool: tools/retire_block.py

General block-retirement for these s-expr sheets. Capabilities:

  • excise symbols by Reference; retire nets by name-glob (all label types: local / hierarchical / global)
  • anchor-aware wire removal (union-find): removes a wire only if its connected component reaches no kept anchor (kept non-power pin or kept label) AND is seeded by this edit — so shared nets survive and pre-existing dangles are untouched
  • place (no_connect) on exposed Module1 pins (pin coords via a validated transform abs=(px+rot(lx,ly).x, py-rot(lx,ly).y), rot0/no-mirror; refuses other orientations)
  • clean orphaned power flags, dangling NCs, orphaned labels
  • --sheet-ports NAME,... removes root-sheet hierarchical pins + connecting wires

Usage: python3 tools/retire_block.py <sheet> --symbols J7,U18 --nets 'SD_*' --nc-pins 57,61 [--sheet-ports ...] [--apply] (dry-run without --apply).

Known gap (next improvement): does NOT auto-prune orphaned power-label/wire stubs left after a connector is removed (e.g. leftover +3.3v/HDMI_5v labels). Those were cleaned manually — see git history.

Strip phase — DONE (microSD + HDMI + MIPI + PCIe-M2), ERC 182 → 133, 0 new violations, 0 regressions

Block Sheet(s) Command(s) (run with --apply)
microSD CM5_GPIO --symbols J7,U18,C5 --nets 'SD_*' --nc-pins 57,61,62,63,67,69,75
HDMI CM5_HighSpeed --symbols J22,J10 --nets 'HDMI0_*,HDMI1_*,HDMI_5v' --nc-pins 143,145,146,147,148,149,151,152,153,154,158,160,164,166,170,172,176,178,182,184,188,190,199,200
MIPI/camera CM5_HighSpeed, CM5_GPIO, CM5IO(root) (1) HighSpeed --symbols J5,J16 --nets 'DPHY0_*,DPHY1_*,SCL1,SDA1' --nc-pins 115,117,121,123,127,129,133,135,139,141,175,177,181,183,187,189,193,194,195,196 (2) GPIO --nc-pins 80,82,97,100 (3) root --sheet-ports SCL0,SDA0,CAM_GPIO0,CAM_GPIO1 (4) HighSpeed --symbols R8 --nets 'SCL0,SDA0' (5) manual: remove 3 orphaned +3.3v labels + 2 wire stubs
PCIe-M2 (M-key/NVMe) CM5_HighSpeed, CM5IO(root), .kicad_pro (1) HighSpeed --nets 'PCIE_*' --nc-pins 102,104,106,109,110,112,116,118,122,124 (2) root --drop-sheet PCIe-M2.kicad_sch (3) root --sheet-ports PCIE_CLK_P,PCIE_CLK_N,PCIE_TX_P,PCIE_TX_N,PCIE_nRST,PCIE_RX_P,PCIE_RX_N,PCIE_nCLKREQ,PCIE_PWR_EN,PCIE_nWAKE (strip dangling HighSpeed sheet-symbol pins) (4) git rm carrier/PCIe-M2.kicad_sch (5) manual: remove PCIe-M2 entry from .kicad_pro sheets array + its ERC exclusion. Design confirmed: 1× M.2 E-key (Wi-Fi) only, 0× M-key SSD — the single CM5 PCIe ×1 lane is reserved for the E-key; the freed PCIE_* lanes (now NC on Module1 unit 2) will be rewired to the fresh E-key sheet in the add phase.

retire_block.py gained --drop-sheet FILE: removes the whole (sheet …) block by Sheetfile + its sheet_instances path (run on the parent sheet). .kicad_pro sheets-array + ERC-exclusion cleanup is still manual (next tool gap).

Notes that bit us (don't re-learn the hard way):

  • MIPI lanes are labeled DPHY0_*/DPHY1_*, NOT MIPI*. The connectors J5/J16 are camera FFCs bundling DPHY + cross-sheet I2C0 (SCL0/SDA0) + CAM_GPIO0/1 — a 3-sheet hierarchical block.
  • R8 is a camera pull-up, not USB (the Explore-agent inventory was wrong). Confirmed via kicad-cli sch export netlist (R8.2 → J16.17). Always verify inventory against the netlist.
  • SCL0/SDA0 (I2C0) are camera-only 2-node nets (netlist-proven). NC'd to reserve I2C0 for a future external I2C RTC (CM5's own RTC is on-module via VBAT). If you add an external RTC, wire it here.

Next steps

  1. Drop the PCIe-M2 sheet entirely DONE (see strip table). Confirmed E-key-only (no M-key SSD).
  2. Re-baseline DONEanalysis/baseline/carrier_stripped.json is the add-phase diff anchor.
  3. M.2 E-key sheet DONE (carrier/M2_Ekey.kicad_sch, generated by tools/build_ekey.py). J_E = KiCad Connector:Bus_M.2_Socket_E, fp Connector_PCBEdge:M.2_2230-xx-E. PCIe link-0 wired to the 9 freed CM5 pins via global labels (PCIE_TX/RX/CLK ±, nRST, nCLKREQ, nWAKE — netlist-confirmed J_E↔Module1); +3V3_RF (4 pins) + 10µF∥2×0.1µF decoupling + temp PWR_FLAG; 11 GND; W_DISABLE1/2 10k pull-ups to +3V3_RF; ~41 NC (USB/BT, PCM/I2S, SDIO, UART, COEX, I2C, link-1). RX not coupled on carrier (card-side per M.2; BOM C_PCIE removed). Verify: ERC 133→133 (zero new); analyzer diff +7 added, 0 regressions. Connector-symbol pin types remodeled for cross-connector ERC: lines CM5 drives → passive; lines the card drives into CM5 inputs (RX pair + CLKREQ#/WAKE#) → output. Open items (review/next): (a) CM5 PCIE_PWR_EN (pin 106) still NC — wire to the +3V3_RF buck EN in the power sheet; (b) the temp PWR_FLAG on +3V3_RF must be REMOVED once the buck drives it (else dual-driver ERC); (c) optionally route WIFI_DISABLE# to a CM5 GPIO for SW radio control; (d) analyzer CG-AUD 5:1 sig/gnd on J_E is inherent to the M.2 connector (INFO, not fixable). New tool: tools/build_ekey.py (place connector + auto-NC + labels).
  4. Power sheet (carrier/Power.kicad_sch, generated by tools/build_power.py) — staged, sync + datasheet-built parts (user choice). Spec rating fix: the spec's buck candidates miss its own ≥20V-Vin rule (TPS54424=17V, TPS54560=async) — selecting synchronous ≥30V parts instead (LM61460-class 6A for +5V & +3V3_RF/4A; TPS54202 28V for +3V3_AUX), each verified vs datasheet before commit.
    • Stage 1 — input protection DONE. J_PWR(15V) → D_REV LM74700-Q1 ideal-diode + Q_REV N-FET (reverse-polarity/-current) → VIN_PROT; SMBJ18A TVS + 100µF/25V ∥ 10µF/50V bulk; C_VCAP charge-pump cap. Netlist-verified topology; ERC 133→133 (zero new); analyzer 0 regressions. Confirm vs LM74700-Q1 datasheet: C_VCAP value, EN-to-+15V_IN, FET pick (Vds≥30V, SOA/inrush) + optional gate R.
    • Stage 2 — U_BUCK5 15→+5v 5A DONE. LM61460-Q1 (6A, 42V absmax sync; custom symbol built from the datasheetcarrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1, fp Package_SO:Texas_HTSSOP-14-1EP…ThermalVias). VREF=1.0V → FB divider R_FBT5 40.2k / R_FBB5 10.0k = 5.02V (SPICE-confirmed vfb=1.0V). L5 4.7µH, Cout 2×47µF/16V, Cin 2×10µF/50V, Cboot 100n+Rboot 4.7Ω, RT 31.6k(~400kHz), BIAS→+5v, EN→VIN_PROT, PGOOD 100k→+5v. +5v kept HIERARCHICAL (pre-existing carrier net; global would collide same_local_global_label) — Power sheet exposes a +5v sheet pin, tied into the root +5v net; netlist-verified it drives Module1 + GPIO + USB. ERC 133→133 (zero new); analyzer +32 added 0 regressions; SPICE 12/13 pass, 1 benign warn (L5/Cboot mis-detected as LC filter). Confirm vs datasheet: RT freq, L sat ≥7A, EP=pad15, optional EN UVLO divider.
    • Stage 3 — U_BUCK33R 15→+3V3_RF 4A DONE. Second LM61460-Q1 (shared BOM with stage 2: L 4.7µH/7A ripple ~1.3A=33%, Cin 2×10µ/50V, Cout 2×47µ/16V, RT 31.6k ~420kHz, Cboot 100n+Rboot 4.7). FB divider R_FBT33 23.2k / R_FBB33 10.0k = 3.32V (SPICE: vfb 0.994V, inrush settles 3.32V). BIAS→+3V3_RF (datasheet: valid ≥3.1V, auto-falls back to VIN below). EN ← CM5 PCIE_PWR_EN (Module1 pin 106: NC swapped for a global label on CM5_HighSpeed) + R_EN33 100k pull-down (mirrors reference R14 — netlist-verified Module1.106 + U_BUCK33R.7 + R_EN33.1) so the Wi-Fi rail is OFF until the CM5 asserts it. E-key temp #FLG33RF PWR_FLAG removed (build_ekey); the buck's PWR_FLAG on L33.2 is now the sole +3V3_RF driver — netlist shows buck + all 4 J_E power pins + pull-ups + decoupling on one net. Verify: ERC 133→133 zero new; analyzer +49 added 0 regressions; SPICE 17 pass / 2 benign warn (the L/C_BOOT "LC filter" mis-detect, once per buck) / 0 fail. (Note: kicad-cli netlist export prints a pre-existing "annotation errors" warning — un-numbered generated refdeses like J_E/J_PWR; present at fd627f5 too, cosmetic.)
    • Stage 4 — U_BUCK33A 15→+3V3_AUX ~1A DONE (power sheet complete). TPS54202 (SOT-23-6, sync, fixed 500kHz, VFB=0.596V). Abs-max gate PASSED but thin: VIN abs-max 30V vs SMBJ18A clamp ≤29.2V at full rated 600W surge → 0.8V transient margin (operating max 28V is not the surge number). Custom symbol CM5IO:TPS54202 (the KiCad lib symbol extends TPS54302 — no own pins, can't embed standalone) + bare copy in carrier/CM5IO.kicad_sym. FB per TI's 3.3V table row: 100k/22.1k + Cff 56pF → 3.29V (SPICE: vfb 0.597V, inrush settles 3.315V). L 10µH/2.5A (ΔI~0.52A), Cout 2×22µF/16V, Cboot 100n, Cin 10µF/50V+100n. EN abs-max is only 7V → NO direct VIN tie (unlike LM61460): UVLO divider 866k/110k (solved from Ip=0.7µA/Ih=1.55µA, VEN 1.21/1.19V) → start 10.13V / stop 8.61V, EN ~1.9V at 15V in (~3.5V at 29V clamp — safe). FB+EN pins typed passive (divider-only nets). Verify: ERC 133→133 zero new; analyzer +65/0 zero regressions (warn/error set identical to stage 3); SPICE 23 pass / 2 benign warn / 0 fail / 0 skip. New tooling gap found: simulate_subcircuits.py lowercases refdeses into SPICE names — C_IN33A vs stage-3 C_IN33a collided ("device already exists") and silently SKIPPED the whole VIN_PROT decoupling group → renamed to C_IN33Aa. Rule: never create refdeses differing only by case.
  5. GPS + PPS distribution DONE (carrier/GPS_PPS.kicad_sch, generated by tools/build_gps.py). Per Pinout_BOM §6 (the authority — it CORRECTS the design doc): J_GPS 5-pin (1=PPS, 2=NMEA_RX←GPS TX, 3=NMEA_TX→GPS RX, 4=+3V3_AUX, 5=GND); GPS_PPS_RAW → TVS_PPS PESD3V3L1BA → U_PPS 74LVC1G17 (SOT-23-5, +3V3_AUX, pin 1 true-NC) → PPS_CLEAN → R_PPS 33Ω → SYNC_OUT + J_PPS1 (UWB) / J_PPS2 (scope), 2-pin taps. NMEA = UART2 on GPIO4/GPIO5 (correction C2; UART0/GPIO14-15 stays debug). Cross-sheet wiring: converted the 6 local labels (SYNC_OUT×2, GPIO4×2, GPIO5×2 on CM5_GPIO) to global labels — net names unchanged so no multi-label (LB-001) findings, ERC-neutral (133→133); GPS sheet uses the same global names, root sheet-symbol has NO pins. J_GPS pins 1/2 re-typed output (GPS module drives PPS + TX) so the analyzer's no_driver gate passes — same trick as the E-key socket. Verify: ERC 133→133 zero new; analyzer +73/0 0 regressions (only the pre-existing MPN-coverage counter moved 50→57 parts); netlist confirms all 6 cross-sheet nets (SYNC_OUT = Module1.18+J2.6+R_PPS.2); SPICE 24 pass / 2 benign warn / 0 fail / 0 skip.

Out-of-repo notes

Richer running notes live in this machine's Claude memory: ~/.claude/projects/-Users-noise-Documents-obsidian-rpiboard/memory/ (project-cm5-eda, port-progress, eda-tooling-gaps, tooling-installs-cm5). This doc is the portable subset.