2026-06-30 16:41:22 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""build_power.py — generate the CM5-carrier Power sheet, stage by stage.
|
|
|
|
|
|
|
|
|
|
Same connectivity model as build_ekey.py: every component pin gets a named label
|
|
|
|
|
(or a power:GND symbol) coincident with its connection point, so nets form by name
|
|
|
|
|
with no wire routing. Coords are grid-snapped to 1.27mm so labels bind to pins.
|
|
|
|
|
|
|
|
|
|
STAGE 1 (this file): input protection —
|
|
|
|
|
J_PWR(15V) -> D_REV(LM74700 ideal-diode + Q_REV N-FET) -> VIN_PROT, with input TVS
|
|
|
|
|
+ bulk. Reverse-polarity + reverse-current via the ideal-diode controller.
|
|
|
|
|
Later stages (bucks) append to PLACEMENTS and re-run.
|
|
|
|
|
|
|
|
|
|
Datasheet items to CONFIRM against LM74700-Q1 (values, not topology):
|
|
|
|
|
- C_VCAP value/reference (charge-pump reservoir; ~1uF typ)
|
|
|
|
|
- EN handling (tied to +15V_IN for always-on; EN abs-max covers 15V)
|
|
|
|
|
- Q_REV FET pick (Vds>=30V, low Rds(on), SOA for inrush) + optional gate resistor
|
|
|
|
|
- TVS standoff (SMBJ18A: 18V standoff / ~29V clamp < buck Vin rating)
|
|
|
|
|
"""
|
|
|
|
|
import re, sys, os, uuid as uuidlib
|
|
|
|
|
|
|
|
|
|
ROOT_UUID = "e63e39d7-6ac0-4ffd-8aa3-1841a4541b55"
|
|
|
|
|
PROJECT = "CM5IO"
|
|
|
|
|
KSS = "/Applications/KiCad/KiCad.app/Contents/SharedSupport/symbols"
|
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
CARRIER = os.path.join(HERE, "..", "carrier")
|
|
|
|
|
|
|
|
|
|
def U(label): return str(uuidlib.uuid5(uuidlib.NAMESPACE_DNS, "cm5carrier-power-" + label))
|
|
|
|
|
SHEET_SYMBOL_UUID = U("sheet-symbol")
|
|
|
|
|
SHEET_FILE_UUID = U("sheet-file")
|
|
|
|
|
|
|
|
|
|
def g(v): return round(round(v / 1.27) * 1.27, 2)
|
|
|
|
|
|
|
|
|
|
def paren_end(t, s):
|
|
|
|
|
d = 0
|
|
|
|
|
for i in range(s, len(t)):
|
|
|
|
|
if t[i] == '(': d += 1
|
|
|
|
|
elif t[i] == ')':
|
|
|
|
|
d -= 1
|
|
|
|
|
if d == 0: return i + 1
|
|
|
|
|
raise ValueError("unbalanced")
|
|
|
|
|
|
|
|
|
|
def get_symbol(text, name):
|
|
|
|
|
m = re.search(r'\(symbol\s+"' + re.escape(name) + r'"', text)
|
|
|
|
|
return text[m.start():paren_end(text, m.start())] if m else None
|
|
|
|
|
|
|
|
|
|
def lib_pins(seg):
|
|
|
|
|
out = {}
|
|
|
|
|
for pm in re.finditer(r'\(pin\b', seg):
|
|
|
|
|
s = pm.start(); ps = seg[s:paren_end(seg, s)]
|
|
|
|
|
at = re.search(r'\(at\s+([\d.-]+)\s+([\d.-]+)\s+([\d.-]+)\)', ps)
|
|
|
|
|
nu = re.search(r'\(number\s+"([^"]+)"', ps)
|
|
|
|
|
if at and nu:
|
|
|
|
|
out.setdefault(nu.group(1), (float(at.group(1)), float(at.group(2)), float(at.group(3))))
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
# ---- lib_symbol sources -----------------------------------------------------------
|
|
|
|
|
carrier_hs = open(os.path.join(CARRIER, "CM5_HighSpeed.kicad_sch")).read()
|
|
|
|
|
LIBS = {} # lib_id -> (embed_block, {pin:(lx,ly,ang)})
|
|
|
|
|
def add_from(lib_id, srctext, srcname=None):
|
|
|
|
|
srcname = srcname or lib_id.split(':')[-1]
|
|
|
|
|
blk = get_symbol(srctext, lib_id) or get_symbol(srctext, srcname)
|
|
|
|
|
if not blk: sys.exit("missing lib_symbol " + lib_id)
|
|
|
|
|
if ('"' + lib_id + '"') not in blk[:60]:
|
|
|
|
|
blk = blk.replace('(symbol "' + srcname + '"', '(symbol "' + lib_id + '"', 1)
|
|
|
|
|
LIBS[lib_id] = (blk, lib_pins(blk))
|
|
|
|
|
|
|
|
|
|
# reuse R/C/C_Polarized/GND verbatim from the carrier (guaranteed compatible)
|
|
|
|
|
for lid in ["Device:R", "Device:C", "Device:C_Polarized", "power:GND"]:
|
|
|
|
|
add_from(lid, carrier_hs)
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
add_from("Device:L", open(f"{KSS}/Device.kicad_sym").read(), "L")
|
|
|
|
|
|
|
|
|
|
def register_lm61460():
|
|
|
|
|
"""Hand-authored lib_symbol for LM61460-Q1 (14-pin HTSSOP-1EP + EP=15),
|
|
|
|
|
built from the datasheet pin table (SNVSB70F). VREF(FB)=1.0V, VIN abs-max 42V.
|
|
|
|
|
Pin types picked for cross-sheet ERC: VIN/PGND/AGND/BIAS/EP power_in, VCC power_out,
|
|
|
|
|
SW output, FB/EN input, RT/CBOOT/RBOOT passive, PGOOD open_collector."""
|
|
|
|
|
P = [('8','VIN1','power_in',-16.51,15.24,0), ('12','VIN2','power_in',-16.51,12.7,0),
|
|
|
|
|
('7','EN/SYNC','input',-16.51,5.08,0), ('1','BIAS','power_in',-16.51,0,0),
|
|
|
|
|
('6','RT','passive',-16.51,-7.62,0),
|
|
|
|
|
('10','SW','output',16.51,15.24,180), ('14','CBOOT','passive',16.51,12.7,180),
|
|
|
|
|
('13','RBOOT','passive',16.51,10.16,180), ('2','VCC','power_out',16.51,5.08,180),
|
|
|
|
|
('4','FB','passive',16.51,-2.54,180), ('5','PGOOD','open_collector',16.51,-7.62,180),
|
|
|
|
|
('9','PGND1','power_in',-7.62,-21.59,90), ('11','PGND2','power_in',-2.54,-21.59,90),
|
|
|
|
|
('3','AGND','power_in',2.54,-21.59,90), ('15','EP','power_in',7.62,-21.59,90)]
|
|
|
|
|
pt = ""
|
|
|
|
|
for num, name, ty, lx, ly, ang in P:
|
|
|
|
|
pt += (f'\t\t\t(pin {ty} line (at {lx} {ly} {ang}) (length 3.81)\n'
|
|
|
|
|
f'\t\t\t\t(name "{name}" (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
f'\t\t\t\t(number "{num}" (effects (font (size 1.27 1.27))))\n\t\t\t)\n')
|
|
|
|
|
blk = ('\t(symbol "CM5IO:LM61460-Q1"\n\t\t(pin_names (offset 1.016))\n'
|
|
|
|
|
'\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n'
|
|
|
|
|
'\t\t(property "Reference" "U" (at 0 22.86 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
'\t\t(property "Value" "LM61460-Q1" (at 0 -26.67 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
'\t\t(property "Footprint" "Package_SO:Texas_HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3.155x3.255mm_ThermalVias" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
'\t\t(property "Datasheet" "https://www.ti.com/lit/ds/symlink/lm61460-q1.pdf" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
'\t\t(symbol "LM61460-Q1_0_1"\n'
|
|
|
|
|
'\t\t\t(rectangle (start -12.7 20.32) (end 12.7 -20.32) (stroke (width 0.254) (type default)) (fill (type background)))\n\t\t)\n'
|
|
|
|
|
f'\t\t(symbol "LM61460-Q1_1_1"\n{pt}\t\t)\n\t)')
|
|
|
|
|
LIBS["CM5IO:LM61460-Q1"] = (blk, lib_pins(blk))
|
|
|
|
|
register_lm61460()
|
2026-07-02 07:03:31 -04:00
|
|
|
|
|
|
|
|
def register_tps54202():
|
|
|
|
|
"""Hand-authored lib_symbol for TPS54202 (SOT-23-6), from the datasheet pin table
|
|
|
|
|
(SLVSD26C): GND=1, SW=2, VIN=3, FB=4, EN=5, BOOT=6. VFB=0.596V, VIN abs-max 30V,
|
|
|
|
|
fixed 500kHz. The KiCad lib symbol extends TPS54302 (no own pins) so it can't be
|
|
|
|
|
embedded standalone. FB/EN typed passive: both are high-Z sense pins on
|
|
|
|
|
resistor-divider-only nets (else ERC/analyzer flag no_driver)."""
|
|
|
|
|
P = [('3','VIN','power_in',-13.97,5.08,0), ('5','EN','passive',-13.97,0,0),
|
|
|
|
|
('6','BOOT','passive',13.97,5.08,180), ('2','SW','output',13.97,2.54,180),
|
|
|
|
|
('4','FB','passive',13.97,-2.54,180), ('1','GND','power_in',0,-13.97,90)]
|
|
|
|
|
pt = ""
|
|
|
|
|
for num, name, ty, lx, ly, ang in P:
|
|
|
|
|
pt += (f'\t\t\t(pin {ty} line (at {lx} {ly} {ang}) (length 3.81)\n'
|
|
|
|
|
f'\t\t\t\t(name "{name}" (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
f'\t\t\t\t(number "{num}" (effects (font (size 1.27 1.27))))\n\t\t\t)\n')
|
|
|
|
|
blk = ('\t(symbol "CM5IO:TPS54202"\n\t\t(pin_names (offset 1.016))\n'
|
|
|
|
|
'\t\t(exclude_from_sim no)\n\t\t(in_bom yes)\n\t\t(on_board yes)\n'
|
|
|
|
|
'\t\t(property "Reference" "U" (at 0 12.7 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
'\t\t(property "Value" "TPS54202" (at 0 -16.51 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
'\t\t(property "Footprint" "Package_TO_SOT_SMD:SOT-23-6" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
'\t\t(property "Datasheet" "https://www.ti.com/lit/ds/symlink/tps54202.pdf" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
'\t\t(symbol "TPS54202_0_1"\n'
|
|
|
|
|
'\t\t\t(rectangle (start -10.16 10.16) (end 10.16 -10.16) (stroke (width 0.254) (type default)) (fill (type background)))\n\t\t)\n'
|
|
|
|
|
f'\t\t(symbol "TPS54202_1_1"\n{pt}\t\t)\n\t)')
|
|
|
|
|
LIBS["CM5IO:TPS54202"] = (blk, lib_pins(blk))
|
|
|
|
|
register_tps54202()
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
# +5v pre-exists in the carrier as a HIERARCHICAL net (local + hierarchical labels on the CM5
|
|
|
|
|
# sheets) -> expose it hierarchically here (a global label collides: same_local_global_label).
|
|
|
|
|
# +3V3_RF/+3V3_AUX are new nets introduced by this port -> global is fine.
|
2026-07-01 22:11:24 -04:00
|
|
|
GLOBAL_NETS = {"+3V3_RF", "+3V3_AUX", "PCIE_PWR_EN"}
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
HIER_NETS = {"+5v"}
|
2026-06-30 16:41:22 -04:00
|
|
|
# from KiCad libs (renamed to full lib_id)
|
|
|
|
|
add_from("power:PWR_FLAG", open(f"{KSS}/power.kicad_sym").read(), "PWR_FLAG")
|
|
|
|
|
add_from("Power_Management:LM74700", open(f"{KSS}/Power_Management.kicad_sym").read(), "LM74700")
|
|
|
|
|
add_from("Transistor_FET:Q_NMOS_GDS", open(f"{KSS}/Transistor_FET.kicad_sym").read(), "Q_NMOS_GDS")
|
|
|
|
|
add_from("Device:D_TVS", open(f"{KSS}/Device.kicad_sym").read(), "D_TVS")
|
|
|
|
|
add_from("Connector:Screw_Terminal_01x02", open(f"{KSS}/Connector.kicad_sym").read(), "Screw_Terminal_01x02")
|
|
|
|
|
|
|
|
|
|
# ---- placement engine -------------------------------------------------------------
|
|
|
|
|
body = []
|
|
|
|
|
def emit_label(net, x, y, ang=0):
|
|
|
|
|
body.append(f'\t(label "{net}"\n\t\t(at {x} {y} {ang})\n'
|
|
|
|
|
f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("l-"+net+f"-{x}-{y}")}")\n\t)\n')
|
|
|
|
|
def emit_glabel(net, x, y, ang=0):
|
|
|
|
|
body.append(f'\t(global_label "{net}"\n\t\t(shape input)\n\t\t(at {x} {y} {ang})\n'
|
|
|
|
|
f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("gl-"+net+f"-{x}-{y}")}")\n\t)\n')
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
def emit_hlabel(net, x, y, ang=0):
|
|
|
|
|
body.append(f'\t(hierarchical_label "{net}"\n\t\t(shape passive)\n\t\t(at {x} {y} {ang})\n'
|
|
|
|
|
f'\t\t(effects (font (size 1.27 1.27)) (justify left))\n\t\t(uuid "{U("hl-"+net+f"-{x}-{y}")}")\n\t)\n')
|
2026-06-30 16:41:22 -04:00
|
|
|
def emit_gnd(x, y, tag):
|
|
|
|
|
body.append(f'\t(symbol\n\t\t(lib_id "power:GND")\n\t\t(at {x} {y} 0)\n\t\t(unit 1)\n'
|
|
|
|
|
f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("gnd-"+tag)}")\n'
|
|
|
|
|
f'\t\t(property "Reference" "#PWR{tag}" (at {x} {y+3} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(property "Value" "GND" (at {x} {y+2} 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
f'\t\t(property "Footprint" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(pin "1" (uuid "{U("gp-"+tag)}"))\n'
|
|
|
|
|
f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "#PWR{tag}") (unit 1))))\n\t)\n')
|
|
|
|
|
def emit_flag(x, y, tag):
|
|
|
|
|
body.append(f'\t(symbol\n\t\t(lib_id "power:PWR_FLAG")\n\t\t(at {x} {y} 0)\n\t\t(unit 1)\n'
|
|
|
|
|
f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("flg-"+tag)}")\n'
|
|
|
|
|
f'\t\t(property "Reference" "#FLG{tag}" (at {x} {y-3} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(property "Value" "PWR_FLAG" (at {x} {y-2} 0) (effects (font (size 1.27 1.27))))\n'
|
|
|
|
|
f'\t\t(property "Footprint" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(pin "1" (uuid "{U("fp-"+tag)}"))\n'
|
|
|
|
|
f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "#FLG{tag}") (unit 1))))\n\t)\n')
|
|
|
|
|
|
|
|
|
|
def place(lib_id, ref, value, footprint, ox, oy, pin_nets):
|
|
|
|
|
"""Place a symbol at grid-snapped (ox,oy); attach each pin's net by name.
|
|
|
|
|
pin_nets: {pinnum: 'NET' | ('gnd',) }. GND pins use a power:GND symbol."""
|
|
|
|
|
ox, oy = g(ox), g(oy)
|
|
|
|
|
_, pins = LIBS[lib_id]
|
|
|
|
|
extra = ('\t\t(property "Description" "" (at %g %g 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' % (ox, oy))
|
|
|
|
|
sym = (f'\t(symbol\n\t\t(lib_id "{lib_id}")\n\t\t(at {ox} {oy} 0)\n\t\t(unit 1)\n'
|
|
|
|
|
f'\t\t(exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)\n\t\t(uuid "{U("sym-"+ref)}")\n'
|
|
|
|
|
f'\t\t(property "Reference" "{ref}" (at {ox+2.54} {oy-1} 0) (effects (font (size 1.27 1.27)) (justify left)))\n'
|
|
|
|
|
f'\t\t(property "Value" "{value}" (at {ox+2.54} {oy+1} 0) (effects (font (size 1.27 1.27)) (justify left)))\n'
|
|
|
|
|
f'\t\t(property "Footprint" "{footprint}" (at {ox} {oy} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'
|
|
|
|
|
f'\t\t(property "Datasheet" "" (at {ox} {oy} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n' + extra)
|
|
|
|
|
for num in sorted(pins, key=lambda n: int(re.sub(r'\D', '', n) or 0)):
|
|
|
|
|
sym += f'\t\t(pin "{num}" (uuid "{U("p-"+ref+"-"+num)}"))\n'
|
|
|
|
|
sym += f'\t\t(instances (project "{PROJECT}" (path "/{ROOT_UUID}/{SHEET_SYMBOL_UUID}" (reference "{ref}") (unit 1))))\n\t)\n'
|
|
|
|
|
body.append(sym)
|
|
|
|
|
for num, net in pin_nets.items():
|
|
|
|
|
lx, ly, _ = pins[num]; px, py = round(ox + lx, 2), round(oy - ly, 2)
|
|
|
|
|
if net == ('gnd',): emit_gnd(px, py, ref + num)
|
|
|
|
|
elif isinstance(net, tuple) and net[0] == 'global': emit_glabel(net[1], px, py)
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
elif net in GLOBAL_NETS: emit_glabel(net, px, py)
|
|
|
|
|
elif net in HIER_NETS: emit_hlabel(net, px, py)
|
2026-06-30 16:41:22 -04:00
|
|
|
else: emit_label(net, px, py)
|
|
|
|
|
return ox, oy
|
|
|
|
|
|
|
|
|
|
# ---- STAGE 1: input protection ----------------------------------------------------
|
|
|
|
|
GND = ('gnd',)
|
|
|
|
|
place("Connector:Screw_Terminal_01x02", "J_PWR", "15V_DC_IN", "TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-2_1x02_P5.00mm_Horizontal",
|
|
|
|
|
80, 80, {'1': '+15V_IN', '2': GND})
|
|
|
|
|
drev = place("Power_Management:LM74700", "D_REV", "LM74700-Q1", "Package_TO_SOT_SMD:SOT-23-6",
|
|
|
|
|
120, 80, {'6': '+15V_IN', '3': '+15V_IN', '4': 'VIN_PROT', '5': 'GATE_REV', '1': 'VCAP_REV', '2': GND})
|
|
|
|
|
place("Transistor_FET:Q_NMOS_GDS", "Q_REV", "CSD18540 (Vds>=30V)", "Package_TO_SOT_SMD:TO-263-2",
|
|
|
|
|
140, 90, {'1': 'GATE_REV', '2': 'VIN_PROT', '3': '+15V_IN'})
|
|
|
|
|
place("Device:C", "C_VCAP", "1u", "Capacitor_SMD:C_0402_1005Metric", 120, 100, {'1': 'VCAP_REV', '2': GND})
|
|
|
|
|
tvs = place("Device:D_TVS", "TVS_IN", "SMBJ18A", "Diode_SMD:D_SMB", 160, 80, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C_Polarized", "C_BULK1", "100u/25V", "Capacitor_SMD:CP_Elec_8x10", 170, 80, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_BULK2", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 180, 80, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
# PWR_FLAGs co-located exactly on real pins so they bind: +15V_IN @ D_REV.6 (ANODE),
|
|
|
|
|
# VIN_PROT @ TVS.1 (a power_in driver for VIN_PROT, needed once bucks consume it).
|
|
|
|
|
def pin_abs(origin, lib_id, num):
|
|
|
|
|
lx, ly, _ = LIBS[lib_id][1][num]
|
|
|
|
|
return round(origin[0] + lx, 2), round(origin[1] - ly, 2)
|
|
|
|
|
fx, fy = pin_abs(drev, "Power_Management:LM74700", "6"); emit_flag(fx, fy, "15IN")
|
|
|
|
|
vx, vy = pin_abs(tvs, "Device:D_TVS", "1"); emit_flag(vx, vy, "VINP")
|
|
|
|
|
|
Power stage 2: LM61460-Q1 15V->+5v 5A sync buck (SPICE-verified 5.02V)
Second power stage. U_BUCK5 = LM61460-Q1 (6A, 42V abs-max synchronous).
No KiCad symbol existed, so built one from the TI datasheet (SNVSB70F:
14-pin HTSSOP-1EP + EP, VREF=1.0V) and added it to the project library
carrier/CM5IO.kicad_sym as CM5IO:LM61460-Q1.
- FB divider R_FBT5 40.2k / R_FBB5 10.0k -> 5.02V (SPICE-confirmed
vfb=1.0V=VREF at 5.02V); L5 4.7uH, Cout 2x47u/16V, Cin 2x10u/50V,
Cboot 100n+Rboot, RT 31.6k (~400kHz), BIAS->+5v, EN->VIN_PROT
- +5v pre-exists in the carrier as a hierarchical net -> exposed via a
hierarchical label + a +5v sheet pin on the Power symbol, tied into the
root +5v net (a global label would trip same_local_global_label).
Netlist confirms the buck now sources Module1 + GPIO headers + USB.
- FB pin typed passive (high-Z sense) to clear an analyzer no_driver
- build_power.py: register_lm61460(), Device:L, HIER_NETS/emit_hlabel
Verify: ERC 133->133 (zero new); analyzer +32 added, 0 regressions;
SPICE 12/13 pass, 0 fail (1 benign warn: L5/Cboot mis-grouped as LC).
Confirm vs datasheet: RT freq, L saturation >=7A, EP=pad15, EN UVLO.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:31:26 -04:00
|
|
|
# ---- STAGE 2: U_BUCK5 15V -> +5v 5A (LM61460-Q1, sync, 42V absmax) --------------
|
|
|
|
|
# FB divider: Vout = VREF*(1+Rfbt/Rfbb), VREF=1.0V -> Rfbt/Rfbb=4 -> 40.2k/10.0k = 5.02V.
|
|
|
|
|
# fsw ~= 400kHz (RT 31.6k); L=4.7uH; Cout 2x47u/16V; Cin 2x10u/50V; Cboot 100n; BIAS->+5v.
|
|
|
|
|
place("CM5IO:LM61460-Q1", "U_BUCK5", "LM61460-Q1", "Package_SO:Texas_HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3.155x3.255mm_ThermalVias",
|
|
|
|
|
110, 150, {'8': 'VIN_PROT', '12': 'VIN_PROT', '7': 'VIN_PROT', '1': '+5v', '2': 'VCC5',
|
|
|
|
|
'6': 'RT5', '5': 'PG5', '4': 'FB5', '10': 'SW5', '14': 'BOOT5', '13': 'RBOOT5',
|
|
|
|
|
'9': GND, '11': GND, '3': GND, '15': GND})
|
|
|
|
|
l5 = place("Device:L", "L5", "4.7uH/7A", "Inductor_SMD:L_Bourns_SRP1245A", 150, 138, {'1': 'SW5', '2': '+5v'})
|
|
|
|
|
place("Device:C", "C_BOOT5", "100n", "Capacitor_SMD:C_0402_1005Metric", 140, 132, {'1': 'SW5', '2': 'BOOT5'})
|
|
|
|
|
place("Device:R", "R_BOOT5", "4.7", "Resistor_SMD:R_0402_1005Metric", 145, 128, {'1': 'RBOOT5', '2': 'BOOT5'})
|
|
|
|
|
place("Device:R", "R_RT5", "31.6k", "Resistor_SMD:R_0402_1005Metric", 90, 158, {'1': 'RT5', '2': GND})
|
|
|
|
|
place("Device:R", "R_PG5", "100k", "Resistor_SMD:R_0402_1005Metric", 135, 143, {'1': 'PG5', '2': '+5v'})
|
|
|
|
|
place("Device:R", "R_FBT5", "40.2k", "Resistor_SMD:R_0402_1005Metric", 165, 150, {'1': '+5v', '2': 'FB5'})
|
|
|
|
|
place("Device:R", "R_FBB5", "10.0k", "Resistor_SMD:R_0402_1005Metric", 165, 158, {'1': 'FB5', '2': GND})
|
|
|
|
|
place("Device:C", "C_VCC5", "1u", "Capacitor_SMD:C_0402_1005Metric", 100, 158, {'1': 'VCC5', '2': GND})
|
|
|
|
|
place("Device:C", "C_IN5a", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 95, 145, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_IN5b", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 100, 145, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT5a", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 175, 138, {'1': '+5v', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT5b", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 180, 138, {'1': '+5v', '2': GND})
|
|
|
|
|
# +5v is now sourced by this buck -> PWR_FLAG co-located on L5 pin2 (+5v)
|
|
|
|
|
p5x, p5y = pin_abs(l5, "Device:L", "2"); emit_flag(p5x, p5y, "P5V")
|
|
|
|
|
|
2026-07-01 22:11:24 -04:00
|
|
|
# ---- STAGE 3: U_BUCK33R 15V -> +3V3_RF 4A (LM61460-Q1, sync) --------------------
|
|
|
|
|
# FB divider: VREF=1.0V -> Rfbt/Rfbb=2.32 -> 23.2k/10.0k = 3.32V.
|
|
|
|
|
# Same support set as stage 2 (RT 31.6k ~420kHz, L 4.7uH/7A: ripple ~1.3A = 33% of 4A).
|
|
|
|
|
# BIAS -> +3V3_RF (datasheet: BIAS>=3.1V uses output LDO path; falls back to VIN below).
|
|
|
|
|
# EN <- PCIE_PWR_EN (CM5 Module1 pin 106, global net) + 100k pull-down (= reference R14)
|
|
|
|
|
# so the Wi-Fi rail stays OFF until the CM5 asserts it.
|
|
|
|
|
place("CM5IO:LM61460-Q1", "U_BUCK33R", "LM61460-Q1", "Package_SO:Texas_HTSSOP-14-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask3.155x3.255mm_ThermalVias",
|
|
|
|
|
230, 150, {'8': 'VIN_PROT', '12': 'VIN_PROT', '7': 'PCIE_PWR_EN', '1': '+3V3_RF', '2': 'VCC33',
|
|
|
|
|
'6': 'RT33', '5': 'PG33', '4': 'FB33', '10': 'SW33', '14': 'BOOT33', '13': 'RBOOT33',
|
|
|
|
|
'9': GND, '11': GND, '3': GND, '15': GND})
|
|
|
|
|
l33 = place("Device:L", "L33", "4.7uH/7A", "Inductor_SMD:L_Bourns_SRP1245A", 270, 138, {'1': 'SW33', '2': '+3V3_RF'})
|
|
|
|
|
place("Device:C", "C_BOOT33", "100n", "Capacitor_SMD:C_0402_1005Metric", 260, 132, {'1': 'SW33', '2': 'BOOT33'})
|
|
|
|
|
place("Device:R", "R_BOOT33", "4.7", "Resistor_SMD:R_0402_1005Metric", 265, 128, {'1': 'RBOOT33', '2': 'BOOT33'})
|
|
|
|
|
place("Device:R", "R_RT33", "31.6k", "Resistor_SMD:R_0402_1005Metric", 210, 158, {'1': 'RT33', '2': GND})
|
|
|
|
|
place("Device:R", "R_PG33", "100k", "Resistor_SMD:R_0402_1005Metric", 255, 143, {'1': 'PG33', '2': '+3V3_RF'})
|
|
|
|
|
place("Device:R", "R_FBT33", "23.2k", "Resistor_SMD:R_0402_1005Metric", 285, 150, {'1': '+3V3_RF', '2': 'FB33'})
|
|
|
|
|
place("Device:R", "R_FBB33", "10.0k", "Resistor_SMD:R_0402_1005Metric", 285, 158, {'1': 'FB33', '2': GND})
|
|
|
|
|
place("Device:R", "R_EN33", "100k", "Resistor_SMD:R_0402_1005Metric", 200, 165, {'1': 'PCIE_PWR_EN', '2': GND})
|
|
|
|
|
place("Device:C", "C_VCC33", "1u", "Capacitor_SMD:C_0402_1005Metric", 220, 158, {'1': 'VCC33', '2': GND})
|
|
|
|
|
place("Device:C", "C_IN33a", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 215, 145, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_IN33b", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 220, 145, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT33a", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 240, 165, {'1': '+3V3_RF', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT33b", "47u/16V", "Capacitor_SMD:C_1210_3225Metric", 245, 165, {'1': '+3V3_RF', '2': GND})
|
|
|
|
|
# +3V3_RF is now sourced by this buck -> PWR_FLAG on L33 pin2 (replaces the E-key temp flag)
|
|
|
|
|
p33x, p33y = pin_abs(l33, "Device:L", "2"); emit_flag(p33x, p33y, "P33R")
|
|
|
|
|
|
2026-07-02 07:03:31 -04:00
|
|
|
# ---- STAGE 4: U_BUCK33A 15V -> +3V3_AUX ~1A (TPS54202, sync, 500kHz fixed) ------
|
|
|
|
|
# Quiet rail: GPS + PPS Schmitt buffer + IO pull-ups + RTC domain (design doc ~1A).
|
|
|
|
|
# Abs-max gate PASSED (thin): VIN abs-max 30V vs SMBJ18A clamp <=29.2V at full rated
|
|
|
|
|
# 600W surge (0.8V margin, transient-only; 28V operating max is not the surge number).
|
|
|
|
|
# FB per TI's 3.3V table row: R2=100k/R3=22.1k + Cff 56pF -> 0.596*(1+100/22.1)=3.29V.
|
|
|
|
|
# L=10uH (dI~0.52A, Ipk~1.26A), Cout 2x22uF. EN abs-max is 7V -> NO direct VIN tie;
|
|
|
|
|
# UVLO divider 866k/110k (Ip=0.7uA/Ih=1.55uA, Ven 1.21/1.19V) -> start 10.1V/stop 8.6V,
|
|
|
|
|
# EN sits ~1.9V at 15V in (~3.5V at 29V surge clamp, still < 7V abs-max).
|
|
|
|
|
place("CM5IO:TPS54202", "U_BUCK33A", "TPS54202", "Package_TO_SOT_SMD:SOT-23-6",
|
|
|
|
|
230, 95, {'3': 'VIN_PROT', '5': 'EN33A', '6': 'BOOT33A', '2': 'SW33A',
|
|
|
|
|
'4': 'FB33A', '1': GND})
|
|
|
|
|
l33a = place("Device:L", "L33A", "10uH/2.5A", "Inductor_SMD:L_1210_3225Metric", 260, 85, {'1': 'SW33A', '2': '+3V3_AUX'})
|
|
|
|
|
place("Device:C", "C_BOOT33A", "100n", "Capacitor_SMD:C_0402_1005Metric", 250, 78, {'1': 'SW33A', '2': 'BOOT33A'})
|
|
|
|
|
place("Device:R", "R_UVT33A", "866k", "Resistor_SMD:R_0402_1005Metric", 205, 85, {'1': 'VIN_PROT', '2': 'EN33A'})
|
|
|
|
|
place("Device:R", "R_UVB33A", "110k", "Resistor_SMD:R_0402_1005Metric", 205, 95, {'1': 'EN33A', '2': GND})
|
|
|
|
|
place("Device:R", "R_FBT33A", "100k", "Resistor_SMD:R_0402_1005Metric", 280, 95, {'1': '+3V3_AUX', '2': 'FB33A'})
|
|
|
|
|
place("Device:C", "C_FF33A", "56p", "Capacitor_SMD:C_0402_1005Metric", 288, 95, {'1': '+3V3_AUX', '2': 'FB33A'})
|
|
|
|
|
place("Device:R", "R_FBB33A", "22.1k", "Resistor_SMD:R_0402_1005Metric", 280, 105, {'1': 'FB33A', '2': GND})
|
|
|
|
|
# NB refdes must not differ from stage-3's C_IN33a by case alone — SPICE names are
|
|
|
|
|
# case-insensitive and the simulate_subcircuits testbench collides ("device already exists").
|
|
|
|
|
place("Device:C", "C_IN33Aa", "10u/50V", "Capacitor_SMD:C_1210_3225Metric", 215, 105, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_INHF33A", "100n", "Capacitor_SMD:C_0402_1005Metric", 220, 105, {'1': 'VIN_PROT', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT33Aa", "22u/16V", "Capacitor_SMD:C_1210_3225Metric", 265, 105, {'1': '+3V3_AUX', '2': GND})
|
|
|
|
|
place("Device:C", "C_OUT33Ab", "22u/16V", "Capacitor_SMD:C_1210_3225Metric", 270, 105, {'1': '+3V3_AUX', '2': GND})
|
|
|
|
|
# +3V3_AUX sourced here -> PWR_FLAG on L33A pin2
|
|
|
|
|
p33ax, p33ay = pin_abs(l33a, "Device:L", "2"); emit_flag(p33ax, p33ay, "P33A")
|
|
|
|
|
|
2026-06-30 16:41:22 -04:00
|
|
|
# ---- file scaffold ----------------------------------------------------------------
|
|
|
|
|
libsyms = "\n".join(blk for blk, _ in LIBS.values())
|
|
|
|
|
sheet = ('(kicad_sch\n\t(version 20231120)\n\t(generator "eeschema")\n\t(generator_version "8.0")\n'
|
|
|
|
|
f'\t(uuid "{SHEET_FILE_UUID}")\n\t(paper "A4")\n'
|
|
|
|
|
'\t(title_block\n\t\t(title "CM5 Carrier - Power")\n\t\t(rev "A")\n'
|
|
|
|
|
'\t\t(comment 1 "15V inlet -> ideal-diode -> VIN_PROT -> 3 bucks (+5V, +3V3_RF, +3V3_AUX)")\n\t)\n'
|
|
|
|
|
'\t(lib_symbols\n' + libsyms + '\n\t)\n' + "".join(body) +
|
|
|
|
|
'\t(sheet_instances\n\t\t(path "/"\n\t\t\t(page "5")\n\t\t)\n\t)\n)\n')
|
|
|
|
|
assert sheet.count('(') == sheet.count(')'), f"UNBALANCED {sheet.count('(')} vs {sheet.count(')')}"
|
|
|
|
|
print("sheet-symbol uuid:", SHEET_SYMBOL_UUID)
|
|
|
|
|
print("parens balanced:", sheet.count('('))
|
|
|
|
|
out = os.path.join(CARRIER, "Power.kicad_sch")
|
|
|
|
|
if '--apply' in sys.argv: open(out, 'w').write(sheet); print("WROTE " + out)
|
|
|
|
|
else: print("DRY RUN (--apply to write)")
|