#!/usr/bin/env python3 """Generate the authoritative Ti375 pin tables and MAX2771 register image. The package source is exported directly from Efinity 2025.2's Ti375C529 resource database by export_ti375_package.py. This script deliberately fails if a named ball, bank, voltage, or clock capability does not match that source. """ from __future__ import annotations import csv from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PACKAGE_CSV = ROOT / "docs" / "ti375_c529_package.csv" # signal, ball, direction, subsystem, selected I/O standard, design note ASSIGNMENTS = [ ("HARD_TCK", "G10", "input", "hard_jtag", "3.3 V LVCMOS", "Dedicated hard TAP TCK; FT4232H interface 1 BDBUS0 through power-domain isolation"), ("HARD_TMS", "G12", "input", "hard_jtag", "3.3 V LVCMOS", "Dedicated hard TAP TMS; FT4232H interface 1 BDBUS3 through power-domain isolation"), ("HARD_TDI", "G9", "input", "hard_jtag", "3.3 V LVCMOS", "Dedicated hard TAP TDI; FT4232H interface 1 BDBUS1 through power-domain isolation"), ("HARD_TDO", "G13", "output", "hard_jtag", "3.3 V LVCMOS", "Dedicated hard TAP TDO; FT4232H interface 1 BDBUS2 through power-domain isolation"), ("FPGA_CRESET_N", "F9", "input", "configuration", "1.8 V LVCMOS", "Open-drain fan-in of all-rail-valid, FTDI, and filtered external/manual reset requests"), ("FPGA_CDONE", "F8", "output", "configuration", "1.8 V LVCMOS", "Configuration status test point"), ("QSPI_CS_N", "A7", "output", "configuration", "1.8 V LVCMOS", "SPI-active SSL_N"), ("QSPI_SCLK", "G6", "output", "configuration", "1.8 V LVCMOS", "SPI-active CCK; official CCK_PLLIN0 pin"), ("QSPI_IO0", "H4", "bidirectional", "configuration", "1.8 V LVCMOS", "SPI-active CDI0"), ("QSPI_IO1", "H5", "bidirectional", "configuration", "1.8 V LVCMOS", "SPI-active CDI1"), ("QSPI_IO2", "G5", "bidirectional", "configuration", "1.8 V LVCMOS", "SPI-active CDI2"), ("QSPI_IO3", "B7", "bidirectional", "configuration", "1.8 V LVCMOS", "SPI-active CDI3"), ("FPGA_TEST_N", "J2", "input", "configuration", "1.8 V LVCMOS", "Mandatory external weak pull-up disables test mode during configuration"), ("FPGA_CSI", "J5", "input", "configuration", "1.8 V LVCMOS", "Mandatory external weak pull-up selects this FPGA throughout configuration"), ("RISCV_TCK", "F12", "input", "secondary_jtag", "3.3 V LVCMOS", "Fabric RISC-V JTAG clock moved off the plain-GPIO PMOD allocation"), ("RISCV_TDI", "V19", "input", "secondary_jtag", "3.3 V LVCMOS", "Official development-kit hardened RISC-V JTAG assignment"), ("RISCV_TMS", "P18", "input", "secondary_jtag", "3.3 V LVCMOS", "Clock-capable fabric pin; official development-kit assignment"), ("RISCV_TDO", "T18", "output", "secondary_jtag", "3.3 V LVCMOS", "Official development-kit hardened RISC-V JTAG assignment"), ("MAX_CLKOUT", "B11", "input", "max2771", "3.0 V LVCMOS", "Source-synchronous ADC capture clock on dedicated clock-capable input"), ("MAX_I0", "D10", "input", "max2771", "3.0 V LVCMOS", "ADC I bit 0"), ("MAX_I1", "D11", "input", "max2771", "3.0 V LVCMOS", "ADC I bit 1; clock-capable pad retained in same BR0 bank"), ("MAX_Q0", "E10", "input", "max2771", "3.0 V LVCMOS", "ADC Q bit 0; PLL-capable pad in same BR0 bank"), ("MAX_Q1", "E11", "input", "max2771", "3.0 V LVCMOS", "ADC Q bit 1; PLL-capable pad in same BR0 bank"), ("MAX_LD", "F11", "input", "max2771", "3.0 V LVCMOS", "PLL lock status"), ("MAX_SCLK", "F16", "output", "max2771", "3.0 V LVCMOS", "Three-wire serial clock"), ("MAX_CSN", "F17", "output", "max2771", "3.0 V LVCMOS", "Three-wire active-low chip select"), ("MAX_SDATA", "G16", "bidirectional", "max2771", "3.0 V LVCMOS", "Single bidirectional three-wire serial data; not split MOSI/MISO"), ("MAX_SHDN", "G17", "output", "max2771", "3.0 V LVCMOS", "MAX2771 hardware shutdown control"), ("IMU_SCK", "A8", "output", "sch16t", "3.3 V LVCMOS", "SafeSPI mode-0 clock"), ("IMU_MOSI", "A9", "output", "sch16t", "3.3 V LVCMOS", "SafeSPI controller-to-sensor data"), ("IMU_MISO", "B9", "input", "sch16t", "3.3 V LVCMOS", "SafeSPI sensor-to-controller data"), ("IMU_CS", "B10", "output", "sch16t", "3.3 V LVCMOS", "SafeSPI active-low chip select"), ("IMU_DRY_SYNC", "A11", "bidirectional", "sch16t", "3.3 V LVCMOS", "Murata multiplexed Data Ready output / SYNC input"), ("IMU_EXTRESN", "C10", "output", "sch16t", "3.3 V LVCMOS", "External active-low reset"), ("CLK_40M_FPGA", "U17", "input", "clock", "3.3 V LVCMOS", "40 MHz oscillator branch on clock-capable pin"), ("UART0_RX", "P17", "input", "ft4232", "3.3 V LVCMOS", "FT4232H channel C TXD to FPGA RX"), ("UART0_TX", "R17", "output", "ft4232", "3.3 V LVCMOS", "FPGA TX to FT4232H channel C RXD"), ("UART1_RX", "R18", "input", "ft4232", "3.3 V LVCMOS", "FT4232H channel D TXD to FPGA RX"), ("UART1_TX", "T19", "output", "ft4232", "3.3 V LVCMOS", "FPGA TX to FT4232H channel D RXD"), ("FTDI_DBG_GPIO", "U19", "bidirectional", "ft4232", "3.3 V LVCMOS", "Channel D debug GPIO/test point; PLL-capable"), ("PMOD1_1", "U6", "bidirectional", "pmod_3v3", "3.3 V LVCMOS", "PMOD 1 pin 1; plain HVIO GPIO"), ("PMOD1_2", "V4", "bidirectional", "pmod_3v3", "3.3 V LVCMOS", "PMOD 1 pin 2; plain HVIO GPIO"), ("PMOD1_3", "A13", "bidirectional", "pmod_tr1", "3.3 V LVCMOS", "PMOD 1 pin 3"), ("PMOD1_4", "C9", "bidirectional", "pmod_3v3", "3.3 V LVCMOS", "PMOD 1 pin 4; plain HVIO GPIO"), ("PMOD1_7", "C12", "bidirectional", "pmod_tr1", "3.3 V LVCMOS", "PMOD 1 pin 7"), ("PMOD1_8", "C13", "bidirectional", "pmod_tr1", "3.3 V LVCMOS", "PMOD 1 pin 8"), ("PMOD1_9", "C14", "bidirectional", "pmod_tr1", "3.3 V LVCMOS", "PMOD 1 pin 9"), ("PMOD1_10", "D12", "bidirectional", "pmod_tr1", "3.3 V LVCMOS", "PMOD 1 pin 10"), ("PMOD2_1", "V20", "bidirectional", "pmod_3v3", "3.3 V LVCMOS", "PMOD 2 pin 1; plain HVIO GPIO"), ("PMOD2_2", "D13", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 2"), ("PMOD2_3", "E13", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 3"), ("PMOD2_4", "E14", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 4"), ("PMOD2_7", "E16", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 7"), ("PMOD2_8", "F13", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 8"), ("PMOD2_9", "E15", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 9"), ("PMOD2_10", "F14", "bidirectional", "pmod_tr2", "3.3 V LVCMOS", "PMOD 2 pin 10"), # The two 1.8-V PMODs use plain HSIO pairs only. None of these pads has # a configuration, clock, PLL, or external-feedback alternate function. ("PMOD3_1", "C20", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 1; HSIO pair 51 P"), ("PMOD3_2", "B20", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 2; HSIO pair 51 N"), ("PMOD3_3", "C17", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 3; HSIO pair 52 P"), ("PMOD3_4", "B17", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 4; HSIO pair 52 N"), ("PMOD3_7", "A16", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 7; HSIO pair 53 P"), ("PMOD3_8", "A17", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 8; HSIO pair 53 N"), ("PMOD3_9", "C19", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 9; HSIO pair 54 P"), ("PMOD3_10", "B19", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 3 pin 10; HSIO pair 54 N"), ("PMOD4_1", "B14", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 1; HSIO pair 55 P"), ("PMOD4_2", "A14", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 2; HSIO pair 55 N"), ("PMOD4_3", "J18", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 3; HSIO pair 56 P"), ("PMOD4_4", "H18", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 4; HSIO pair 56 N"), ("PMOD4_7", "A18", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 7; HSIO pair 57 P"), ("PMOD4_8", "A19", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 8; HSIO pair 57 N"), ("PMOD4_9", "D16", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 9; HSIO pair 58 P"), ("PMOD4_10", "D17", "bidirectional", "pmod_2e", "1.8 V LVCMOS", "PMOD 4 pin 10; HSIO pair 58 N"), ] EXPECTED_HARD_NAMES = { "G10": "TCK", "G12": "TMS", "G9": "TDI", "G13": "TDO", "F9": "CRESET_N", "F8": "CDONE", "C8": "JTAG_VCCIO_SEL", } def load_package() -> tuple[list[dict[str, str]], dict[str, dict[str, str]]]: with PACKAGE_CSV.open(newline="") as f: rows = list(csv.DictReader(f)) assert len(rows) == 529, f"C529 package source has {len(rows)} rows, expected 529" by_ball = {r["ball"]: r for r in rows} assert len(by_ball) == 529 for ball, name in EXPECTED_HARD_NAMES.items(): assert by_ball[ball]["official_pad_name"] == name, (ball, by_ball[ball], name) return rows, by_ball def output_pin_tables(rows: list[dict[str, str]], by_ball: dict[str, dict[str, str]]) -> None: assigned: dict[str, dict[str, str]] = {} out_rows: list[dict[str, str]] = [] for signal, ball, direction, subsystem, standard, note in ASSIGNMENTS: assert ball not in assigned, f"duplicate assignment at {ball}" pkg = by_ball[ball] if "clock" in note.lower() and "clock-capable" in note.lower(): assert "CLOCK_CAPABLE" in pkg["special_capability"], (signal, ball, pkg) if standard.startswith("3.3"): assert "3.3" in pkg["allowed_vccio_v"], (signal, ball, pkg) if standard.startswith("3.0"): assert "3.0" in pkg["allowed_vccio_v"], (signal, ball, pkg) if standard.startswith("1.8"): assert "1.8" in pkg["allowed_vccio_v"], (signal, ball, pkg) item = { "signal": signal, "ball": ball, "official_pin_name": pkg["official_pad_name"], "die_bank": pkg["die_bank"], "package_bank": pkg["package_bank"], "vccio": standard.split()[0] + " V", "io_standard": standard, "direction": direction, "clock_capability": "yes" if "CLOCK_CAPABLE" in pkg["special_capability"] else "no", "special_function": pkg["special_capability"], "subsystem": subsystem, "connector": "on-board", "connector_pin": "", "note": note, } if subsystem.startswith("pmod_"): pmod_number = signal.removeprefix("PMOD").split("_", 1)[0] item["connector"] = f"JPMOD{pmod_number}" item["connector_pin"] = signal.rsplit("_", 1)[1] assigned[ball] = item out_rows.append(item) # Only the four PMODs are exposed. All other fabric GPIOs remain physically # unconnected, reducing connector count, board area, and escape-routing load. inventory: list[dict[str, str]] = [] for pkg in rows: if pkg["fabric_gpio"] != "yes": continue ball = pkg["ball"] if ball in assigned: a = assigned[ball] inventory.append({ "ball": ball, "official_pin_name": pkg["official_pad_name"], "package_bank": pkg["package_bank"], "bank_class": pkg["bank_class"], "selected_vccio": a["vccio"], "io_standard": a["io_standard"], "clock_capability": a["clock_capability"], "special_function": pkg["special_capability"], "status": "used", "signal": a["signal"], "connector": a["connector"], "connector_pin": a["connector_pin"], "note": a["note"], }) continue voltage = "3.3 V" if pkg["bank_class"] == "HVIO" else "1.8 V" standard = "3.3 V LVCMOS" if pkg["bank_class"] == "HVIO" else "1.8 V LVCMOS" inventory.append({ "ball": ball, "official_pin_name": pkg["official_pad_name"], "package_bank": pkg["package_bank"], "bank_class": pkg["bank_class"], "selected_vccio": voltage, "io_standard": standard, "clock_capability": "yes" if "CLOCK_CAPABLE" in pkg["special_capability"] else "no", "special_function": pkg["special_capability"], "status": "unused", "signal": "", "connector": "", "connector_pin": "", "note": "Unused fabric GPIO; left unconnected at the BGA", }) all_gpio = {r["ball"] for r in rows if r["fabric_gpio"] == "yes"} assert len(all_gpio) == 227 used_gpio = set(assigned) & all_gpio # Two former stand-alone auxiliary probes were deliberately released when # the connector-like test-point bank was removed. assert len(used_gpio) == 66 assert len(all_gpio - used_gpio) == 161 fieldnames = list(out_rows[0]) with (ROOT / "docs" / "ti375_pinout.csv").open("w", newline="") as f: w = csv.DictWriter(f, fieldnames=fieldnames) w.writeheader() w.writerows(sorted(out_rows, key=lambda r: r["ball"])) with (ROOT / "docs" / "unused_gpio_inventory.csv").open("w", newline="") as f: w = csv.DictWriter(f, fieldnames=list(inventory[0])) w.writeheader() w.writerows(sorted(inventory, key=lambda r: r["ball"])) with (ROOT / "docs" / "ti375_pin_constraints.isf").open("w") as f: f.write("# Efinity interface constraints generated from the official Ti375C529 database.\n") f.write("# Verify port names against the final RTL top level before importing.\n") for a in sorted(out_rows, key=lambda r: r["signal"]): f.write(f'set_pin_assignment -pin_name {a["ball"]} -port_name {a["signal"]}\n') def output_max2771_registers() -> None: defaults = { 0x0: 0xBEA41603, 0x1: 0x20550288, 0x2: 0x0EAFA1DC, 0x3: 0x698C0008, 0x4: 0x00C00080, 0x5: 0x08000070, 0x6: 0x08000000, 0x7: 0x010061B2, 0x8: 0x01E0F401, 0x9: 0x00C00002, 0xA: 0x010061B0, } regs = dict(defaults) # GPS L1/L1C wideband example from MAX2771 Rev 2 Applications Information. regs[0x0] = (regs[0x0] & ~(1 << 17)) | (1 << 17) # MIXPOLE=36 MHz regs[0x0] = (regs[0x0] & ~(0x3 << 15)) | (0x2 << 15) # both LNAs disabled regs[0x0] = (regs[0x0] & ~(0x3 << 13)) | (0x0 << 13) # high-band mixer regs[0x0] = (regs[0x0] & ~(0x7 << 3)) | (0x3 << 3) # 23.4 MHz LPF regs[0x0] &= ~(1 << 2) # 5th order regs[0x0] &= ~(1 << 1) # low-pass, not polyphase regs[0x3] |= 1 << 3 # integer-N PLL regs[0x4] = (regs[0x4] & ~((0x7FFF << 13) | (0x3FF << 3))) | (1540 << 13) | (16 << 3) regs[0x7] |= 1 << 28 # external ADC clock on ADC_CLKIN regs[0xA] |= 1 << 2 # CLKOUT outputs ADC sampling clock assert (regs[0] >> 22) & 0xFF == 0xFA # required reserved field assert (regs[9] >> 22) & 0x7 == 0x3 # required reserved field assert regs[4] == 0x00C08080 assert abs(16.368 / 16 * 1540 - 1575.42) < 1e-9 with (ROOT / "docs" / "max2771_registers.csv").open("w", newline="") as f: fieldnames = ["address", "value_hex", "default_hex", "changed", "purpose"] w = csv.DictWriter(f, fieldnames=fieldnames) w.writeheader() purposes = { 0: "MIXIN_HI, both LNAs disabled, 23.4 MHz 5th-order LPF, 36 MHz mixer pole", 3: "Integer-N L1 PLL", 4: "NDIV=1540, RDIV=16: 16.368 MHz / 16 * 1540 = 1575.42 MHz", 7: "External 40 MHz ADC sampling clock on ADC_CLKIN", 10: "Route ADC sampling clock to CLKOUT for source-synchronous FPGA capture", } for addr in range(11): w.writerow({ "address": f"0x{addr:X}", "value_hex": f"0x{regs[addr]:08X}", "default_hex": f"0x{defaults[addr]:08X}", "changed": "yes" if regs[addr] != defaults[addr] else "no", "purpose": purposes.get(addr, "Preserve MAX2771 documented reset/default value"), }) def main() -> None: rows, by_ball = load_package() output_pin_tables(rows, by_ball) output_max2771_registers() if __name__ == "__main__": main()