Files
Roflin 5d9880f9c5 Add W5100 read-only register-dump probe (bring-up)
Reads socket-3 / status registers over the indirect bus and streams them on the
UART, without resetting the chip, to inspect what a UDP send left behind. First
hardware run was inconclusive (socket regs read 0x00 but SIPR/PHYSR didn't match
either "state persisted" or "clean reset") -- the FPGA reflash appears to glitch
RST_N, so a self-contained configure+send+readback is the reliable next step.
Committed as a starting point for that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-20 15:03:44 +00:00

203 lines
9.0 KiB
Python

"""Read-only W5100 register dump over UART — post-mortem for a UDP send.
Flash the normal BBA build, fire a `udp broadcast` from the shell, THEN flash
this: it does NOT reset the W5100 (RST_N is held high by R36's pull-up across the
FPGA reconfig, so the chip keeps the state the BBA master left), and just READS a
handful of socket-3 / status registers over the indirect bus and streams them on
the UART (115200 8N1). That shows what the master actually did:
SIP=C0 -> SIPR0 = 192 => NETLCKR unlock worked, source IP took
SMR=02 -> Sn_MR = UDP
SSR=22 -> Sn_SR = SOCK_UDP (socket really opened)
SIR=10 -> Sn_IR bit4 SEND_OK set (send completed)
TRD/TWR -> if TWR advanced past TRD a frame was queued; equal+nonzero = sent
PHY -> PHYSR0 (link/speed)
If SIP!=C0 the network config never took; if SSR!=22 the socket never opened; if
SIR has no SEND_OK the send never finished; if TWR==TRD==0 nothing was queued.
Build/flash: python -m exi_bba.w5100_regdump --flash (see W5100SelfTest for the
platform wiring pattern this reuses).
"""
from amaranth import *
# indirect-mode A[1:0] selects
_A_MR, _A_AR0, _A_AR1, _A_DR = 0b00, 0b01, 0b10, 0b11
_MR_IND, _MR_AI = 0x01, 0x02
# registers to read (16-bit indirect addresses), in report order
_REGS = [
("SIP", 0x000F), # SIPR0 (192 if unlock+config worked)
("SMR", 0x0700), # Sn_MR (socket 3)
("SSR", 0x0703), # Sn_SR
("SIR", 0x0702), # Sn_IR
("TRh", 0x0722), # Sn_TX_RD hi
("TRl", 0x0723), # Sn_TX_RD lo
("TWh", 0x0724), # Sn_TX_WR hi
("TWl", 0x0725), # Sn_TX_WR lo
("PHY", 0x003C), # PHYSR0
]
class W5100RegDump(Elaboratable):
def __init__(self, *, strobe_cycles=3, blink_cycles=3_000_000):
self._strobe = strobe_cycles
self._blink = blink_cycles
self.bus_addr = Signal(2)
self.bus_data_o = Signal(8); self.bus_data_oe = Signal()
self.bus_data_i = Signal(8)
self.cs_n = Signal(init=1); self.rd_n = Signal(init=1)
self.wr_n = Signal(init=1); self.rst_n = Signal(init=1) # rst_n stays HIGH
self.uart_tx = Signal(init=1)
self.vals = Array([Signal(8, name=f"v{i}") for i in range(len(_REGS))])
self.done = Signal()
def elaborate(self, platform):
m = Module()
STROBE = self._strobe
# ── bus engine (same as w5100_selftest) ──────────────────────────────
bus_go=Signal(); bus_rw=Signal(); bus_a=Signal(2); bus_wd=Signal(8)
bus_rd=Signal(8); bus_done=Signal(); bus_ctr=Signal(range(STROBE+2)); rw_r=Signal()
a_o=Signal(2); d_o=Signal(8); d_oe=Signal()
cs_r=Signal(init=1); rd_r=Signal(init=1); wr_r=Signal(init=1)
m.d.comb += [self.bus_addr.eq(a_o), self.bus_data_o.eq(d_o), self.bus_data_oe.eq(d_oe),
self.cs_n.eq(cs_r), self.rd_n.eq(rd_r), self.wr_n.eq(wr_r),
self.rst_n.eq(1)] # NEVER reset the chip
m.d.sync += bus_done.eq(0)
with m.FSM(domain="sync", name="bus_fsm"):
with m.State("IDLE"):
m.d.sync += [cs_r.eq(1), rd_r.eq(1), wr_r.eq(1), d_oe.eq(0)]
with m.If(bus_go):
m.d.sync += [a_o.eq(bus_a), rw_r.eq(bus_rw), cs_r.eq(0), bus_ctr.eq(0)]
with m.If(bus_rw):
m.d.sync += [d_o.eq(bus_wd), d_oe.eq(1), wr_r.eq(0)]
with m.Else():
m.d.sync += rd_r.eq(0)
m.next = "STROBE"
with m.State("STROBE"):
m.d.sync += bus_ctr.eq(bus_ctr+1)
with m.If(bus_ctr == STROBE-1):
with m.If(~rw_r):
m.d.sync += bus_rd.eq(self.bus_data_i)
m.d.sync += [rd_r.eq(1), wr_r.eq(1)]
m.next = "FINISH"
with m.State("FINISH"):
m.d.sync += [cs_r.eq(1), d_oe.eq(0), bus_done.eq(1)]
m.next = "IDLE"
m.d.comb += [bus_go.eq(0), bus_rw.eq(0), bus_a.eq(0), bus_wd.eq(0)]
def bwrite(a, d): m.d.comb += [bus_go.eq(1), bus_rw.eq(1), bus_a.eq(a), bus_wd.eq(d)]
def bread(a): m.d.comb += [bus_go.eq(1), bus_rw.eq(0), bus_a.eq(a)]
addrs = Array([Const(a, 16) for _, a in _REGS])
idx = Signal(range(len(_REGS)+1))
# ── read each register via indirect mode (chip already in IND from the
# master, but set MR=IND|AI once to be safe) ─────────────────────────
with m.FSM(domain="sync", name="dump_fsm"):
with m.State("SETMR"):
bwrite(_A_MR, _MR_IND | _MR_AI)
m.next = "SETMR_W"
with m.State("SETMR_W"):
with m.If(bus_done): m.d.sync += idx.eq(0); m.next = "AR0"
with m.State("AR0"):
bwrite(_A_AR0, addrs[idx][8:16])
m.next = "AR0_W"
with m.State("AR0_W"):
with m.If(bus_done): m.next = "AR1"
with m.State("AR1"):
bwrite(_A_AR1, addrs[idx][0:8])
m.next = "AR1_W"
with m.State("AR1_W"):
with m.If(bus_done): m.next = "RDR"
with m.State("RDR"):
bread(_A_DR)
m.next = "RDR_W"
with m.State("RDR_W"):
with m.If(bus_done):
m.d.sync += self.vals[idx].eq(bus_rd)
with m.If(idx == len(_REGS)-1):
m.next = "DONE"
with m.Else():
m.d.sync += idx.eq(idx+1); m.next = "AR0"
with m.State("DONE"):
m.d.comb += self.done.eq(1)
# ── UART report: "REG SIP=XX SMR=XX SSR=XX SIR=XX TR=XXXX TW=XXXX PHY=XX" ─
DIV = round(24_000_000 / 115_200)
# build "R:" + name=val pairs
parts = b"R:"
pos = {}
for name, _ in _REGS:
parts += b" " + name.encode()[:3] + b"="
pos[name] = len(parts) # index of the (first) hex digit
parts += b"00"
parts += b"\r\n"
tmpl = list(parts); MSGLEN = len(tmpl)
rom = Array([Const(b, 8) for b in tmpl])
def hexch(nib): return Mux(nib < 10, 0x30+nib, 0x37+nib)
cur = Signal(8); uidx = Signal(range(MSGLEN+1))
m.d.comb += cur.eq(rom[uidx])
with m.Switch(uidx):
for i, (name, _) in enumerate(_REGS):
p = pos[name]
with m.Case(p): m.d.comb += cur.eq(hexch(self.vals[i][4:8]))
with m.Case(p+1): m.d.comb += cur.eq(hexch(self.vals[i][0:4]))
shift=Signal(10, init=0x3FF); nbits=Signal(range(11)); baud=Signal(range(DIV))
gap=Signal(range(DIV*30+1))
m.d.comb += self.uart_tx.eq(shift[0])
with m.FSM(domain="sync", name="uart_fsm"):
with m.State("IDLE"):
with m.If(self.done): m.d.sync += uidx.eq(0); m.next="LOAD"
with m.State("LOAD"):
with m.If(uidx == MSGLEN):
m.d.sync += gap.eq(DIV*30); m.next="GAP"
with m.Else():
m.d.sync += [shift.eq(Cat(C(0,1), cur, C(1,1))), nbits.eq(10), baud.eq(DIV-1)]
m.next="SHIFT"
with m.State("SHIFT"):
with m.If(baud==0):
m.d.sync += baud.eq(DIV-1)
with m.If(nbits==1):
m.d.sync += uidx.eq(uidx+1); m.next="LOAD"
with m.Else():
m.d.sync += [nbits.eq(nbits-1), shift.eq(Cat(shift[1:], C(1,1)))]
with m.Else():
m.d.sync += baud.eq(baud-1)
with m.State("GAP"):
m.d.sync += gap.eq(gap-1)
with m.If(gap==0): m.d.sync += uidx.eq(0); m.next="LOAD"
return m
class W5100RegDumpTop(Elaboratable):
def elaborate(self, platform):
m = Module()
m.domains += ClockDomain("sync")
m.submodules.hfosc = Instance("SB_HFOSC", p_CLKHF_DIV="0b01",
i_CLKHFEN=Const(1,1), i_CLKHFPU=Const(1,1), o_CLKHF=ClockSignal("sync"))
m.submodules.dut = dut = W5100RegDump()
w5100 = platform.request("w5100", 0)
uart = platform.request("uart", 0)
m.d.comb += [
w5100.addr.o.eq(dut.bus_addr), w5100.data.o.eq(dut.bus_data_o),
w5100.data.oe.eq(dut.bus_data_oe), dut.bus_data_i.eq(w5100.data.i),
w5100.cs_n.o.eq(dut.cs_n), w5100.rd_n.o.eq(dut.rd_n),
w5100.wr_n.o.eq(dut.wr_n), w5100.rst_n.o.eq(dut.rst_n),
uart.tx.o.eq(dut.uart_tx),
]
return m
if __name__ == "__main__":
import sys
if "--build" in sys.argv or "--flash" in sys.argv:
from exi_bba.synth import IceBreakerPlatform
IceBreakerPlatform().build(W5100RegDumpTop(), do_program="--flash" in sys.argv,
name="w5100_regdump", build_dir="build_w5100test")
print("[built] build_w5100test/w5100_regdump.bin")
raise SystemExit(0)
print("use --build or --flash (this module is a hardware read-only probe)")