fix(w5100): revert buffer base to 0x4000/0x6000 — IDM decodes only 15 bits
Reverts the 0x8000/0xC000 change from162dbf7, which was WRONG and actively broke transmission. The W5100S datasheet pseudo-code quotes 0x8000 (TX) / 0xC000 (RX) for the memory blocks, but the PARALLEL/indirect (IDM) interface only decodes a 15-bit offset. Bench-proven with an address-aliasing probe: writing distinct values to each candidate base and reading them back showed 0xC000 and 0x4000 are the SAME cell (0xC000 & 0x7FFF == 0x4000) while 0x6000 holds its own value. Consequence of the bad base: every payload byte written to 0x9800+i aliased down onto 0x0000+i, landing on SHAR (0x09-0x0E) and SIPR (0x0F-0x12) and overwriting them with the payload. The chip then transmitted frames with a garbage source MAC/IP, which the peer NIC drops without counting -- the "SEND_OK but rx_packets=0" symptom, and the bogus SIPR0 readback (0x50 was literally payload byte 15, 'P'). The NETLCKR unlock from9c4bc7ais a genuine W5100S requirement and stays. Also: - w5100_udptest.py: self-contained configure+send+readback probe (and the address-aliasing probe that found this), reports over UART. - scripts/build-flash.sh: sweep N seeds and flash the best (capture timing is seed-dependent, so the sweep is mandatory). - scripts/uart-console.py: interactive UART console, plus --send/--read one-shot modes for scripting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,16 +68,17 @@ _S0_RX_RD = 0x0428 # Socket 0 RX read pointer
|
||||
# Per-socket register/buffer geometry (2 KB per socket, RMSR/TMSR=0x55).
|
||||
# The UDP-test socket number is configurable; addresses are computed from it in
|
||||
# __init__ (see _socket_addrs). Socket-n register block base = 0x0400+n*0x100,
|
||||
# TX buffer base = 0x8000+n*0x800, RX buffer base = 0xC000+n*0x800.
|
||||
# ⚠️ The TX/RX MEMORY blocks live at 0x8000 / 0xC000 on the **W5100S** (this
|
||||
# board's chip) — NOT 0x4000 / 0x6000 as on the original W5100. Getting this
|
||||
# wrong writes frames to dead address space: SEND then transmits the (empty)
|
||||
# real TX buffer, so the chip reports "sent" but nothing valid reaches the wire
|
||||
# (bench-confirmed at bring-up 2026-09-20 — rx_packets=0 on the peer). The sim
|
||||
# bus-model used these same constants so it never caught it; confirmed against
|
||||
# the W5100S datasheet (TX 0x8000-0xBFFF, RX 0xC000-0xFFFF).
|
||||
_TX_BASE = 0x8000 # Socket 0 TX buffer base (W5100S)
|
||||
_RX_BASE = 0xC000 # Socket 0 RX buffer base (W5100S)
|
||||
# TX buffer base = 0x4000+n*0x800, RX buffer base = 0x6000+n*0x800.
|
||||
# NOTE: the W5100S datasheet's pseudo-code quotes 0x8000/0xC000 for the TX/RX
|
||||
# memory blocks, but the PARALLEL/indirect (IDM) interface only decodes a 15-bit
|
||||
# offset (mask 0x7FFF) -- bench-proven 2026-09-20 by an address-aliasing probe:
|
||||
# 0xC000 and 0x4000 read back the SAME cell, while 0x6000 is distinct. So over
|
||||
# IDM the buffers live at the original W5100 addresses. Using 0x8000 makes
|
||||
# payload writes alias down onto 0x0000+, silently CLOBBERING SHAR/SIPR (frames
|
||||
# then go out with a zero source MAC/IP and get dropped). Do not "fix" these to
|
||||
# 0x8000/0xC000 again.
|
||||
_TX_BASE = 0x4000 # Socket 0 TX buffer base
|
||||
_RX_BASE = 0x6000 # Socket 0 RX buffer base
|
||||
_S0_TX_MASK = 0x07FF # 2 KB ring mask
|
||||
_S0_RX_MASK = 0x07FF
|
||||
_SN_MASK = 0x07FF # 2 KB ring mask (any socket)
|
||||
@@ -90,7 +91,7 @@ def _socket_addrs(n):
|
||||
MR=base + 0x00, CR=base + 0x01, IR=base + 0x02, SR=base + 0x03,
|
||||
PORT=base + 0x04, DIPR=base + 0x0C, DPORT=base + 0x10,
|
||||
TX_WR=base + 0x24, RX_RSR=base + 0x26, RX_RD=base + 0x28,
|
||||
TX_BASE=0x8000 + n * 0x0800, RX_BASE=0xC000 + n * 0x0800,
|
||||
TX_BASE=0x4000 + n * 0x0800, RX_BASE=0x6000 + n * 0x0800,
|
||||
)
|
||||
|
||||
# MR bits / command / mode values
|
||||
@@ -211,6 +212,14 @@ class W5100ParallelMaster(Elaboratable):
|
||||
# Init control
|
||||
self.init_req = Signal()
|
||||
self.init_done = Signal()
|
||||
|
||||
# Debug register peek (bring-up diagnostics): pulse dbg_rd_req with
|
||||
# dbg_rd_addr set; dbg_rd_data is valid when dbg_rd_done pulses. Reads
|
||||
# one byte over the indirect bus, reusing the fixed-length read path.
|
||||
self.dbg_rd_req = Signal()
|
||||
self.dbg_rd_addr = Signal(16)
|
||||
self.dbg_rd_data = Signal(8)
|
||||
self.dbg_rd_done = Signal()
|
||||
self.par = Signal(48) # MAC address (PAR0..5 packed)
|
||||
|
||||
# TX stream
|
||||
@@ -540,6 +549,7 @@ class W5100ParallelMaster(Elaboratable):
|
||||
# ── Main control FSM (Phase 1: init only) ────────────────────────────
|
||||
if self._enable_udp:
|
||||
m.d.sync += self.udp_rx_none.eq(0) # pulse: default low
|
||||
m.d.sync += self.dbg_rd_done.eq(0) # pulse: default low
|
||||
with m.FSM(domain="sync", name="main_fsm"):
|
||||
with m.State("IDLE"):
|
||||
m.d.sync += self.init_done.eq(0)
|
||||
@@ -556,6 +566,21 @@ class W5100ParallelMaster(Elaboratable):
|
||||
m.next = "UDP_DIPR"
|
||||
with m.Elif(self.udp_rx_req):
|
||||
m.next = "UDP_RX_RSR"
|
||||
with m.Elif(self.dbg_rd_req):
|
||||
m.next = "DBG_RD"
|
||||
|
||||
# Debug peek: read one byte from dbg_rd_addr over the indirect bus.
|
||||
with m.State("DBG_RD"):
|
||||
m.d.sync += [xfer_addr.eq(self.dbg_rd_addr), xfer_rw.eq(0),
|
||||
xfer_stream.eq(0), xfer_sread.eq(0), xfer_wrap.eq(0),
|
||||
xfer_direct.eq(0), xfer_len.eq(1), xfer_start.eq(1)]
|
||||
m.next = "DBG_RD_W"
|
||||
with m.State("DBG_RD_W"):
|
||||
m.d.sync += xfer_start.eq(0)
|
||||
with m.If(xfer_done):
|
||||
m.d.sync += [self.dbg_rd_data.eq(rbuf[0]),
|
||||
self.dbg_rd_done.eq(1)]
|
||||
m.next = "IDLE"
|
||||
|
||||
# MR = 0x80 software reset (direct A=00), then settle.
|
||||
write_reg("MR_RST", _MR, [_MR_RST], "MR_WAIT", direct=True)
|
||||
|
||||
Reference in New Issue
Block a user