From 5d9880f9c5cfe4e5574eb3546746344cc3041f74 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sun, 20 Sep 2026 15:03:44 +0000 Subject: [PATCH] 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 --- exi_bba/w5100_regdump.py | 202 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 exi_bba/w5100_regdump.py diff --git a/exi_bba/w5100_regdump.py b/exi_bba/w5100_regdump.py new file mode 100644 index 0000000..cd98458 --- /dev/null +++ b/exi_bba/w5100_regdump.py @@ -0,0 +1,202 @@ +"""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)")