#!/usr/bin/env python3 """EXI device-ID query over the Pi's HARDWARE SPI (spidev) — a near-full-speed (~25 MHz, close to the GameCube's real ~27 MHz EXI clock) companion to the bit-bang exi_devid_rpi.py. Use bit-bang (exi_devid_rpi.py) for a slow logic smoke test; use THIS to prove the FPGA's SPI Mode-3 capture front-end also samples cleanly *at rate* without a GameCube. The Pi 4 SPI SCK is core_clk/even-divisor, so a 27 MHz request lands on ~25 MHz — comfortably inside the design's 54 MHz 2x oversampling (and a hair more margin than the real 27 MHz). Do NOT push past ~27 MHz: the capture domain only closes ~49-58 MHz across seeds, so >~27 MHz input under-samples. The FPGA prefetches the reply during a clock-IDLE gap between the header and the data bytes (the GC pauses the clock there), so CS must stay LOW across that gap. spidev toggles CS per call, so we park SPI0's hardware CS on BCM7 (unused) and drive the real CS manually on BCM8. Pi config (then reboot): /boot/firmware/config.txt: dtoverlay=spi0-1cs,cs0_pin=7 (That frees BCM8 and puts the unused hw CE0 on BCM7. To go back to the bit-bang script, remove that line + reboot so BCM9/10/11 return to plain GPIO.) Wiring (Pi -> FPGA EXI, all 3.3 V, shared GND) — identical to the bit-bang one: SCLK BCM11 (pin 23) -> EXI CLK (FPGA 44) MOSI BCM10 (pin 19) -> EXI MOSI (FPGA 4) MISO BCM9 (pin 21) <- EXI MISO (FPGA 3) CS BCM8 (pin 24) -> EXI CS (FPGA 45) [held low manually across the txn] GND (pin 25) <-> GND Run: sudo python3 exi_devid_spi.py [speed_hz] [gap_us] """ import sys, time import spidev try: import RPi.GPIO as GPIO except ImportError: raise SystemExit("needs RPi.GPIO -> sudo apt install python3-rpi.gpio") SPEED = int(sys.argv[1]) if len(sys.argv) > 1 else 27_000_000 # ~25 MHz actual (core/10) GAP_US = int(sys.argv[2]) if len(sys.argv) > 2 else 50 # header->data prefetch gap CS = 8 # BCM8 manual chip-select (hardware CE0 parked on BCM7 by the overlay) spi = spidev.SpiDev() spi.open(0, 0) spi.mode = 0b11 # SPI Mode 3: CPOL=1, CPHA=1 (EXI CLK idles high) spi.max_speed_hz = SPEED GPIO.setmode(GPIO.BCM) GPIO.setup(CS, GPIO.OUT, initial=1) # CS idle high GPIO.output(CS, 0) # assert CS (active low) for the whole transaction spi.xfer2([0x00, 0x03]) # header: read, addr[12:0]=0, len-1=3 (4 bytes) time.sleep(GAP_US / 1e6) # clock-idle gap -> FPGA prefetches the reply resp = spi.xfer2([0x00] * 6) # clock read bytes; MISO carries the response GPIO.output(CS, 1) # deassert CS spi.close(); GPIO.cleanup() print(f"SPI mode 3, {SPEED/1e6:.1f} MHz requested (actual ~= core/even-divisor, ~25 MHz), " f"header->data gap {GAP_US} us") print("read bytes:", " ".join("%02x" % b for b in resp)) want = [0x04, 0x02, 0x02, 0x00] if any(resp[i:i + 4] == want for i in range(len(resp) - 3)): print("EXI device-ID 04 02 02 00 -> FOUND. Capture path works at ~full EXI rate!") else: print("device-ID not found. If the slow bit-bang test (exi_devid_rpi.py) PASSES " "but this fails, the front-end is marginal at rate on the flashed seed -> " "reflash the BEST --seeds bitstream, and/or raise the gap (arg2) or lower " "the speed (arg1). Otherwise check wiring/GND and that the FPGA is running.")