"""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)")