Files
Roflin 6621ce9d0d Both directions proven on hardware: 60-byte ARP out, DHCP frame in
TX: the TWD fix lands a full 60-byte ARP on the wire, byte-for-byte correct
(previously truncated at 16).  TI sets.

RX: a real 342-byte DHCP broadcast from the PC was received in MACRAW mode,
assembled into the SPRAM ring and DMA-read back over EXI.  The descriptor
length 0x015A cross-checks exactly against the frame's own IP total-length
field plus the ethernet header, so the assembler's length is confirmed, not
merely plausible.

Also replaces the full-path probe's 'is 08 and 06 somewhere in the buffer'
guess with a real descriptor decode -- it called a DHCP frame an ARP reply.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 18:59:26 +00:00

138 lines
5.2 KiB
Python

#!/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])}")
# Decode the ring descriptor instead of guessing from stray bytes: the
# first 4 bytes are the descriptor, whose length INCLUDES itself.
dlen = (data[2] << 8) | data[3]
etype = (data[16] << 8) | data[17]
print(f" descriptor length 0x{dlen:04X} = {dlen} -> {dlen-4} frame bytes")
print(f" dst {':'.join('%02x'%b for b in data[4:10])}"
f" src {':'.join('%02x'%b for b in data[10:16])} ethertype 0x{etype:04X}")
if 4 < dlen <= 1518:
print(" -> RX PATH WORKS: a real frame was assembled into the SPRAM ring.")
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()