fix(w5100): actually drive RSTn on init so a wedged chip can recover

w5100_rst_n was declared and never driven anywhere -- the pin sat at its init
value of 1 forever.  The only reset the chip ever got was the MR software
reset, which arrives over the very bus a wedged chip has stopped answering, so
a wedged W5100 could not be recovered at all: not by NCRA reset, not by
reflashing, not even by reconfiguring the FPGA (RSTn just floats to its R36
pull-up while unconfigured).  Only a 12 V power cycle worked.

Init now pulses RSTn low (1 ms, datasheet minimum is ~2 us) and waits the full
post-reset settle before touching the bus.  RSTn low time is clamped to
reset_cycles so the testbenches' small values stay reachable by wait_ctr.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 19:09:25 +00:00
parent b42dc65796
commit e80b7485d5
+36 -1
View File
@@ -50,6 +50,10 @@ _SUBR0 = 0x0005 # Subnet mask, 4 bytes
_SHAR0 = 0x0009 # Source MAC, 6 bytes _SHAR0 = 0x0009 # Source MAC, 6 bytes
_SIPR0 = 0x000F # Source IP, 4 bytes _SIPR0 = 0x000F # Source IP, 4 bytes
_NETLCKR = 0x0071 # W5100S Network Lock Register (WO) — GWR/SUBR/SHAR/SIPR are _NETLCKR = 0x0071 # W5100S Network Lock Register (WO) — GWR/SUBR/SHAR/SIPR are
# Default RSTn low time before release (sync cycles). 24_000 @ 24 MHz = 1 ms;
# the W5100S datasheet minimum is ~2 us, so this is generous on purpose.
_RST_LOW_DEFAULT = 24_000
_NETLCK_UNLOCK = 0x3A # writable ONLY while NETLCKR==0x3A (default 0x00 = locked) _NETLCK_UNLOCK = 0x3A # writable ONLY while NETLCKR==0x3A (default 0x00 = locked)
_IR = 0x0015 # Interrupt register _IR = 0x0015 # Interrupt register
_IMR = 0x0016 # Interrupt mask _IMR = 0x0016 # Interrupt mask
@@ -150,6 +154,10 @@ class W5100ParallelMaster(Elaboratable):
self._strobe = strobe_cycles self._strobe = strobe_cycles
# MR-reset settle wait; testbench overrides with a small value. # MR-reset settle wait; testbench overrides with a small value.
self._reset_cycles = reset_cycles self._reset_cycles = reset_cycles
# RSTn low time. Clamped to reset_cycles so the testbenches (which
# pass a tiny reset_cycles to stay fast) keep wait_ctr wide enough to
# actually reach it -- an unreachable target would hang the FSM.
self._rst_low = min(_RST_LOW_DEFAULT, reset_cycles)
# ── Optional socket-1 UDP bring-up test ─────────────────────────────── # ── Optional socket-1 UDP bring-up test ───────────────────────────────
# When enabled, init also configures the W5100's IP stack (GAR/SUBR/ # When enabled, init also configures the W5100's IP stack (GAR/SUBR/
@@ -556,7 +564,8 @@ class W5100ParallelMaster(Elaboratable):
with m.If(self.init_req): with m.If(self.init_req):
for i in range(6): for i in range(6):
m.d.sync += mac_shadow[i].eq(self.par[i*8:(i+1)*8]) m.d.sync += mac_shadow[i].eq(self.par[i*8:(i+1)*8])
m.next = "MR_RST" m.d.sync += [self.w5100_rst_n.eq(0), wait_ctr.eq(0)]
m.next = "HW_RST"
with m.Elif(~self.w5100_int_n): with m.Elif(~self.w5100_int_n):
m.next = "RX_CHECK" m.next = "RX_CHECK"
with m.Elif(self.tx_valid & self.tx_sof): with m.Elif(self.tx_valid & self.tx_sof):
@@ -582,6 +591,32 @@ class W5100ParallelMaster(Elaboratable):
self.dbg_rd_done.eq(1)] self.dbg_rd_done.eq(1)]
m.next = "IDLE" m.next = "IDLE"
# Pulse the W5100's RSTn pin, then let it boot, BEFORE touching the
# bus. This used to be missing entirely: w5100_rst_n was declared
# and never driven, so the only reset the chip ever got was the MR
# software reset -- which a wedged chip does not answer, because it
# arrives over the very bus that is stuck. The result was a W5100
# that could only be recovered by a 12 V power cycle; even
# reconfiguring the FPGA did not help, since RSTn just floats to its
# pull-up (R36) while the FPGA is unconfigured. Driving it here
# makes an NCRA reset able to recover the chip on its own.
# RSTn low: datasheet minimum is ~2 us; _RST_LOW is far longer and
# costs nothing on a path that runs once per init.
with m.State("HW_RST"):
with m.If(wait_ctr == self._rst_low):
m.d.sync += [self.w5100_rst_n.eq(1), wait_ctr.eq(0)]
m.next = "HW_RST_WAIT"
with m.Else():
m.d.sync += wait_ctr.eq(wait_ctr + 1)
# RSTn released: the W5100S needs 60.3 ms before it answers at all.
with m.State("HW_RST_WAIT"):
with m.If(wait_ctr == self._reset_cycles):
m.d.sync += wait_ctr.eq(0)
m.next = "MR_RST"
with m.Else():
m.d.sync += wait_ctr.eq(wait_ctr + 1)
# MR = 0x80 software reset (direct A=00), then settle. # MR = 0x80 software reset (direct A=00), then settle.
# #
# reset_cycles MUST cover the W5100S's post-reset dead time (the # reset_cycles MUST cover the W5100S's post-reset dead time (the