Files
pcb/scripts/design_data.py
T
onur 95609a6b6a Complete the routing: 0 DRC violations, 0 unconnected
The board now routes end to end. Every net carrying two or more pads is a
single connected copper group (216 nets, 1175 pads); kicad-cli reports 0 DRC
violations and 0 unconnected items with zones refilled.

Freerouting was never the blocker. The 14 remaining opens were three separate
defects:

1. Obstacles in route_local_power.py modelled every pad as its axis-aligned
   bounding box. A 0.8-mm BGA dogbone site sits diagonally between four lands:
   true clearance 0.1407 mm, but the box test measured 0.4 mm from the box edge
   and rejected it, so no via could be placed anywhere in the Ti375 escape
   field. Replaced with an exact rounded-rectangle model, validated against
   KiCad's own SHAPE::Collide to 0.00 um over 14850 samples. Same 0 DRC and one
   fewer unconnected at the route_local_power stage.

2. CLOCK's 0.15-mm clearance cannot physically escape the BGA: two adjacent
   0.45-mm dogbone vias on 0.8-mm pitch leave a 0.35-mm channel and a 0.10-mm
   trace needs 0.40 mm. HARD_TCK and MAX_SCLK could never leave the package.
   New rule "Ti375 escape field copper clearance" relaxes clearance to the
   0.10 mm this field already uses, only where both objects are inside U1's
   courtyard. The clock trunk keeps 0.15 mm outside it.

3. RF_NO_DIGITAL was a rectangle reaching 7 mm past the RF line, covering the
   MAX2771's whole digital pin field and forcing every U21 output onto F.Cu
   alone. Narrowed to the corridor generate_pcb.py already reserves as
   TEMP_RF_LINE (y <= 43.7), clearing RF copper by 1.1 mm. The RF return path
   is guaranteed by the unrouted L2/L7 ground planes, not by this area.

scripts/close_remaining.py replaces the ad-hoc residual scripts: a multi-layer
maze over (x, y, layer) at 0.1 mm on all five routable layers, seeded from all
copper of both groups, exact clearances, 0.10-mm necking inside the escape
field, and reversible rip-up that rolls back entirely if a displaced net cannot
be put back. ~150 s for the whole board.

Also removes the regenerable intermediate boards, one-off session logs and the
superseded residual scripts, and tracks board_routed.* as the deliverable.

The 402 schematic-parity warnings are pre-existing and unchanged: 199
auto-named unconnected pins, 199 footprint library-nickname mismatches, and 4
mounting holes with no symbol. None affects connectivity or fabrication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012KaTmCCxCEW4awsmXRNT1d
2026-09-01 11:58:41 +03:00

833 lines
50 KiB
Python

#!/usr/bin/env python3
"""Single-source component and connectivity data for the GNSS/IMU FPGA PCB.
Generated KiCad, BOM, pinout, and assembly data import this module. Ti375 ball
data comes from the Efinity 2025.2 C529 resource DB, never a hand transcription.
"""
from __future__ import annotations
import csv
from dataclasses import dataclass, field
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
@dataclass
class Pin:
number: str
name: str
net: str | None
etype: str = "passive"
@dataclass
class Component:
ref: str
value: str
mpn: str
manufacturer: str
footprint: str
pins: list[Pin]
description: str = ""
assembly: str = "SMT"
dnp: bool = False
fields: dict[str, str] = field(default_factory=dict)
def p(number: str | int, name: str, net: str | None, etype: str = "passive") -> Pin:
return Pin(str(number), name, net, etype)
components: list[Component] = []
_refs = {"R": 0, "C": 0, "L": 0, "FB": 0, "TP": 0}
def add(component: Component) -> Component:
assert not any(c.ref == component.ref for c in components), component.ref
components.append(component)
return component
def two_pin(prefix: str, value: str, mpn: str, manufacturer: str,
footprint: str, net1: str, net2: str, description: str,
*, dnp: bool = False, ref: str | None = None) -> Component:
if ref is None:
_refs[prefix] += 1
ref = f"{prefix}{_refs[prefix]}"
return add(Component(ref, value, mpn, manufacturer, footprint,
[p(1, "1", net1), p(2, "2", net2)],
description, dnp=dnp))
def r(value: str, net1: str, net2: str, *, mpn: str = "",
description: str = "Resistor", dnp: bool = False) -> Component:
values = {
"0R": "RC0402JR-070RL", "22R": "RC0402FR-0722RL",
"27R": "RC0402FR-0727RL", "33R": "RC0402FR-0733RL",
"825R": "RC0402FR-07825RL", "1K": "RC0402FR-071KL", "1.24K": "RC0402FR-071K24L",
"2.1K": "RC0402FR-072K1L", "2.2K": "RC0402FR-072K2L",
"4.7K": "RC0402FR-074K7L", "10K": "RC0402FR-0710KL",
"12K": "RC0402FR-0712KL", "15K": "RC0402FR-0715KL",
"22.1K": "RC0402FR-0722K1L", "100K": "RC0402FR-07100KL",
"133K": "RC0402FR-07133KL", "205K": "RC0402FR-07205KL",
"232K": "RC0402FR-07232KL", "383K": "RC0402FR-07383KL",
"442K": "RC0402FR-07442KL", "900K": "RC0402FR-07909KL",
"1M": "RC0402FR-071ML",
}
return two_pin("R", value, mpn or values.get(value, ""), "Yageo",
"Resistor_SMD:R_0402_1005Metric", net1, net2, description,
dnp=dnp)
def c(value: str, net1: str, net2: str, *, mpn: str = "",
footprint: str = "Capacitor_SMD:C_0402_1005Metric",
description: str = "MLCC", dnp: bool = False) -> Component:
values = {
"10pF": "GRM1555C1H100JA01D", "15pF": "GRM1555C1H150JA01D",
"27pF": "600L270JT200T", "750pF": "GRM1555C1H751JA01D",
"1.8nF": "GRM155R71H182KA01D", "4.7nF": "GRM155R71H472KA01D",
"10nF": "GRM155R71H103KA88D", "100nF": "GRM155R71A104KA01D",
"470nF": "GCM155C71A474KE36D", "1uF": "GRM155R60J105KE19D",
"10uF": "GRM188R60J106ME47D", "22uF": "GRM21BZ70J226ME44L",
"47uF": "GRM32ER70J476ME19L", "100uF": "GRT32EC70J107ME13L",
}
return two_pin("C", value, mpn or values.get(value, ""), "Murata",
footprint, net1, net2, description, dnp=dnp)
def tp(net: str, label: str) -> Component:
_refs["TP"] += 1
return add(Component(f"TP{_refs['TP']}", label, "", "",
"TestPoint:TestPoint_Pad_D1.0mm",
[p(1, label, net)], "Local labelled SMT bring-up test pad", "SMT"))
def add_ic(ref: str, value: str, mpn: str, manufacturer: str, footprint: str,
pins: list[Pin], description: str) -> Component:
return add(Component(ref, value, mpn, manufacturer, footprint, pins, description))
# Ti375 C529: represent every package ball exactly once.
pinout = {row["ball"]: row for row in csv.DictReader(
(ROOT / "docs" / "ti375_pinout.csv").open(newline=""))}
package = list(csv.DictReader((ROOT / "docs" / "ti375_c529_package.csv").open(newline="")))
def fpga_net(row: dict[str, str]) -> str | None:
ball = row["ball"]
if ball in pinout:
return pinout[ball]["signal"]
name = row["official_pad_name"]
if name in ("GND", "VQPS"):
return "GND"
if name == "VCC" or name.startswith("VCCA_") or name == "VDD_SOC":
return "VCC0V95"
if name == "VCCAUX":
return "VCC1V8"
vccio = {
"VCCIO4C": "VCC1V8", "VCCIO4A_4B": "VCC1V8", "VCCIO4D": "VCC1V8",
"VCCIO2A": "VCC1V8", "VCCIO2B": "VCC1V8", "VCCIO2C": "VCC1V8",
"VCCIO2D": "VCC1V8", "VCCIO2E": "VCC1V8",
"VCCIO33_BR0": "VCC3V0_DIG", "VCCIO33_TR0": "VCC3V0_DIG",
"VCCIO33_TR1": "VCC3V3", "VCCIO33_TR2": "VCC3V3",
"VCCIO33_BR3_BR4": "VCC3V3", "VCCIO33_TL1_TL5": "VCC3V3",
"VCCIO33_BL2_BL3": "VCC3V3",
}
if name in vccio:
return vccio[name]
if name.startswith("REF_RES_"):
return name
# JTAG_VCCIO_SEL floats for 3.3-V BR3/BR4. Official NC, unused DDR
# signals, and unused DDR rails remain open per Efinix's unused table.
return None
fpga_pins = []
for row in package:
etype = {"power": "power_in", "input": "input", "output": "output"}.get(
row["direction"], "bidirectional")
fpga_pins.append(p(row["ball"], row["official_pad_name"], fpga_net(row), etype))
add_ic("U1", "Ti375C529C3", "Ti375C529C3", "Efinix",
"Package_BGA:BGA-529_19x19mm_Layout23x23_P0.8mm", fpga_pins,
"Titanium Ti375 FPGA, commercial standard-power 0.95-V option, C529")
for name in ("REF_RES_4A", "REF_RES_4B", "REF_RES_4D", "REF_RES_2E", "REF_RES_2A"):
r("10K", name, "GND", description="Efinix mandatory 10-kohm 1% reference resistor")
# USB-C debug port (JTAG/UART only). This connector never sources SYS5: its
# VBUS pins only feed local presence-sensing dividers, so a host attached here
# can never be back-fed and dedicated USB power input J7 stays the only USB
# source of board power. See J7 below for the power port and its TUSB320.
add_ic("J1", "USB-C USB2 sink", "USB4105-GF-A-120", "GCT",
"Connector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal", [
p("A1", "GND", "GND"), p("A4", "VBUS", "DBG_VBUS_RAW"), p("A5", "CC1", "USB_CC1"),
p("A6", "D+", "USB_D_P"), p("A7", "D-", "USB_D_N"), p("A8", "SBU1", None),
p("A9", "VBUS", "DBG_VBUS_RAW"), p("A12", "GND", "GND"),
p("B1", "GND", "GND"), p("B4", "VBUS", "DBG_VBUS_RAW"), p("B5", "CC2", "USB_CC2"),
p("B6", "D+", "USB_D_P"), p("B7", "D-", "USB_D_N"), p("B8", "SBU2", None),
p("B9", "VBUS", "DBG_VBUS_RAW"), p("B12", "GND", "GND"), p("SH", "SHIELD", "USB_SHIELD")],
"USB 2.0 Type-C receptacle, UFP/sink, debug data only")
add_ic("U5", "TUSB320LAI", "TUSB320LAIRSW(R)", "Texas Instruments",
"Package_DFN_QFN:Texas_X2QFN-12_1.6x1.6mm_P0.4mm", [
p(1, "CC1", "USB_CC1", "bidirectional"), p(2, "CC2", "USB_CC2", "bidirectional"),
p(3, "PORT", "GND", "input"), p(4, "VBUS_DET", "USB_VBUS_DET", "input"),
p(5, "ADDR", None), p(6, "OUT3", None, "output"),
p(7, "OUT1", None, "output"), p(8, "OUT2", None, "output"),
p(9, "ID", None, "output"), p(10, "GND", "GND", "power_in"),
p(11, "EN_N", "GND", "input"), p(12, "VDD", "VCC3V3_AUX", "power_in")],
"USB Type-C port controller in UFP GPIO mode; integrated Rd on CC1/CC2; "
"current-advertisement outputs unused since this port never sources SYS5")
r("900K", "DBG_VBUS_RAW", "USB_VBUS_DET", description="TUSB320 VBUS_DET current limiter")
r("10K", "VCC3V3_AUX", "TUSB_OUT1", description="TUSB320 open-drain OUT1 pull-up")
r("10K", "VCC3V3_AUX", "TUSB_OUT2", description="TUSB320 open-drain OUT2 pull-up")
c("100nF", "VCC3V3_AUX", "GND", description="TUSB320 local bypass")
add_ic("U6", "TPD2EUSB30A", "TPD2EUSB30ADRTR", "Texas Instruments",
"Package_TO_SOT_SMD:Texas_DRT-3", [p(1, "IO1", "USB_D_P"), p(2, "GND", "GND"), p(3, "IO2", "USB_D_N")],
"Low-capacitance USB 2.0 ESD protection")
c("4.7nF", "USB_SHIELD", "GND", description="USB shield HF coupling")
r("1M", "USB_SHIELD", "GND", description="USB shield bleed")
r("0R", "USB_SHIELD", "GND", description="Optional direct shield bond", dnp=True)
add_ic("J3", "EXT_5V", "1935161", "Phoenix Contact",
"TerminalBlock_Phoenix:TerminalBlock_Phoenix_MKDS-1,5-2_1x02_P5.00mm_Horizontal", [
p(1, "EXT_5V", "EXT5_RAW", "power_in"), p(2, "GND", "GND", "power_in")],
"Clearly labelled external regulated 5-V input")
components[-1].assembly = "THT"
def add_efuse(ref: str, input_net: str, output_net: str, ilim: str) -> None:
add_ic(ref, "TPS259470A", "TPS259470ARPWR", "Texas Instruments",
"board:Texas_RPW0010A_QFN10", [
p(1, "EN/UVLO", f"{ref}_ENUV", "input"), p(2, "OVLO", f"{ref}_OV", "input"),
p(3, "AUXOFF", None, "open_collector"), p(4, "FLT", f"{ref}_FLT", "open_collector"),
p(5, "IN", input_net, "power_in"), p(6, "OUT", output_net, "power_out"),
p(7, "DVDT", f"{ref}_DVDT", "output"), p(8, "GND", "GND", "power_in"),
p(9, "ILM", f"{ref}_ILM", "output"), p(10, "ITIMER", None)],
"True-reverse-blocking eFuse; UV/OV/current/inrush protection")
r("232K", input_net, f"{ref}_ENUV", description="4.0-V nominal eFuse UV divider top")
r("100K", f"{ref}_ENUV", "GND", description="eFuse UV divider bottom")
r("383K", input_net, f"{ref}_OV", description="5.8-V nominal eFuse OV divider top")
r("100K", f"{ref}_OV", "GND", description="eFuse OV divider bottom")
r(ilim, f"{ref}_ILM", "GND", description="eFuse RILM=3334/ILIM")
c("4.7nF", f"{ref}_DVDT", "GND", description="eFuse output slew control")
c("1uF", input_net, "GND", description="eFuse local input bypass")
c("10uF", output_net, "GND", description="eFuse local output bypass")
tp(f"{ref}_FLT", f"{ref} FLT")
add_efuse("U7", "VBUS_RAW", "USB5_PROT", "1.24K") # 2.69 A typical, <3 A max.
add_efuse("U8", "EXT5_RAW", "EXT5_PROT", "825R") # 4.04 A typical.
add_ic("U9", "TPS2121", "TPS2121RUXR", "Texas Instruments", "board:Texas_RUX0012A_VQFN12", [
p(1, "OUT", "SYS5", "power_out"), p(2, "IN2", "EXT5_PROT", "power_in"), p(3, "CP2", "VCC3V3_AUX", "input"),
p(4, "OV2", "MUX_OV2", "input"), p(5, "OV1", "MUX_OV1", "input"), p(6, "PR1", "GND", "input"),
p(7, "IN1", "USB5_PROT", "power_in"), p(8, "OUT", "SYS5", "power_out"), p(9, "ST", "MUX_ST", "output"),
p(10, "ILM", "MUX_ILM", "output"), p(11, "SS", "MUX_SS", "output"), p(12, "GND", "GND", "power_in")],
"4.5-A power mux; external input wins near-equal valid 5-V inputs")
r("442K", "USB5_PROT", "MUX_OV1", description="TPS2121 IN1 5.75-V OV divider top")
r("100K", "MUX_OV1", "GND", description="TPS2121 IN1 OV divider bottom")
r("442K", "EXT5_PROT", "MUX_OV2", description="TPS2121 IN2 5.75-V OV divider top")
r("100K", "MUX_OV2", "GND", description="TPS2121 IN2 OV divider bottom")
r("22.1K", "MUX_ILM", "GND", description="TPS2121 4.5-A typical output limit")
c("10nF", "MUX_SS", "GND", description="TPS2121 soft-start")
for _ in range(2):
c("47uF", "SYS5", "GND", footprint="Capacitor_SMD:C_1210_3225Metric", description="5-V system bulk")
add_ic("U10", "SN74LVC2G02", "SN74LVC2G02DCUR", "Texas Instruments",
"Package_SO:VSSOP-8_2.3x2mm_P0.5mm", [
p(1, "1Y", "USB_3A_OK", "output"), p(2, "1A", "TUSB_OUT1", "input"), p(3, "1B", "TUSB_OUT2", "input"),
p(4, "GND", "GND", "power_in"), p(5, "2A", "MUX_ST", "input"), p(6, "2B", "MUX_ST", "input"),
p(7, "2Y", "EXT_SELECTED", "output"), p(8, "VCC", "VCC3V3_AUX", "power_in")],
"USB 3-A advertisement NOR decode and mux-status inversion")
add_ic("U11", "SN74LVC1G32", "SN74LVC1G32DBVR", "Texas Instruments", "Package_TO_SOT_SMD:SOT-23-5", [
p(1, "A", "USB_3A_OK", "input"), p(2, "B", "EXT_SELECTED", "input"), p(3, "GND", "GND", "power_in"),
p(4, "Y", "MAIN_EN", "output"), p(5, "VCC", "VCC3V3_AUX", "power_in")],
"Main-rail enable OR: qualified 3-A USB or selected external 5 V")
c("100nF", "VCC3V3_AUX", "GND", description="Power-qualification logic local bypass")
c("100nF", "VCC3V3_AUX", "GND", description="Power qualification logic bypass")
# Rail generation and Efinix sequencing.
def add_ldo(ref: str, value: str, mpn: str, vin: str, vout: str, enable: str, desc: str) -> None:
add_ic(ref, value, mpn, "Texas Instruments", "Package_TO_SOT_SMD:SOT-23-5", [
p(1, "IN", vin, "power_in"), p(2, "GND", "GND", "power_in"), p(3, "EN", enable, "input"),
p(4, "NC", None), p(5, "OUT", vout, "power_out")], desc)
c("1uF", vin, "GND", description=f"{ref} local input capacitor")
c("1uF", vout, "GND", description=f"{ref} low-noise LDO output capacitor")
add_ldo("U12", "TPS7A2033", "TPS7A2033PDBVR", "SYS5", "VCC3V3_AUX", "SYS5",
"Always-on 300-mA auxiliary 3.3-V LDO")
add_ic("U13", "TPS6287B15HA0", "TPS6287B15HA0WRZVR", "Texas Instruments",
"board:Texas_RZV0024A_WQFN24", [
p(1, "VOSNS", "CORE_SENSE_P", "input"), p(2, "EN", "CORE_EN_R", "input"),
p(3, "VSET1", "GND", "input"), p(4, "VSET2", "SYS5", "input"),
p(5, "VIN", "SYS5", "power_in"), p(6, "VIN", "SYS5", "power_in"),
p(7, "GND", "GND", "power_in"), p(8, "GND", "GND", "power_in"),
p(9, "SW", "CORE_SW", "power_out"), p(10, "SW", "CORE_SW", "power_out"),
p(11, "SW", "CORE_SW", "power_out"), p(12, "SW", "CORE_SW", "power_out"),
p(13, "GND", "GND", "power_in"), p(14, "GND", "GND", "power_in"),
p(15, "VIN", "SYS5", "power_in"), p(16, "VIN", "SYS5", "power_in"),
p(17, "SYNCOUT", None, "output"), p(18, "MODE/SYNC", "SYS5", "input"),
p(19, "SDA", "GND", "bidirectional"), p(20, "SCL", "GND", "bidirectional"),
p(21, "PG", "CORE_PG", "open_collector"), p(22, "AGND", "GND", "power_in"),
p(23, "COMP", "CORE_COMP", "input"), p(24, "GOSNS", "CORE_SENSE_N", "input"),
p(25, "THERMAL_PADS", "GND", "power_in")],
"15-A synchronous 0.95-V FPGA core/VCCA/VDD_SOC buck, fixed PWM")
r("15K", "MAIN_EN", "CORE_EN_R", description="TPS6287B mandatory >=15-kohm EN series resistor")
r("0R", "VCC0V95", "CORE_SENSE_P", description="Remote positive sense link at FPGA PDN")
r("0R", "GND", "CORE_SENSE_N", description="Remote ground sense link at FPGA PDN")
r("2.1K", "CORE_COMP", "CORE_COMP_RC", description="TPS6287B calculated compensation RZ")
c("1.8nF", "CORE_COMP_RC", "GND", description="TPS6287B calculated compensation CC")
c("10pF", "CORE_COMP", "GND", description="TPS6287B HF compensation bypass CC2")
two_pin("L", "100nH", "XEL4030-101MEC", "Coilcraft", "board:Coilcraft_XEL4030",
"CORE_SW", "VCC0V95", "Core inductor: 30-A saturation, 1.5-mohm DCR")
for _ in range(4):
c("22uF", "SYS5", "GND", mpn="GCM31CR71A226KE02L",
footprint="Capacitor_SMD:C_1206_3216Metric", description="Core buck butterfly input capacitor")
for _ in range(4):
c("100uF", "VCC0V95", "GND", footprint="Capacitor_SMD:C_1210_3225Metric",
description="Core output; 400-uF nominal, >=195-uF effective target")
r("10K", "VCC3V3_AUX", "CORE_PG", description="Core buck PG pull-up")
def add_tps62867(ref: str, output: str, sw: str, en: str, pg: str, vset: str, value: str) -> None:
add_ic(ref, f"TPS62867 {value}", "TPS62867RQYR", "Texas Instruments",
"board:Texas_RQY0009A_VQFN9", [
p(1, "AGND", "GND", "power_in"), p(2, "PGND", "GND", "power_in"),
p(3, "VIN", "SYS5", "power_in"), p(4, "EN", en, "input"),
p(5, "PG", pg, "open_collector"), p(6, "VSET/MODE", f"{ref}_VSET", "input"),
p(7, "SW", sw, "power_out"), p(8, "VOS", output, "input"), p(9, "FB", output, "input")],
f"6-A synchronous {value} FPGA auxiliary/I/O buck")
r(vset, f"{ref}_VSET", "GND", description=f"TPS62867 fixed {value} VSET resistor")
two_pin("L", "240nH", "DFE201612E-R24M=P2", "Murata", "board:Murata_DFE201612E",
sw, output, "TPS62867 manufacturer-recommended 6.6-A inductor")
for _ in range(2):
c("10uF", "SYS5", "GND", description=f"{ref} input capacitor")
for _ in range(2):
c("22uF", output, "GND", footprint="Capacitor_SMD:C_0805_2012Metric",
description=f"{ref} output capacitor")
add_tps62867("U14", "VCC1V8", "BUCK1V8_SW", "SEQ_FLAG1", "PG_1V8", "133K", "1.8 V")
add_tps62867("U15", "VCC3V3", "BUCK3V3_SW", "SEQ_FLAG2", "PG_3V3", "205K", "3.3 V")
add_ic("U16", "LM3880-1AA", "LM3880MF-1AA/NOPB", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-6", [
p(1, "VCC", "VCC3V3_AUX", "power_in"), p(2, "GND", "GND", "power_in"),
p(3, "EN", "CORE_PG", "input"), p(4, "FLAG3", "SEQ_FLAG3", "open_collector"),
p(5, "FLAG2", "SEQ_FLAG2", "open_collector"), p(6, "FLAG1", "SEQ_FLAG1", "open_collector")],
"Three-rail sequencer, 10-ms delay per rail")
for net in ("SEQ_FLAG1", "SEQ_FLAG2"):
r("10K", "VCC3V3_AUX", net, description="LM3880 open-drain flag pull-up")
c("100nF", "VCC3V3_AUX", "GND", description="LM3880 bypass")
# Keep the four independent rail-valid indications separate from CRESET_N.
# They are combined below by always-on logic; this prevents an intentional
# configuration reset from corrupting the power-good state used to isolate the
# FTDI drivers.
r("10K", "VCC1V8", "FPGA_CRESET_N", description="FPGA CRESET_N configuration-rail pull-up")
r("0R", "SEQ_FLAG3", "PWR_SEQ_OK", description="Sequencer final-flag sense link")
r("0R", "PG_1V8", "PWR_1V8_OK", description="1.8-V buck power-good sense link")
r("0R", "PG_3V3", "PWR_3V3_OK", description="3.3-V buck power-good sense link")
add_ldo("U17", "TPS7A2030", "TPS7A2030PDBVR", "VCC3V3", "VCC3V0_RF", "VCC3V3",
"Dedicated low-noise 3.0-V MAX2771 analog LDO")
add_ldo("U18", "TPS7A2030", "TPS7A2030PDBVR", "VCC3V3", "VCC3V0_DIG", "VCC3V3",
"Dedicated low-noise 3.0-V MAX2771 digital and FPGA-bank LDO")
add_ldo("U19", "TPS7A2030", "TPS7A2030PDBVR", "VCC3V3", "VCC3V0_CLK", "VCC3V3",
"Dedicated low-noise 3.0-V oscillator LDO")
add_ldo("U20", "TPS7A2033", "TPS7A2033PDBVR", "SYS5", "VCC3V3_ANT", "MAIN_EN",
"Clean 3.3-V active-antenna bias LDO")
# The supervisor is powered from the always-on auxiliary rail, so its open-drain
# output remains authoritative even while the monitored 3.0-V FPGA I/O rail is
# absent. Its 20-ms (CT open) timeout also guarantees the configuration delay
# after the last FPGA supply becomes valid. Power-good is kept separate from
# user/FTDI reset so the JTAG buffers stay enabled during deliberate resets.
add_ic("U28", "TPS3808G30", "TPS3808G30DBVR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-6", [
p(1, "RESET_N", "FPGA_POWER_OK_N", "open_collector"), p(2, "GND", "GND", "power_in"),
p(3, "MR_N", "VCC3V3_AUX", "input"), p(4, "CT", None, "input"),
p(5, "SENSE", "VCC3V0_DIG", "input"), p(6, "VDD", "VCC3V3_AUX", "power_in")],
"3.0-V FPGA-bank supervisor; open-drain reset with 20-ms release delay")
# Always-on AND tree creates a true all-rail-valid signal. The fourth gate
# includes LM3880 FLAG3, so both the programmed power-up interval and every
# converter/supervisor indication must be valid before FPGA-facing FTDI
# drivers are enabled or CRESET_N is released.
add_ic("U34", "SN74LVC08A", "SN74LVC08APWR", "Texas Instruments",
"Package_SO:TSSOP-14_4.4x5mm_P0.65mm", [
p(1, "1A", "CORE_PG", "input"), p(2, "1B", "PWR_1V8_OK", "input"),
p(3, "1Y", "PWR_OK_A", "output"),
p(4, "2A", "PWR_3V3_OK", "input"), p(5, "2B", "FPGA_POWER_OK_N", "input"),
p(6, "2Y", "PWR_OK_B", "output"), p(7, "GND", "GND", "power_in"),
p(8, "3Y", "PWR_OK_C", "output"), p(9, "3A", "PWR_OK_A", "input"),
p(10, "3B", "PWR_OK_B", "input"),
p(11, "4Y", "ALL_FPGA_POWER_OK", "output"),
p(12, "4A", "PWR_OK_C", "input"), p(13, "4B", "PWR_SEQ_OK", "input"),
p(14, "VCC", "VCC3V3_AUX", "power_in")],
"Always-on all-rail power-valid logic for reset release and debug isolation")
# Dense reference-design-style bypassing plus one bulk group per rail.
for _ in range(18):
c("100nF", "VCC0V95", "GND", description="Ti375 core/VCCA/VDD_SOC HF bypass")
for _ in range(4):
c("10uF", "VCC0V95", "GND", description="Ti375 core local bulk")
for _ in range(6):
c("100nF", "VCC1V8", "GND", description="Ti375 VCCAUX/HSIO/config bypass")
for _ in range(2):
c("10uF", "VCC1V8", "GND", description="Ti375 1.8-V local bulk")
for rail in ("VCC3V3", "VCC3V0_DIG"):
for _ in range(4):
c("100nF", rail, "GND", description=f"Ti375 HVIO bank bypass on {rail}")
c("10uF", rail, "GND", description=f"Ti375 I/O-rail bulk on {rail}")
# MAX2771 direct MIXIN_HI path and OFF/3.3-V/5-V active-antenna bias.
add_ic("J2", "GPS L1 SMA", "132289", "Amphenol RF",
"Connector_Coaxial:SMA_Amphenol_132289_EdgeMount",
[p(1, "RF", "RF_SMA"), p(2, "GND", "GND")],
"Edge-launch 50-ohm active GNSS antenna input")
components[-1].assembly = "THT"
add_ic("D1", "ESD501", "ESD501DPYR", "Texas Instruments", "board:Texas_DPY0002A_X1SON",
[p(1, "RF", "RF_SMA"), p(2, "GND", "GND")],
"0.3-pF bidirectional 12-V RF ESD protector")
c("27pF", "RF_SMA", "RF_PRE_SAW", mpn="600L270JT200T", footprint="board:Kyocera_600L_0402",
description="High-Q 200-V C0G GNSS RF DC block")
add_ic("FL1", "GPS L1/L1C SAW", "SF14-1582M5UUS2", "Kyocera", "board:Kyocera_SF14_5pad", [
p(1, "IN", "RF_PRE_SAW"), p(2, "GND", "GND"), p(3, "GND", "GND"),
p(4, "OUT", "RF_POST_SAW"), p(5, "GND", "GND")],
"1565.19-1585.65-MHz SAW, 1.2-dB typical insertion loss")
c("DNP", "RF_POST_SAW", "GND", mpn="", description="RF tuning shunt C1", dnp=True)
r("0R", "RF_POST_SAW", "MAX_MIXIN_HI", description="RF tuning series link")
c("DNP", "MAX_MIXIN_HI", "GND", mpn="", description="RF tuning shunt C2", dnp=True)
add_ic("J5", "ANT BIAS OFF/3V3/5V", "61300311121", "Wuerth Elektronik",
"Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical", [
p(1, "3V3", "VCC3V3_ANT", "power_in"), p(2, "SELECT", "ANT_BIAS_SEL", "power_in"),
p(3, "5V", "SYS5", "power_in")],
"One-shunt hardware antenna bias selection; no shunt is OFF")
components[-1].assembly = "THT"
add_ic("U27", "TPS22944", "TPS22944DCKR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-353_SC-70-5", [
p(1, "VOUT", "ANT_BIAS_SW", "power_out"), p(2, "GND", "GND", "power_in"),
p(3, "OC", "ANT_BIAS_OC", "open_collector"), p(4, "ON", "ANT_BIAS_ON", "input"),
p(5, "VIN", "ANT_BIAS_SEL", "power_in")],
"Fixed 100-mA current-limited active-antenna load switch")
r("100K", "ANT_BIAS_SEL", "ANT_BIAS_ON", description="Bias switch follows selected source")
r("100K", "ANT_BIAS_ON", "GND", description="No-jumper default OFF")
r("10K", "VCC3V3_AUX", "ANT_BIAS_OC", description="Antenna overcurrent pull-up")
c("1uF", "ANT_BIAS_SEL", "GND", description="Bias-switch input bypass")
c("1uF", "ANT_BIAS_SW", "GND", description="Bias-switch output bypass before choke")
two_pin("L", "56nH", "0402HPH-56NXJRW", "Coilcraft", "Inductor_SMD:L_0402_1005Metric",
"ANT_BIAS_SW", "RF_SMA", "L1 bias-tee choke: SRF 2.1 GHz, Q 64, 330 mA")
tp("ANT_BIAS_OC", "ANT OC")
add_ic("U21", "MAX2771", "MAX2771ETI+", "Analog Devices",
"Package_DFN_QFN:QFN-28-1EP_5x5mm_P0.5mm_EP3.25x3.25mm", [
p(1, "CPOUT", "MAX_CPOUT", "output"), p(2, "VCCCP", "VCC3V0_RF", "power_in"),
p(3, "XTAL", "MAX_XTAL", "input"), p(4, "CLKOUT", "MAX_CLKOUT_SRC", "output"),
p(5, "ADC_CLKIN", "MAX_ADC_CLKIN", "input"), p(6, "VCCD", "VCC3V0_DIG", "power_in"),
p(7, "SDATA", "MAX_SDATA_DEV", "bidirectional"), p(8, "SCLK", "MAX_SCLK_DEV", "input"),
p(9, "CSN", "MAX_CSN_DEV", "input"), p(10, "Q1", "MAX_Q1_DEV", "output"),
p(11, "Q0", "MAX_Q0_DEV", "output"), p(12, "I0", "MAX_I0_DEV", "output"),
p(13, "I1", "MAX_I1_DEV", "output"), p(14, "VCCADC", "VCC3V0_DIG", "power_in"),
p(15, "ANAIPOUT", None, "output"), p(16, "ANAINOUT", None, "output"),
p(17, "LD", "MAX_LD_DEV", "output"), p(18, "SHDN", "MAX_SHDN_DEV", "input"),
p(19, "LNAIN_HI", None, "input"), p(20, "LNAIN_LO", None, "input"), p(21, "NC", None),
p(22, "VCCRF", "VCC3V0_RF", "power_in"), p(23, "LNAOUT_LO", None, "output"),
p(24, "LNAOUT_HI", None, "output"), p(25, "MIXIN_HI", "MAX_MIXIN_HI", "input"),
p(26, "MIXIN_LO", None, "input"), p(27, "VCCIF", "VCC3V0_RF", "power_in"),
p(28, "VCCVCO", "VCC3V0_RF", "power_in"), p(29, "EP", "GND", "power_in")],
"GNSS receiver; both LNAs disabled; direct high-band mixer input")
# Official typical topology: CPOUT has a small direct capacitor and an R-C
# series integrating branch to ground.
c("15pF", "MAX_CPOUT", "GND", description="MAX2771 PLL loop-filter shunt capacitor")
r("15K", "MAX_CPOUT", "MAX_LOOP_RC", description="MAX2771 PLL loop-filter resistor")
c("750pF", "MAX_LOOP_RC", "GND", description="MAX2771 PLL loop integrating capacitor")
c("10nF", "TCXO16_OUT", "MAX_XTAL", description="MAX2771 external reference AC coupling")
r("33R", "OSC40_OUT", "MAX_ADC_CLKIN", description="40-MHz ADC-clock source termination")
for signal in ("MAX_CLKOUT", "MAX_I0", "MAX_I1", "MAX_Q0", "MAX_Q1", "MAX_LD"):
source = f"{signal}_SRC" if signal == "MAX_CLKOUT" else f"{signal}_DEV"
r("22R", source, signal, description="MAX2771 source-series digital damping")
for signal in ("MAX_SCLK", "MAX_CSN", "MAX_SDATA", "MAX_SHDN"):
r("22R", signal, f"{signal}_DEV", description="MAX2771 serial/control source damping")
r("100K", "MAX_SHDN_DEV", "GND", description="MAX2771 remains shut down before FPGA configuration")
for rail, count in (("VCC3V0_RF", 5), ("VCC3V0_DIG", 2)):
for _ in range(count):
c("100nF", rail, "GND", description=f"MAX2771 local {rail} bypass")
c("1uF", rail, "GND", description=f"MAX2771 local {rail} bulk")
add_ic("U23", "16.368 MHz TCXO", "X1G005441030125", "Epson", "board:Epson_TG2016SMN", [
p(1, "NC", None), p(2, "GND", "GND", "power_in"), p(3, "OUT", "TCXO16_OUT", "output"),
p(4, "VCC", "VCC3V0_CLK", "power_in")],
"TG2016SMN clipped-sine ±0.5-ppm MAX2771 reference")
c("100nF", "VCC3V0_CLK", "GND", description="16.368-MHz TCXO bypass")
c("1uF", "VCC3V0_CLK", "GND", description="Clock-rail local bulk")
add_ic("U24", "40.000 MHz XO", "X1G004171041100", "Epson",
"Oscillator:Oscillator_SMD_SeikoEpson_SG210-4Pin_2.5x2.0mm", [
p(1, "ST", "VCC3V0_CLK", "input"), p(2, "GND", "GND", "power_in"),
p(3, "OUT", "OSC40_OUT", "output"), p(4, "VCC", "VCC3V0_CLK", "power_in")],
"SG-210STF ±25-ppm CMOS FPGA/system ADC oscillator")
r("33R", "OSC40_OUT", "CLK_40M_FPGA", description="40-MHz FPGA-clock source termination")
c("100nF", "VCC3V0_CLK", "GND", description="40-MHz oscillator bypass")
# Exact Murata SCH16T-K01-1 SafeSPI pinout and carrier-board capacitors.
add_ic("U22", "SCH16T-K01-1", "SCH16T-K01-1", "Murata", "board:Murata_SCH16T_K01", [
p(1, "HS_GND", "GND", "power_in"), p(2, "RESERVED", None),
p(3, "TA9_GND", "GND", "power_in"), p(4, "TA8_GND", "GND", "power_in"),
p(5, "RESERVED_GND", "GND", "power_in"), p(6, "EXTRESN", "IMU_EXTRESN", "input"),
p(7, "RESERVED_GND", "GND", "power_in"), p(8, "V3P3", "IMU_3V3", "power_in"),
p(9, "GND", "GND", "power_in"), p(10, "VREGA2", "IMU_VREGA2", "power_out"),
p(11, "VREGA", "IMU_VREGA2", "power_out"), p(12, "GND", "GND", "power_in"),
p(13, "GND", "GND", "power_in"), p(14, "VREGD2", "IMU_VREGD", "power_out"),
p(15, "VREGD", "IMU_VREGD", "power_out"), p(16, "GND", "GND", "power_in"),
p(17, "V3P3", "IMU_3V3", "power_in"), p(18, "VDDIO", "VCC3V3", "power_in"),
p(19, "MISO", "IMU_MISO_DEV", "output"), p(20, "DRY_SYNC", "IMU_DRY_SYNC", "bidirectional"),
p(21, "SCK", "IMU_SCK_DEV", "input"), p(22, "CS", "IMU_CS_DEV", "input"),
p(23, "MOSI", "IMU_MOSI_DEV", "input"), p(24, "HS_GND", "GND", "power_in")],
"Six-DoF SafeSPI IMU, exact K01-1 variant")
two_pin("FB", "600R@100MHz", "BLM15AG601SN1D", "Murata", "Inductor_SMD:L_0402_1005Metric",
"VCC3V3", "IMU_3V3", "IMU supply ferrite isolation")
for signal in ("IMU_SCK", "IMU_MOSI", "IMU_CS"):
r("22R", signal, f"{signal}_DEV", description="SafeSPI source-series damping")
r("22R", "IMU_MISO_DEV", "IMU_MISO", description="SafeSPI MISO source damping at IMU")
r("10K", "IMU_EXTRESN", "GND", description="Hold IMU reset until FPGA releases it")
c("100nF", "IMU_3V3", "GND", description="Murata V3P3 HF bypass")
c("1uF", "IMU_3V3", "GND", description="Murata V3P3 local capacitor")
c("10uF", "IMU_VREGA2", "GND", description="Murata required VREGA2 capacitor")
c("1uF", "IMU_VREGD", "GND", description="Murata required VREGD capacitor")
c("100nF", "VCC3V3", "GND", description="Murata VDDIO bypass")
# FT4232H follows the Efinix development-kit interface order:
# A/interface 0 = RISC-V JTAG, B/interface 1 = FPGA hard JTAG.
ftdi_pins = [
p(1, "EECS", "FTDI_EECS", "output"), p(2, "VCORE", "FTDI_1V8", "power_in"),
p(3, "OSCI", "FTDI_OSCI", "input"), p(4, "OSCO", None, "output"),
p(5, "VPHY", "FTDI_VPHY", "power_in"), p(6, "REF", "FTDI_REF", "input"),
p(7, "DM", "FTDI_D_N", "bidirectional"), p(8, "DP", "FTDI_D_P", "bidirectional"),
p(9, "VPLL", "FTDI_VPLL", "power_in"), p(10, "TEST", "GND", "input"),
p(11, "RESET_N", "FTDI_RESET_N", "input"),
p(12, "ADBUS0/TCK", "FTDI_A_TCK_SRC", "bidirectional"),
p(13, "ADBUS1/TDI", "FTDI_A_TDI_SRC", "bidirectional"),
p(14, "ADBUS2/TDO", "FTDI_A_TDO_SRC", "bidirectional"),
p(15, "ADBUS3/TMS", "FTDI_A_TMS_SRC", "bidirectional"),
p(16, "VCCIO", "VCC3V3_AUX", "power_in"),
p(17, "ADBUS4", None, "bidirectional"), p(18, "ADBUS5", None, "bidirectional"),
p(19, "ADBUS6", None, "bidirectional"), p(20, "ADBUS7", None, "bidirectional"),
p(21, "GND", "GND", "power_in"),
p(22, "BDBUS0/TCK", "FTDI_B_TCK_SRC", "bidirectional"),
p(23, "BDBUS1/TDI", "FTDI_B_TDI_SRC", "bidirectional"),
p(24, "BDBUS2/TDO", "FTDI_B_TDO_SRC", "bidirectional"),
p(25, "BDBUS3/TMS", "FTDI_B_TMS_SRC", "bidirectional"),
p(26, "BDBUS4", "FTDI_B_RESET_N_SRC", "bidirectional"), p(27, "BDBUS5", None, "bidirectional"),
p(28, "BDBUS6", None, "bidirectional"), p(29, "BDBUS7", None, "bidirectional"),
p(30, "SUSPEND_N", None, "output"), p(31, "VCORE", "FTDI_1V8", "power_in"),
p(32, "CDBUS0/TXD", "FTDI_C_TXD_SRC", "bidirectional"),
p(33, "CDBUS1/RXD", "FTDI_C_RXD_SRC", "bidirectional"),
p(34, "CDBUS2", None, "bidirectional"), p(35, "CDBUS3", None, "bidirectional"),
p(36, "VCCIO", "VCC3V3_AUX", "power_in"),
p(37, "CDBUS4", None, "bidirectional"), p(38, "CDBUS5", None, "bidirectional"),
p(39, "CDBUS6", None, "bidirectional"), p(40, "CDBUS7", None, "bidirectional"),
p(41, "GND", "GND", "power_in"),
p(42, "DDBUS0/TXD", "FTDI_D_TXD_SRC", "bidirectional"),
p(43, "VREGOUT", "FTDI_1V8", "power_out"), p(44, "VREGIN", "VCC3V3_AUX", "power_in"),
p(45, "GND", "GND", "power_in"),
p(46, "DDBUS1/RXD", "FTDI_D_RXD_SRC", "bidirectional"),
p(47, "DDBUS2/RTS", "FTDI_D_DBG_SRC", "bidirectional"),
p(48, "DDBUS3", None, "bidirectional"), p(49, "DDBUS4", None, "bidirectional"),
p(50, "VCCIO", "VCC3V3_AUX", "power_in"),
p(51, "DDBUS5", None, "bidirectional"), p(52, "DDBUS6", None, "bidirectional"),
p(53, "DDBUS7", None, "bidirectional"), p(54, "PWREN_N", None, "output"),
p(55, "EEDATA", "FTDI_EEDATA", "bidirectional"), p(56, "EECLK", "FTDI_EECLK", "output"),
p(57, "EP", "GND", "power_in")]
add_ic("U2", "FT4232H-56Q", "FT4232H-56Q-REEL", "FTDI", "board:FTDI_VQFN56_EP5p9",
ftdi_pins, "Quad high-speed USB bridge; all four channels assigned")
r("27R", "USB_D_P", "FTDI_D_P", description="FTDI USB D+ series termination")
r("27R", "USB_D_N", "FTDI_D_N", description="FTDI USB D- series termination")
r("12K", "FTDI_REF", "GND", description="FT4232H USB PHY reference resistor, 1%")
r("4.7K", "DBG_VBUS_RAW", "FTDI_RESET_N", description="Self-powered VBUS-present reset divider top")
r("10K", "FTDI_RESET_N", "GND", description="Self-powered VBUS-present reset divider bottom")
for _ in range(2):
c("100nF", "VCC3V3_AUX", "GND", description="FT4232H VCCIO/VREGIN bypass")
for _ in range(2):
c("100nF", "FTDI_1V8", "GND", description="FT4232H VCORE bypass")
c("1uF", "FTDI_1V8", "GND", description="FT4232H regulator output capacitor")
two_pin("FB", "600R@100MHz", "BLM15AG601SN1D", "Murata", "Inductor_SMD:L_0402_1005Metric",
"VCC3V3_AUX", "FTDI_VPHY", "FTDI USB-PHY supply ferrite")
two_pin("FB", "600R@100MHz", "BLM15AG601SN1D", "Murata", "Inductor_SMD:L_0402_1005Metric",
"VCC3V3_AUX", "FTDI_VPLL", "FTDI PLL supply ferrite")
c("100nF", "FTDI_VPHY", "GND", description="FTDI VPHY bypass")
c("100nF", "FTDI_VPLL", "GND", description="FTDI VPLL bypass")
add_ic("U25", "12.000 MHz XO", "ECS-2520MV-120-BN-TR", "ECS",
"Oscillator:Oscillator_SMD_ECS_2520MV-xxx-xx-4Pin_2.5x2.0mm", [
p(1, "OE", "VCC3V3_AUX", "input"), p(2, "GND", "GND", "power_in"),
p(3, "OUT", "FTDI_OSC12", "output"), p(4, "VDD", "VCC3V3_AUX", "power_in")],
"Current-production 12-MHz CMOS FT4232H clock")
r("33R", "FTDI_OSC12", "FTDI_OSCI", description="FTDI oscillator source termination")
c("100nF", "VCC3V3_AUX", "GND", description="FTDI oscillator local bypass")
add_ic("U3", "93LC56B x16", "93LC56BT-I/OT", "Microchip", "Package_TO_SOT_SMD:SOT-23-6", [
p(1, "CS", "FTDI_EECS", "input"), p(2, "CLK", "FTDI_EECLK", "input"),
p(3, "DI", "FTDI_EEDATA", "input"), p(4, "DO", "FTDI_EEDO", "output"),
p(5, "VSS", "GND", "power_in"), p(6, "VCC", "VCC3V3_AUX", "power_in")],
"FTDI descriptor/channel-mode EEPROM, fixed 16-bit organization")
r("2.2K", "FTDI_EEDO", "FTDI_EEDATA", description="EEPROM shared-data isolation")
r("10K", "FTDI_EEDO", "VCC3V3_AUX", description="EEPROM DO pull-up")
c("100nF", "VCC3V3_AUX", "GND", description="FTDI EEPROM bypass")
# FTDI is intentionally powered from the always-on debug rail. All signals
# that can drive the FPGA therefore pass through Ioff-capable buffers powered
# by the FPGA's 3.3-V I/O rail. When the FPGA rail is absent, the buffer
# outputs are high impedance and no unpowered FPGA pin can be back-driven.
add_ic("U29", "SN74LVC244A", "SN74LVC244APWR", "Texas Instruments",
"Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm", [
p(1, "1OE_N", "FTDI_FPGA_BUF_OE_N", "input"),
p(2, "1A1", "FTDI_A_TCK_SRC", "input"), p(18, "1Y1", "BUF_A_TCK", "output"),
p(4, "1A2", "FTDI_A_TDI_SRC", "input"), p(16, "1Y2", "BUF_A_TDI", "output"),
p(6, "1A3", "FTDI_A_TMS_SRC", "input"), p(14, "1Y3", "BUF_A_TMS", "output"),
p(8, "1A4", "FTDI_B_TCK_SRC", "input"), p(12, "1Y4", "BUF_B_TCK", "output"),
p(19, "2OE_N", "FTDI_FPGA_BUF_OE_N", "input"),
p(11, "2A1", "FTDI_B_TDI_SRC", "input"), p(9, "2Y1", "BUF_B_TDI", "output"),
p(13, "2A2", "FTDI_B_TMS_SRC", "input"), p(7, "2Y2", "BUF_B_TMS", "output"),
p(15, "2A3", "FTDI_C_TXD_SRC", "input"), p(5, "2Y3", "BUF_C_TXD", "output"),
p(17, "2A4", "FTDI_D_TXD_SRC", "input"), p(3, "2Y4", "BUF_D_TXD", "output"),
p(10, "GND", "GND", "power_in"), p(20, "VCC", "VCC3V3", "power_in")],
"Ioff-protected FPGA-input buffer; automatically isolates when FPGA 3.3 V is off")
add_ic("U30", "SN74LVC1G125", "SN74LVC1G125DBVR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-5", [p(1, "OE_N", "FTDI_FPGA_BUF_OE_N", "input"),
p(2, "A", "FTDI_D_DBG_SRC", "input"),
p(3, "GND", "GND", "power_in"), p(4, "Y", "BUF_D_DBG", "output"),
p(5, "VCC", "VCC3V3", "power_in")],
"Ioff-protected tri-state debug-GPIO buffer powered from FPGA 3.3 V")
add_ic("U31", "SN74LVC1G07", "SN74LVC1G07DBVR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-5", [p(1, "NC", None), p(2, "A", "FTDI_B_RESET_N_SRC", "input"),
p(3, "GND", "GND", "power_in"), p(4, "Y_OD", "FPGA_CRESET_N", "open_collector"),
p(5, "VCC", "VCC3V3_AUX", "power_in")],
"Open-drain FTDI reset request into FPGA CRESET_N")
add_ic("U32", "SN74LVC1G07", "SN74LVC1G07DBVR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-5", [p(1, "NC", None), p(2, "A", "ALL_FPGA_POWER_OK", "input"),
p(3, "GND", "GND", "power_in"), p(4, "Y_OD", "FPGA_CRESET_N", "open_collector"),
p(5, "VCC", "VCC3V3_AUX", "power_in")],
"Open-drain all-rail-valid translation into FPGA CRESET_N")
add_ic("U33", "SN74LVC1G07", "SN74LVC1G07DBVR", "Texas Instruments",
"Package_TO_SOT_SMD:SOT-23-5", [p(1, "NC", None), p(2, "A", "EXT_RESET_FILTER_N", "input"),
p(3, "GND", "GND", "power_in"), p(4, "Y_OD", "FPGA_CRESET_N", "open_collector"),
p(5, "VCC", "VCC1V8", "power_in")],
"Open-drain external/manual reset isolation into FPGA CRESET_N")
add_ic("Q1", "MMBT3904", "MMBT3904LT1G", "onsemi", "Package_TO_SOT_SMD:SOT-23", [
p(1, "B", "FTDI_BUF_OE_BASE", "input"), p(2, "E", "GND", "power_in"),
p(3, "C", "FTDI_FPGA_BUF_OE_N", "open_collector")],
"Power-good inverter: enables FTDI-to-FPGA buffers only after all rails are valid")
for src, dst in (("BUF_B_TCK", "HARD_TCK"), ("BUF_B_TMS", "HARD_TMS"),
("BUF_B_TDI", "HARD_TDI"), ("HARD_TDO", "FTDI_B_TDO_SRC")):
r("33R", src, dst, description="FTDI interface-1 hard-JTAG source damping")
r("10K", "VCC3V3", "HARD_TMS", description="Efinix-required hard-JTAG weak pull-up")
c("100nF", "VCC3V3_AUX", "GND", description="Additional FT4232H channel-A VCCIO bypass")
for src, dst in (("BUF_A_TCK", "RISCV_TCK"), ("BUF_A_TDI", "RISCV_TDI"),
("BUF_A_TMS", "RISCV_TMS"), ("RISCV_TDO", "FTDI_A_TDO_SRC"),
("BUF_C_TXD", "UART0_RX"), ("UART0_TX", "FTDI_C_RXD_SRC"),
("BUF_D_TXD", "UART1_RX"), ("UART1_TX", "FTDI_D_RXD_SRC"),
("BUF_D_DBG", "FTDI_DBG_GPIO")):
r("33R", src, dst, description="FT4232H channel signal damping")
# Active-serial flash and four compact Digilent-format PMOD connectors.
add_ic("U26", "512-Mbit QSPI flash", "IS25WP512M-JLLE", "ISSI",
"Package_SON:WSON-8-1EP_6x5mm_P1.27mm_EP3.4x4.3mm", [
p(1, "CS_N", "QSPI_CS_N", "input"), p(2, "SO/IO1", "QSPI_IO1", "bidirectional"),
p(3, "WP_N/IO2", "QSPI_IO2", "bidirectional"), p(4, "GND", "GND", "power_in"),
p(5, "SI/IO0", "QSPI_IO0", "bidirectional"), p(6, "SCLK", "QSPI_SCLK", "input"),
p(7, "HOLD_N/IO3", "QSPI_IO3", "bidirectional"), p(8, "VCC", "VCC1V8", "power_in"),
p(9, "EP", "GND", "power_in")],
"1.8-V 512-Mbit FPGA active-serial configuration flash")
r("10K", "VCC1V8", "QSPI_CS_N", description="Configuration flash CS pull-up")
r("10K", "VCC1V8", "FPGA_TEST_N", description="Efinix mandatory TEST_N weak pull-up")
r("10K", "VCC1V8", "FPGA_CSI", description="Efinix mandatory CSI weak pull-up")
for net in ("QSPI_IO2", "QSPI_IO3"):
r("10K", "VCC1V8", net, description="Configuration flash hold/write-protect pull-up")
c("100nF", "VCC1V8", "GND", description="Configuration flash bypass")
c("1uF", "VCC1V8", "GND", description="Configuration flash local bulk")
def add_pmod(ref: str, prefix: str, rail: str, voltage: str) -> None:
pins = [p(i, f"IO{i}", f"{prefix}_{i}") for i in (1, 2, 3, 4)]
pins += [p(5, "GND", "GND"), p(6, voltage, rail)]
pins += [p(i, f"IO{i}", f"{prefix}_{i}") for i in (7, 8, 9, 10)]
pins += [p(11, "GND", "GND"), p(12, voltage, rail)]
add_ic(ref, f"PMOD {voltage}", "67997-412HLF", "Amphenol ICC",
"board:Digilent_PMOD_2x6_P2.54mm", pins,
f"Digilent 2x6 PMOD: eight {voltage} GPIO, two GND, two {voltage}")
components[-1].assembly = "THT"
add_pmod("JPMOD1", "PMOD1", "VCC3V3", "3.3 V")
add_pmod("JPMOD2", "PMOD2", "VCC3V3", "3.3 V")
add_pmod("JPMOD3", "PMOD3", "VCC1V8", "1.8 V")
add_pmod("JPMOD4", "PMOD4", "VCC1V8", "1.8 V")
for net, label in (("VBUS_RAW", "USB VBUS"), ("EXT5_RAW", "EXT 5V"), ("SYS5", "SYS 5V"),
("VCC3V3_AUX", "AUX 3V3"), ("VCC0V95", "CORE 0V95"),
("VCC1V8", "1V8"), ("VCC3V3", "3V3"), ("VCC3V0_RF", "RF 3V0"),
("VCC3V0_DIG", "DIG 3V0"), ("FPGA_CRESET_N", "CRESET_N"),
("FPGA_CDONE", "CDONE"), ("GND", "GND")):
tp(net, label)
# Configuration/JTAG pulls are located on the FPGA side of the isolation
# buffers and use the voltage of their respective I/O bank.
r("10K", "VCC1V8", "FPGA_CDONE", description="Efinix-required CDONE open-drain pull-up")
for net in ("HARD_TCK", "HARD_TDI", "HARD_TDO"):
r("10K", "VCC3V3", net, description="Efinix-required hard-JTAG weak pull-up")
for net in ("RISCV_TCK", "RISCV_TMS", "RISCV_TDI", "RISCV_TDO"):
r("10K", "VCC3V3", net, description="RISC-V JTAG idle-state pull-up")
# Supervisor support and reset-request fan-in. FTDI BDBUS4 uses the
# Efinix-compatible active-low convention. Independent open-drain paths avoid
# output contention and allow JTAG to remain live while CRESET_N is asserted.
r("10K", "VCC3V3_AUX", "FTDI_B_RESET_N_SRC", description="FTDI FPGA-reset request default release")
r("10K", "VCC3V3_AUX", "FPGA_POWER_OK_N", description="TPS3808 open-drain power-good pull-up")
r("10K", "VCC3V3", "FTDI_FPGA_BUF_OE_N", description="Keep FTDI-to-FPGA buffers disabled until power-good")
r("10K", "ALL_FPGA_POWER_OK", "FTDI_BUF_OE_BASE", description="All-rail-good inverter base-current limiter")
r("100K", "FTDI_BUF_OE_BASE", "GND", description="Power-good inverter default-off base pull-down")
r("10K", "VCC1V8", "EXT_RESET_FILTER_N", description="External/manual reset input pull-up")
r("1K", "EXT_RESET_N", "EXT_RESET_FILTER_N", description="External-reset input current and ESD limiting")
r("10K", "VCC3V3_AUX", "PWR_SEQ_OK", description="LM3880 final-flag pull-up for all-power logic")
r("10K", "VCC3V3_AUX", "PWR_1V8_OK", description="1.8-V power-good pull-up for all-power logic")
r("10K", "VCC3V3_AUX", "PWR_3V3_OK", description="3.3-V power-good pull-up for all-power logic")
c("4.7nF", "VCC3V0_DIG", "GND", description="TPS3808 SENSE transient filter")
c("4.7nF", "EXT_RESET_FILTER_N", "GND", description="Manual/external reset cable-noise filter")
c("100nF", "VCC3V3_AUX", "GND", description="TPS3808 local bypass")
c("100nF", "VCC3V3", "GND", description="SN74LVC244A local bypass")
c("100nF", "VCC3V3", "GND", description="SN74LVC1G125 local bypass")
c("100nF", "VCC3V3_AUX", "GND", description="SN74LVC1G07 local bypass")
c("100nF", "VCC3V3_AUX", "GND", description="Power-good open-drain buffer local bypass")
c("100nF", "VCC1V8", "GND", description="External-reset open-drain buffer local bypass")
c("100nF", "VCC3V3_AUX", "GND", description="All-rail-valid logic local bypass")
add_ic("J4", "EXT RESET_N", "SM02B-SRSS-TB(LF)(SN)", "JST",
"Connector_JST:JST_SH_SM02B-SRSS-TB_1x02-1MP_P1.00mm_Horizontal", [
p(1, "EXT_RESET_N", "EXT_RESET_N", "input"), p(2, "GND", "GND", "power_in")],
"External active-low open-drain reset input; JST-SH 1.0-mm")
add_ic("SW1", "FPGA RESET", "B3U-1000P", "Omron",
"Button_Switch_SMD:SW_SPST_B3U-1000P", [
p(1, "RESET", "EXT_RESET_FILTER_N", "passive"), p(2, "GND", "GND", "passive")],
"User-accessible momentary FPGA configuration reset")
add_ic("D2", "RESET ESD", "ESD5Z3.3T1G", "onsemi", "Diode_SMD:D_SOD-523", [
p(1, "K", "EXT_RESET_N", "passive"), p(2, "A", "GND", "passive")],
"External-reset connector ESD clamp")
# Dedicated USB-C power input, physically and electrically separate from the
# J1 debug/JTAG/UART port. Its VBUS feeds eFuse U7 (unchanged: U7 already
# reads net "VBUS_RAW", which now belongs to this connector instead of J1) so
# it goes through the same reverse-blocking protection as before and arrives
# at the same TPS2121 IN1 input. U9 keeps external EXT5_PROT (J3) as the
# higher-priority source, so external power and this USB-C power input can
# never feed SYS5 at the same time. D+/D-/SBU are power-only and left
# unconnected; the shield ties to the same USB_SHIELD net as J1.
add_ic("J7", "USB-C power input", "USB4105-GF-A-120", "GCT",
"Connector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal", [
p("A1", "GND", "GND"), p("A4", "VBUS", "VBUS_RAW"), p("A5", "CC1", "PWR_CC1"),
p("A6", "D+", None), p("A7", "D-", None), p("A8", "SBU1", None),
p("A9", "VBUS", "VBUS_RAW"), p("A12", "GND", "GND"),
p("B1", "GND", "GND"), p("B4", "VBUS", "VBUS_RAW"), p("B5", "CC2", "PWR_CC2"),
p("B6", "D+", None), p("B7", "D-", None), p("B8", "SBU2", None),
p("B9", "VBUS", "VBUS_RAW"), p("B12", "GND", "GND"), p("SH", "SHIELD", "USB_SHIELD")],
"USB 2.0 Type-C receptacle, UFP/sink, power input only")
add_ic("U35", "TUSB320LAI", "TUSB320LAIRSW(R)", "Texas Instruments",
"Package_DFN_QFN:Texas_X2QFN-12_1.6x1.6mm_P0.4mm", [
p(1, "CC1", "PWR_CC1", "bidirectional"), p(2, "CC2", "PWR_CC2", "bidirectional"),
p(3, "PORT", "GND", "input"), p(4, "VBUS_DET", "PWR_USB_VBUS_DET", "input"),
p(5, "ADDR", None), p(6, "OUT3", None, "output"),
p(7, "OUT1", "TUSB_OUT1", "output"), p(8, "OUT2", "TUSB_OUT2", "output"),
p(9, "ID", None, "output"), p(10, "GND", "GND", "power_in"),
p(11, "EN_N", "GND", "input"), p(12, "VDD", "VCC3V3_AUX", "power_in")],
"USB Type-C port controller in UFP GPIO mode for the power input; "
"integrated Rd on CC1/CC2; OUT1/OUT2 3-A advertisement drives U10/U11 MAIN_EN gating")
# TUSB_OUT1/TUSB_OUT2 pull-ups already exist beside U5 (net names are reused,
# not the physical position); adding a second pair here would halve their
# effective value instead of moving them.
r("900K", "VBUS_RAW", "PWR_USB_VBUS_DET", description="TUSB320 VBUS_DET current limiter")
c("100nF", "VCC3V3_AUX", "GND", description="TUSB320 local bypass")
def validate() -> None:
refs = [x.ref for x in components]
assert len(refs) == len(set(refs))
u1 = next(x for x in components if x.ref == "U1")
assert len(u1.pins) == 529 and len({x.number for x in u1.pins}) == 529
assert next(x for x in u1.pins if x.number == "G10").net == "HARD_TCK"
assert {pin.number: pin.net for pin in u1.pins if pin.number in {
"G10", "G12", "G9", "G13", "F12", "V19", "P18", "T18"
}} == {
"G10": "HARD_TCK", "G12": "HARD_TMS", "G9": "HARD_TDI", "G13": "HARD_TDO",
"F12": "RISCV_TCK", "V19": "RISCV_TDI", "P18": "RISCV_TMS", "T18": "RISCV_TDO",
}
assert next(x for x in u1.pins if x.number == "B11").net == "MAX_CLKOUT"
maxic = next(x for x in components if x.ref == "U21")
assert next(x for x in maxic.pins if x.number == "25").net == "MAX_MIXIN_HI"
assert all(next(x for x in maxic.pins if x.number == n).net is None
for n in ("19", "20", "23", "24", "26"))
pmods = [next(x for x in components if x.ref == f"JPMOD{i}") for i in range(1, 5)]
assert all(len(x.pins) == 12 for x in pmods)
assert all(sum(pin.net == "GND" for pin in x.pins) == 2 for x in pmods)
assert all(sum((pin.net or "").startswith("PMOD") for pin in x.pins) == 8 for x in pmods)
assert all(sum(pin.net == "VCC3V3" for pin in x.pins) == 2 for x in pmods[:2])
assert all(sum(pin.net == "VCC1V8" for pin in x.pins) == 2 for x in pmods[2:])
assert not any((pin.net or "").startswith("EXP_") for pin in u1.pins)
u2 = next(x for x in components if x.ref == "U2")
assert {pin.number: pin.net for pin in u2.pins if pin.number in {
"12", "13", "14", "15", "22", "23", "24", "25"
}} == {
"12": "FTDI_A_TCK_SRC", "13": "FTDI_A_TDI_SRC",
"14": "FTDI_A_TDO_SRC", "15": "FTDI_A_TMS_SRC",
"22": "FTDI_B_TCK_SRC", "23": "FTDI_B_TDI_SRC",
"24": "FTDI_B_TDO_SRC", "25": "FTDI_B_TMS_SRC",
}
assert not any(c.ref in {"U4", "J6"} for c in components)
assert not any((pin.net or "").startswith("FTDI_HARD_") or pin.net == "JTAG_SW_OE_N"
for c in components for pin in c.pins)
def has_link(net_a: str, net_b: str, value: str = "33R") -> bool:
return any(c.value == value and len(c.pins) == 2 and
{c.pins[0].net, c.pins[1].net} == {net_a, net_b}
for c in components)
for src, dst in (
("BUF_A_TCK", "RISCV_TCK"), ("BUF_A_TMS", "RISCV_TMS"),
("BUF_A_TDI", "RISCV_TDI"), ("FTDI_A_TDO_SRC", "RISCV_TDO"),
("BUF_B_TCK", "HARD_TCK"), ("BUF_B_TMS", "HARD_TMS"),
("BUF_B_TDI", "HARD_TDI"), ("FTDI_B_TDO_SRC", "HARD_TDO"),
):
assert has_link(src, dst), (src, dst)
assert next(pin for pin in u2.pins if pin.number == "26").net == "FTDI_B_RESET_N_SRC"
u28 = next(x for x in components if x.ref == "U28")
assert {pin.name: pin.net for pin in u28.pins} == {
"RESET_N": "FPGA_POWER_OK_N", "GND": "GND", "MR_N": "VCC3V3_AUX",
"CT": None, "SENSE": "VCC3V0_DIG", "VDD": "VCC3V3_AUX"}
u34 = next(x for x in components if x.ref == "U34")
assert {pin.number: pin.net for pin in u34.pins if pin.number in {"1", "2", "4", "5", "12", "13"}} == {
"1": "CORE_PG", "2": "PWR_1V8_OK", "4": "PWR_3V3_OK",
"5": "FPGA_POWER_OK_N", "12": "PWR_OK_C", "13": "PWR_SEQ_OK"}
assert next(pin for pin in next(x for x in components if x.ref == "U32").pins
if pin.number == "2").net == "ALL_FPGA_POWER_OK"
assert not any(c.value == "0R" and "FPGA_CRESET_N" in {pin.net for pin in c.pins}
for c in components)
for net, rail in (("FPGA_CDONE", "VCC1V8"), ("HARD_TCK", "VCC3V3"),
("HARD_TMS", "VCC3V3"), ("HARD_TDI", "VCC3V3"),
("HARD_TDO", "VCC3V3")):
assert has_link(net, rail, "10K"), (net, rail)
assert {pin.net for pin in next(x for x in components if x.ref == "J4").pins} == {
"EXT_RESET_N", "GND"}
assert [x.ref for x in components if x.ref.startswith("TP")] == [f"TP{i}" for i in range(1, 16)]
j5 = next(x for x in components if x.ref == "J5")
assert {x.net for x in j5.pins} == {"VCC3V3_ANT", "ANT_BIAS_SEL", "SYS5"}
validate()