28b7665b33
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>
157 lines
6.2 KiB
Python
Executable File
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)
|