#!/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)