diff --git a/scripts/pi-exi-wirecheck.py b/scripts/pi-exi-wirecheck.py new file mode 100755 index 0000000..708cc77 --- /dev/null +++ b/scripts/pi-exi-wirecheck.py @@ -0,0 +1,156 @@ +#!/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)