Files
rebbarb/scripts/pi-exi-wirecheck.py
T
Roflin 28b7665b33 scripts: Pi<->J4 EXI wiring checker
Verifies the harness before attempting a real EXI transfer.  Keyed on the
EXI_J4 build's SB_IO pull-ups: an FPGA input with PULLUP=1 reads HIGH when
connected, follows the Pi's pull when open, and reads a hard 0 only when
shorted -- so all three cases are distinguishable from the Pi alone.

Includes a T0 control on known-unwired pins; without it a broken pull
mechanism would turn every 'connected' verdict into a false pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 18:18:34 +00:00

157 lines
6.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""Verify the Pi <-> re-bba-rb J4 wiring BEFORE attempting a real EXI transfer.
Requires the FPGA to be running an EXI_J4=1 bitstream, which mirrors EXI onto
the J4 header and enables SB_IO pull-ups on the three EXI *inputs*.
J4.1 GND <-> Pi pin 25 GND
J4.2 DBG0 <-- Pi pin 23 BCM11 EXI CLK (FPGA input)
J4.3 DBG1 <-- Pi pin 19 BCM10 EXI MOSI (FPGA input)
J4.4 DBG2 --> Pi pin 21 BCM9 EXI MISO (FPGA OUTPUT - never drive)
J4.5 DBG3 <-- Pi pin 24 BCM8 EXI CS_N (FPGA input)
J4.6 DBG4 --> Pi pin 22 BCM25 EXI INT_N (FPGA OUTPUT - never drive)
Run: sudo python3 exi_wirecheck.py
"""
import sys, time
import RPi.GPIO as GPIO
CLK, MOSI, MISO, CS_N, INT_N = 11, 10, 9, 8, 25
HDR = {11: "hdr23", 10: "hdr19", 9: "hdr21", 8: "hdr24", 25: "hdr22"}
DRIVEN_BY_PI = [(CLK, "CLK", "J4.2"), (MOSI, "MOSI", "J4.3"), (CS_N, "CS_N", "J4.5")]
DRIVEN_BY_FPGA= [(MISO, "MISO", "J4.4"), (INT_N, "INT_N", "J4.6")]
fails, warns = [], []
def rd(pin, pull):
GPIO.setup(pin, GPIO.IN, pull_up_down=pull)
time.sleep(0.002)
return GPIO.input(pin)
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
for p, _, _ in DRIVEN_BY_PI + DRIVEN_BY_FPGA:
GPIO.setup(p, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
print("=" * 66)
print("T0 CONTROL: pins known to be unwired MUST follow the internal pull.")
print(" Without this, a broken pull mechanism would silently turn every")
print(" T1 verdict into a false \'CONNECTED\'.")
print("=" * 66)
for pin, hdr in ((26, "hdr37"), (20, "hdr38"), (16, "hdr36"), (21, "hdr40")):
u, d = rd(pin, GPIO.PUD_UP), rd(pin, GPIO.PUD_DOWN)
ok = (u, d) == (1, 0)
print(f" BCM{pin:<2} {hdr}: pull-up={u} pull-down={d} "
f"{'pull works' if ok else '** PULL NOT WORKING - T1/T2 INVALID **'}")
if not ok:
fails.append(f"control pin BCM{pin} did not follow the pull; results below are meaningless")
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
print()
print("=" * 66)
print("T1 FPGA-driven pins: value must NOT follow the Pi's internal pull.")
print(" An active driver (~ohms) beats the Pi's ~50k pull; a dead wire")
print(" follows it. This test is decisive.")
print("=" * 66)
for pin, name, j4 in DRIVEN_BY_FPGA:
up, dn = rd(pin, GPIO.PUD_UP), rd(pin, GPIO.PUD_DOWN)
if up == dn:
print(f" {name:<5} {j4} BCM{pin:<2} {HDR[pin]}: pull-up={up} pull-down={dn}"
f" -> CONNECTED, FPGA driving {up}")
else:
print(f" {name:<5} {j4} BCM{pin:<2} {HDR[pin]}: pull-up={up} pull-down={dn}"
f" -> ** NOT CONNECTED ** (follows the Pi's pull)")
fails.append(f"{name} ({j4} -> BCM{pin}/{HDR[pin]}) is open")
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
print()
print("=" * 66)
print("T2 FPGA-input pins: these carry SB_IO PULLUP=1 in an EXI_J4 build, so")
print(" a correct connection reads HIGH regardless of the Pi's pull.")
print(" Follows-the-pull = open. Hard low = shorted. Decisive either way.")
print("=" * 66)
for pin, name, j4 in DRIVEN_BY_PI:
u, d = rd(pin, GPIO.PUD_UP), rd(pin, GPIO.PUD_DOWN)
if (u, d) == (1, 1):
print(f" {name:<5} {j4} BCM{pin:<2} {HDR[pin]}: up=1 down=1 -> held HIGH: FPGA pull-up seen, CONNECTED")
elif (u, d) == (1, 0):
print(f" {name:<5} {j4} BCM{pin:<2} {HDR[pin]}: up=1 down=0 -> follows the Pi pull: ** OPEN **")
fails.append(f"{name} ({j4} -> BCM{pin}/{HDR[pin]}) is open - no connection")
else:
print(f" {name:<5} {j4} BCM{pin:<2} {HDR[pin]}: up={u} down={d} -> hard LOW: ** SHORTED TO GND **")
fails.append(f"{name} ({j4} -> BCM{pin}/{HDR[pin]}) is shorted low - an FPGA input with "
f"PULLUP=1 cannot read a hard 0")
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
print()
print("=" * 66)
print("T3 Bridge scan on the Pi-driven lines (catches solder bridges on J4).")
print(" Drive one HIGH, hold the others as inputs with pull-down: any")
print(" victim reading HIGH shares copper with the aggressor.")
print("=" * 66)
for pin, name, j4 in DRIVEN_BY_PI:
victims = [(p, n) for p, n, _ in DRIVEN_BY_PI if p != pin]
for vp, _ in victims:
GPIO.setup(vp, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, 1)
time.sleep(0.003)
bad = [n for vp, n in victims if GPIO.input(vp) == 1]
GPIO.output(pin, 0)
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
if bad:
print(f" drive {name:<5} HIGH -> ** BRIDGED to {', '.join(bad)} **")
fails.append(f"{name} is bridged to {', '.join(bad)}")
else:
print(f" drive {name:<5} HIGH -> no bridge to {', '.join(n for _, n in victims)}")
print()
print("=" * 66)
print("T4 Liveness: assert CS_N, clock slowly, see if MISO ever moves.")
print(" Proves CLK + CS_N reach the FPGA and MISO comes back, without")
print(" depending on a correct transaction.")
print("=" * 66)
for p in (CLK, CS_N, MOSI):
GPIO.setup(p, GPIO.OUT)
GPIO.output(CLK, 1) # Mode 3: CLK idles HIGH
GPIO.output(MOSI, 0)
GPIO.output(CS_N, 1)
GPIO.setup(MISO, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
time.sleep(0.01)
idle = GPIO.input(MISO)
GPIO.output(CS_N, 0) # assert
time.sleep(0.005)
seen = {GPIO.input(MISO)}
for _ in range(32):
GPIO.output(CLK, 0); time.sleep(0.001)
seen.add(GPIO.input(MISO))
GPIO.output(CLK, 1); time.sleep(0.001)
seen.add(GPIO.input(MISO))
GPIO.output(CS_N, 1)
GPIO.output(CLK, 1)
print(f" MISO idle (CS_N high) = {idle}; levels seen while clocking = {sorted(seen)}")
if len(seen) > 1:
print(" -> MISO CHANGES while clocking: the FPGA is responding. Chain alive.")
else:
print(" -> MISO never changed. Either the FPGA isn't seeing CLK/CS_N, or")
print(" it has nothing to send yet. Not conclusive on its own; the")
print(" device-ID read is the real test.")
warns.append("MISO static while clocking")
GPIO.cleanup()
print()
print("=" * 66)
if fails:
print("WIRING FAULTS:")
for f in fails:
print(" -", f)
if warns:
print("ADVISORY:")
for w in warns:
print(" -", w)
if not fails:
print("No hard wiring faults found." if not warns else
"No hard wiring faults; see advisories above.")
print("Next: sudo python3 ~/exi_devid_rpi.py (expect 04 02 02 00)")
sys.exit(1 if fails else 0)