3d37e4ba6d
The W5100S ethernet back-end now works end-to-end on real hardware: a complete, correctly-checksummed UDP datagram (10.0.0.99:40000 -> 10.0.0.50:6464, 32-byte payload) was transmitted by the FPGA-driven W5100S and captured on a PC, after a full bidirectional ARP handshake. Records the three W5100S-specific findings in CLAUDE.md (NETLCKR unlock, the 15-bit IDM address space putting the buffers at 0x4000/0x6000, and the 60.3 ms reset-to-ready time), and notes that UDP unicast is the reliable bring-up path while a 255.255.255.255 send sets SEND_OK without appearing on the wire. w5100_udptest.py is the self-contained configure+send+readback probe that isolated all of this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
264 lines
12 KiB
Python
264 lines
12 KiB
Python
"""Self-contained W5100S UDP send + register readback (bring-up diagnostic).
|
|
|
|
One bitstream does EVERYTHING — reset, configure socket 3 for UDP, send a
|
|
broadcast, then read back what the chip made of it — so there is no reflash gap
|
|
and no "did the W5100 get reset in between?" ambiguity that made the read-only
|
|
w5100_regdump probe inconclusive.
|
|
|
|
It streams the verdict on the UART (115200 8N1):
|
|
|
|
U: SIP=XX SSR=XX SIR=XX TRD=XXXX TWR=XXXX
|
|
|
|
SIP SIPR0 read back. C0 (=192) => NETLCKR unlock worked and the network
|
|
config actually took. 00 => the write was still being dropped.
|
|
SSR Sn_SR after OPEN. 22 => SOCK_UDP, the socket really opened.
|
|
00 => never opened (OPEN rejected) — SEND can then do nothing.
|
|
SIR Sn_IR after SEND. bit4 (10) => SEND_OK, the chip completed the send.
|
|
TRD/TWR Sn_TX_RD / Sn_TX_WR. TWR should equal the queued byte count;
|
|
TRD catching up to TWR means the chip actually drained/transmitted it.
|
|
TWR=0000 => nothing was ever queued.
|
|
|
|
Everything is driven from a flat (addr, data) script executed over the indirect
|
|
bus, so the whole configure+send is a table rather than a hand-rolled FSM.
|
|
|
|
Build/flash: python -m exi_bba.w5100_udptest --flash
|
|
"""
|
|
from amaranth import *
|
|
|
|
_A_MR, _A_AR0, _A_AR1, _A_DR = 0b00, 0b01, 0b10, 0b11
|
|
_MR_IND, _MR_AI, _MR_RST = 0x01, 0x02, 0x80
|
|
|
|
_SOCK = 3
|
|
_SBASE = 0x0400 + _SOCK * 0x0100 # socket-3 register block = 0x0700
|
|
_TXBUF = 0x4000 + _SOCK * 0x0800 # socket-3 TX buffer (IDM 15-bit) = 0x5800
|
|
_PAYLEN = 32 # >= 18 so the frame clears 64B minimum
|
|
|
|
# ── phase 1: unlock + network config + open socket 3 as UDP ──────────────────
|
|
_CFG1 = [
|
|
(0x0071, 0x3A), # NETLCKR unlock
|
|
(0x0009, 0x00), (0x000A, 0x09), (0x000B, 0xBF), # SHAR 00:09:BF:00:00:01
|
|
(0x000C, 0x00), (0x000D, 0x00), (0x000E, 0x01),
|
|
(0x0005, 0xFF), (0x0006, 0xFF), (0x0007, 0xFF), (0x0008, 0x00), # SUBR /24
|
|
(0x000F, 10), (0x0010, 0), (0x0011, 0), (0x0012, 99), # SIPR 10.0.0.99
|
|
(0x001A, 0x55), (0x001B, 0x55), # RMSR / TMSR
|
|
(_SBASE + 0x00, 0x02), # Sn_MR = UDP
|
|
(_SBASE + 0x04, 0x9C), (_SBASE + 0x05, 0x40), # Sn_PORT = 40000
|
|
(_SBASE + 0x01, 0x01), # Sn_CR = OPEN
|
|
]
|
|
|
|
# ── phase 2: destination + payload + length + SEND ───────────────────────────
|
|
_CFG2 = [
|
|
(_SBASE + 0x0C, 10), (_SBASE + 0x0D, 0), # DIPR 10.0.0.50 (PC)
|
|
(_SBASE + 0x0E, 0), (_SBASE + 0x0F, 50),
|
|
(_SBASE + 0x10, 0x19), (_SBASE + 0x11, 0x40), # DPORT = 6464
|
|
] + [(_TXBUF + i, 0x41 + (i % 26)) for i in range(_PAYLEN)] + [
|
|
(_SBASE + 0x24, (_PAYLEN >> 8) & 0xFF), # Sn_TX_WR = len
|
|
(_SBASE + 0x25, _PAYLEN & 0xFF),
|
|
(_SBASE + 0x01, 0x20), # Sn_CR = SEND
|
|
]
|
|
|
|
_SCRIPT = _CFG1 + _CFG2
|
|
_N1 = len(_CFG1)
|
|
|
|
# registers read back, in report order
|
|
_READS = [("B00", _TXBUF + 0), ("B01", _TXBUF + 1), ("B02", _TXBUF + 2),
|
|
("C00", 0x4000), ("C01", 0x4001),
|
|
("SIR", _SBASE + 0x02), ("TRl", _SBASE + 0x23)]
|
|
|
|
|
|
class W5100UdpTest(Elaboratable):
|
|
def __init__(self, *, strobe_cycles=3, rst_cycles=48_000,
|
|
boot_cycles=2_040_000, settle_cycles=12_000_000):
|
|
self._strobe = strobe_cycles
|
|
self._rst = rst_cycles
|
|
self._boot = boot_cycles # >= 60.3 ms W5100S init
|
|
self._settle = settle_cycles # ~10 ms waits after OPEN and after SEND
|
|
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)
|
|
self.uart_tx = Signal(init=1)
|
|
self.vals = Array([Signal(8, name=f"v{i}") for i in range(len(_READS))])
|
|
self.done = Signal()
|
|
|
|
def elaborate(self, platform):
|
|
m = Module()
|
|
S = self._strobe
|
|
# ── bus engine ───────────────────────────────────────────────────────
|
|
go=Signal(); rw=Signal(); ba=Signal(2); wd=Signal(8); rdv=Signal(8)
|
|
bdone=Signal(); bctr=Signal(range(S+2)); rw_r=Signal()
|
|
a_o=Signal(2); d_o=Signal(8); d_oe=Signal()
|
|
cs=Signal(init=1); rdn=Signal(init=1); wrn=Signal(init=1); rstn=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),
|
|
self.rd_n.eq(rdn), self.wr_n.eq(wrn), self.rst_n.eq(rstn)]
|
|
m.d.sync += bdone.eq(0)
|
|
with m.FSM(domain="sync", name="bus"):
|
|
with m.State("IDLE"):
|
|
m.d.sync += [cs.eq(1), rdn.eq(1), wrn.eq(1), d_oe.eq(0)]
|
|
with m.If(go):
|
|
m.d.sync += [a_o.eq(ba), rw_r.eq(rw), cs.eq(0), bctr.eq(0)]
|
|
with m.If(rw):
|
|
m.d.sync += [d_o.eq(wd), d_oe.eq(1), wrn.eq(0)]
|
|
with m.Else():
|
|
m.d.sync += rdn.eq(0)
|
|
m.next="STROBE"
|
|
with m.State("STROBE"):
|
|
m.d.sync += bctr.eq(bctr+1)
|
|
with m.If(bctr == S-1):
|
|
with m.If(~rw_r): m.d.sync += rdv.eq(self.bus_data_i)
|
|
m.d.sync += [rdn.eq(1), wrn.eq(1)]
|
|
m.next="FINISH"
|
|
with m.State("FINISH"):
|
|
m.d.sync += [cs.eq(1), d_oe.eq(0), bdone.eq(1)]
|
|
m.next="IDLE"
|
|
m.d.comb += [go.eq(0), rw.eq(0), ba.eq(0), wd.eq(0)]
|
|
def bw(a,d): m.d.comb += [go.eq(1), rw.eq(1), ba.eq(a), wd.eq(d)]
|
|
def br(a): m.d.comb += [go.eq(1), rw.eq(0), ba.eq(a)]
|
|
|
|
saddr = Array([Const(a,16) for a,_ in _SCRIPT])
|
|
sdata = Array([Const(d,8) for _,d in _SCRIPT])
|
|
raddr = Array([Const(a,16) for _,a in _READS])
|
|
si = Signal(range(len(_SCRIPT)+1))
|
|
ri = Signal(range(len(_READS)+1))
|
|
ctr = Signal(range(max(self._rst,self._boot,self._settle)+2))
|
|
send_phase = Signal() # 0 = running CFG1, 1 = running CFG2
|
|
|
|
with m.FSM(domain="sync", name="seq"):
|
|
with m.State("RST"):
|
|
m.d.sync += [rstn.eq(0), ctr.eq(ctr+1)]
|
|
with m.If(ctr == self._rst-1):
|
|
m.d.sync += ctr.eq(0); m.next="BOOT"
|
|
with m.State("BOOT"):
|
|
m.d.sync += [rstn.eq(1), ctr.eq(ctr+1)]
|
|
with m.If(ctr == self._boot-1):
|
|
m.d.sync += ctr.eq(0); m.next="MRMODE"
|
|
with m.State("MRMODE"): # MR = indirect + AI
|
|
bw(_A_MR, _MR_IND | _MR_AI); m.next="MRMODE_W"
|
|
with m.State("MRMODE_W"):
|
|
with m.If(bdone): m.d.sync += si.eq(0); m.next="W_AR0"
|
|
|
|
# generic script executor: indirect write saddr[si] = sdata[si]
|
|
with m.State("W_AR0"):
|
|
bw(_A_AR0, saddr[si][8:16]); m.next="W_AR0_W"
|
|
with m.State("W_AR0_W"):
|
|
with m.If(bdone): m.next="W_AR1"
|
|
with m.State("W_AR1"):
|
|
bw(_A_AR1, saddr[si][0:8]); m.next="W_AR1_W"
|
|
with m.State("W_AR1_W"):
|
|
with m.If(bdone): m.next="W_DR"
|
|
with m.State("W_DR"):
|
|
bw(_A_DR, sdata[si]); m.next="W_DR_W"
|
|
with m.State("W_DR_W"):
|
|
with m.If(bdone):
|
|
m.d.sync += si.eq(si+1)
|
|
with m.If((si+1 == _N1) & ~send_phase):
|
|
m.d.sync += [ctr.eq(0), send_phase.eq(1)]
|
|
m.next="SETTLE1" # let OPEN take effect
|
|
with m.Elif(si+1 == len(_SCRIPT)):
|
|
m.d.sync += ctr.eq(0); m.next="SETTLE2" # let SEND finish
|
|
with m.Else():
|
|
m.next="W_AR0"
|
|
with m.State("SETTLE1"):
|
|
m.d.sync += ctr.eq(ctr+1)
|
|
with m.If(ctr == self._settle-1):
|
|
m.d.sync += ctr.eq(0); m.next="W_AR0" # continue with CFG2
|
|
with m.State("SETTLE2"):
|
|
m.d.sync += ctr.eq(ctr+1)
|
|
with m.If(ctr == self._settle-1):
|
|
m.d.sync += [ctr.eq(0), ri.eq(0)]; m.next="R_AR0"
|
|
|
|
# generic readback: indirect read raddr[ri] -> vals[ri]
|
|
with m.State("R_AR0"):
|
|
bw(_A_AR0, raddr[ri][8:16]); m.next="R_AR0_W"
|
|
with m.State("R_AR0_W"):
|
|
with m.If(bdone): m.next="R_AR1"
|
|
with m.State("R_AR1"):
|
|
bw(_A_AR1, raddr[ri][0:8]); m.next="R_AR1_W"
|
|
with m.State("R_AR1_W"):
|
|
with m.If(bdone): m.next="R_DR"
|
|
with m.State("R_DR"):
|
|
br(_A_DR); m.next="R_DR_W"
|
|
with m.State("R_DR_W"):
|
|
with m.If(bdone):
|
|
m.d.sync += self.vals[ri].eq(rdv)
|
|
with m.If(ri == len(_READS)-1):
|
|
m.next="DONE"
|
|
with m.Else():
|
|
m.d.sync += ri.eq(ri+1); m.next="R_AR0"
|
|
with m.State("DONE"):
|
|
m.d.comb += self.done.eq(1)
|
|
|
|
# ── UART report ──────────────────────────────────────────────────────
|
|
DIV = round(24_000_000/115_200)
|
|
parts = b"U:"; pos={}
|
|
for name,_ in _READS:
|
|
parts += b" "+name.encode()+b"="; pos[name]=len(parts); parts += b"00"
|
|
parts += b"\r\n"
|
|
tmpl=list(parts); MSGLEN=len(tmpl)
|
|
rom=Array([Const(b,8) for b in tmpl])
|
|
def hexch(n): return Mux(n<10, 0x30+n, 0x37+n)
|
|
cur=Signal(8); uidx=Signal(range(MSGLEN+1))
|
|
m.d.comb += cur.eq(rom[uidx])
|
|
with m.Switch(uidx):
|
|
for i,(name,_) in enumerate(_READS):
|
|
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); nb=Signal(range(11)); bd=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"):
|
|
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))), nb.eq(10), bd.eq(DIV-1)]
|
|
m.next="SHIFT"
|
|
with m.State("SHIFT"):
|
|
with m.If(bd==0):
|
|
m.d.sync += bd.eq(DIV-1)
|
|
with m.If(nb==1):
|
|
m.d.sync += uidx.eq(uidx+1); m.next="LOAD"
|
|
with m.Else():
|
|
m.d.sync += [nb.eq(nb-1), shift.eq(Cat(shift[1:],C(1,1)))]
|
|
with m.Else():
|
|
m.d.sync += bd.eq(bd-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 W5100UdpTestTop(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 = W5100UdpTest()
|
|
w = platform.request("w5100", 0); u = platform.request("uart", 0)
|
|
m.d.comb += [
|
|
w.addr.o.eq(dut.bus_addr), w.data.o.eq(dut.bus_data_o),
|
|
w.data.oe.eq(dut.bus_data_oe), dut.bus_data_i.eq(w.data.i),
|
|
w.cs_n.o.eq(dut.cs_n), w.rd_n.o.eq(dut.rd_n),
|
|
w.wr_n.o.eq(dut.wr_n), w.rst_n.o.eq(dut.rst_n),
|
|
u.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(W5100UdpTestTop(), do_program="--flash" in sys.argv,
|
|
name="w5100_udptest", build_dir="build_w5100test")
|
|
print("[built] build_w5100test/w5100_udptest.bin")
|
|
raise SystemExit(0)
|
|
print(f"script={len(_SCRIPT)} writes (CFG1={_N1}), payload={_PAYLEN}B at {_TXBUF:#06x}")
|
|
print("use --build or --flash (hardware probe)")
|