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>
This commit is contained in:
@@ -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)")
|
||||
Reference in New Issue
Block a user