EXI bring-up: full Swiss init runs on hardware; W5100 TX blocked

The whole GC init sequence now runs over EXI on the real board: device ID,
NCRA reset self-clear, PAR0-5, MAR, ANALOG, NWAYC, IMR, NCRA SR, NWAYS 0x17.

GC->wire TX does not complete.  Not an EXI fault and not tx_bytes depth --
8-byte frames fail too, and the UART shell's own UDP send hangs the same way,
so the W5100 bus master is stuck and TXFrameDrain is waiting on it.

Also records that id_phase is a one-shot cleared by the first NCRA reset, so
a re-run probe reads 00 00 00 00 for the device ID and looks broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 18:42:16 +00:00
parent a082e15c4f
commit 2b210dc5e3
2 changed files with 154 additions and 0 deletions
+24
View File
@@ -272,6 +272,30 @@ device and exercised the register file end-to-end:
| IMR @0x08 write `0x86` + readback | `0x86` | | IMR @0x08 write `0x86` + readback | `0x86` |
| NCRA @0x00 write `0x01` (reset) | self-cleared to `0x00` on the first poll | | NCRA @0x00 write `0x01` (reset) | self-cleared to `0x00` on the first poll |
**The full Swiss init sequence runs on hardware (2026-09-30)**, driven from the
Pi over J4 with the board on 12 V — device ID `04 02 02 00`, NCRA reset
self-clearing, PAR0–5, MAR0–7, ANALOG, NWAYC, IMR `0x86` readback, NCRA SR,
and NWAYS `0x17`. See `hardware/re-bba-rb/exi_fullpath_rpi.py`.
⚠️ **OPEN: GC→wire TX does not complete, and the W5100 is unresponsive.**
Setting the NCRA ST bits never produces TI, and NCRA keeps reading back `0x0a`
(ST never clears), for EVERY frame length 8…60 with a full NCRA reset between
attempts. The cause is NOT the EXI side and NOT `tx_bytes` depth (8-byte frames
fail too): the **UART shell's own socket-3 `udp unicast` also hangs** — it
echoes the command and never prints `sent` — so the W5100 bus master is stuck
and `TXFrameDrain` is simply waiting on it. Check the ETH_3V3 rail / GC_ON
(U12 gates the W5100 rail off GC_ON) before suspecting gateware. Note an
earlier contaminated run appeared to transmit at one specific length; that was
leftover state from a previous failed attempt feeding a still-pending drain
request — always NCRA-reset between TX attempts when probing.
**`id_phase` is a ONE-SHOT.** The device ID is returned for reads of address 0
only until the first NCRA write with the reset bit, which clears `id_phase`
permanently (`bba_register_file.py:330`); after that address 0 reads back NCRA.
This is correct — it matches the GC's init order — but it means a re-run of any
probe that already issued an NCRA reset will read `00 00 00 00` for the device
ID and look broken. Reconfigure the FPGA (`iceprog -t` is enough) to restore it.
Two bring-up gotchas, both cost real debugging time: Two bring-up gotchas, both cost real debugging time:
- **The master must sample MISO during the CLK-LOW phase.** The FPGA drives MISO - **The master must sample MISO during the CLK-LOW phase.** The FPGA drives MISO
on the RISING edge (Mode 3), so sampling just after that edge races the on the RISING edge (Mode 3), so sampling just after that edge races the
+130
View File
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
"""Full BBA path test: Swiss init, GC->wire TX, wire->GC RX.
Sends a broadcast ARP request ("who has 10.0.0.50, tell 10.0.0.99"). The PC
should answer, which exercises the RX half in the same shot.
"""
import time, sys
import RPi.GPIO as GPIO
CLK, MOSI, MISO, CS = 11, 10, 9, 8
GPIO.setmode(GPIO.BCM); GPIO.setwarnings(False)
GPIO.setup(CLK, GPIO.OUT, initial=1); GPIO.setup(MOSI, GPIO.OUT, initial=0)
GPIO.setup(CS, GPIO.OUT, initial=1); GPIO.setup(MISO, GPIO.IN)
def xfer(b):
r = 0
for i in range(7, -1, -1):
GPIO.output(MOSI, (b >> i) & 1)
GPIO.output(CLK, 0)
r = (r << 1) | GPIO.input(MISO)
GPIO.output(CLK, 1)
return r
def read(addr, n):
GPIO.output(CS, 0); GPIO.output(CLK, 1); time.sleep(0.0003)
xfer((addr >> 6) & 0x7F); xfer(((addr & 0x3F) << 2) | ((n - 1) & 3))
time.sleep(0.002)
out = [xfer(0) for _ in range(n)] # EXACTLY n
GPIO.output(CS, 1); time.sleep(0.0005)
return out
def write(addr, data):
GPIO.output(CS, 0); GPIO.output(CLK, 1); time.sleep(0.0003)
xfer(0x80 | ((addr >> 6) & 0x7F))
xfer(((addr & 0x3F) << 2) | ((len(data) - 1) & 3))
for b in data: xfer(b)
GPIO.output(CS, 1); time.sleep(0.0005)
def stream(addr, n):
"""SPRAM/DMA read: addr >= 0x100 streams until CS, so clock freely."""
GPIO.output(CS, 0); GPIO.output(CLK, 1); time.sleep(0.0003)
xfer((addr >> 6) & 0x7F); xfer(((addr & 0x3F) << 2) | 3)
time.sleep(0.002)
out = [xfer(0) for _ in range(n)]
GPIO.output(CS, 1); time.sleep(0.0005)
return out
def hx(v): return " ".join("%02x" % x for x in v)
MAC = [0x00, 0x09, 0xBF, 0x00, 0x00, 0x01]
SRC_IP, DST_IP = [10, 0, 0, 99], [10, 0, 0, 50]
print("=" * 62)
print("PHASE 1 Swiss init sequence")
print("=" * 62)
dev = read(0x0000, 4)
print(f" 1. device ID {hx(dev)} {'OK' if dev==[4,2,2,0] else 'FAIL'}")
if dev != [4, 2, 2, 0]:
print(" (ID is a one-shot before the first NCRA reset - continuing)")
write(0x00, [0x01]); print(" 2. NCRA = 0x01 (reset + W5100 init)")
for i in range(40):
v = read(0x00, 1)[0]
if not (v & 1): print(f" 3. NCRA reset self-cleared after {i} polls"); break
time.sleep(0.005)
else:
print(" 3. NCRA reset did NOT clear")
write(0x20, MAC[:4]); write(0x24, MAC[4:])
print(f" 4. PAR0-5 = {hx(read(0x20,4))} {hx(read(0x24,2))}")
write(0x26, [0xFF]*4); write(0x2A, [0xFF]*4); print(" 5. MAR0-7 = FF (promiscuous multicast)")
write(0x2E, [0xD6]); print(" 6. ANALOG = 0xD6")
write(0x30, [0x01]); print(" 7. NWAYC")
write(0x08, [0x86]); print(f" 8. IMR = 0x86, readback {hx(read(0x08,1))}")
write(0x32, [0x02]); print(" 9. GCA")
write(0x00, [0x08]); print(f" 10. NCRA SR=1, readback {hx(read(0x00,1))}")
nw = read(0x31, 1); print(f" 11. NWAYS = {hx(nw)} {'link up' if nw==[0x17] else 'UNEXPECTED'}")
# ---------------- build the ARP frame ----------------
frame = [0xFF]*6 + MAC + [0x08, 0x06]
frame += [0x00,0x01, 0x08,0x00, 0x06, 0x04, 0x00,0x01]
frame += MAC + SRC_IP + [0x00]*6 + DST_IP
frame += [0x00] * (60 - len(frame))
print()
print("=" * 62)
print(f"PHASE 2 GC -> wire: {len(frame)}-byte broadcast ARP")
print("=" * 62)
print(f" who has {'.'.join(map(str,DST_IP))}, tell {'.'.join(map(str,SRC_IP))}")
print(f" src MAC {':'.join('%02x'%b for b in MAC)}")
rwp_before = read(0x16, 2)
write(0x34, [len(frame) & 0xFF]); write(0x35, [(len(frame) >> 8) & 0xFF])
print(f" TWD = {hx(read(0x34,2))} (frame length)")
t0 = time.time()
for b in frame:
write(0x48, [b]) # one byte per transaction: eff_addr increments
print(f" streamed {len(frame)} bytes to TXDATA in {time.time()-t0:.2f}s")
write(0x00, [0x08 | 0x02]) # SR + ST0 -> TX trigger
print(" NCRA ST set -> TX triggered")
for i in range(30):
ir = read(0x09, 1)[0]
if ir & 0x04:
print(f" IR = {ir:02x} TI (transmit complete) SET after {i} polls"); break
time.sleep(0.01)
else:
print(f" IR = {read(0x09,1)[0]:02x} TI never set")
print()
print("=" * 62)
print("PHASE 3 wire -> GC: waiting for the ARP reply")
print("=" * 62)
print(f" RWP before = {hx(rwp_before)}")
got = None
for i in range(60):
rwp = read(0x16, 2)
ir = read(0x09, 1)[0]
if rwp != rwp_before or (ir & 0x02):
got = (rwp, ir); break
time.sleep(0.05)
if got:
rwp, ir = got
print(f" RWP moved to {hx(rwp)}, IR = {ir:02x} (RI {'set' if ir&2 else 'clear'})")
data = stream(0x0100, 80)
print(f" SPRAM @0x100: {hx(data[:16])}")
print(f" {hx(data[16:32])}")
print(f" {hx(data[32:48])}")
if 0x08 in data and 0x06 in data:
print(" -> looks like an ARP frame came back. RX PATH WORKS.")
else:
print(f" no RX after 3s (RWP still {hx(read(0x16,2))}, IR {read(0x09,1)[0]:02x})")
print(" TX may still have worked - check the capture.")
GPIO.cleanup()