Commit Graph

3 Commits

Author SHA1 Message Date
Roflin 92f7d716d9 Add RBFI ring-overflow protection: drop frames instead of overwriting
RXFrameAssembler ignored RRP entirely, so it lapped the reader: a traffic
burst overwrote pages the GC had not read, and the "descriptor" the driver
then parsed was frame data from whatever had replaced it.  That is where the
hardware's 00 00 00 01 came from -- not a dropped write.

This is a product bug, not just a netboot one.  Until now ANY burst could
corrupt a frame Swiss was midway through reading, with nothing to indicate it
had happened.

The assembler now takes the GC's read pointer and computes free space as
(rrp - rwp - 1) mod 15.  On a frame start with fewer than 6 free pages -- the
worst case for 1518 bytes plus a 4-byte descriptor -- it enters DROP_FRAME,
discards to EOF, and pulses rx_overflow, which sets IR[7] (RBFI).  That is the
bit the GC's IMR = 0x86 already enables, so a driver that loses a frame is
told so instead of reading corruption.

Deliberate details:
  - rx_ready stays HIGH while dropping.  Stalling the ethernet master instead
    would back-pressure into the W5100's own buffer and turn a dropped frame
    into a wedged receiver.
  - Six pages are reserved up front because a frame's length is not known
    until EOF.  Starting with less risks running into unread data partway
    through, which is worse than refusing cleanly.
  - have_room is REGISTERED, not combinational.  Feeding a mod-15 subtract and
    comparator into the FSM branch cost real depth in `sync`, the domain with
    the least margin: clk fell 27.8-32.2 -> 27.0-28.0 MHz.  Registering it
    restored 28.4-31.4.  One cycle of staleness is harmless -- rwp moves only
    at end-of-frame and rrp only when the GC writes it.

T4 proves the property that matters, and the negative control REPRODUCES THE
HARDWARE FAULT IN SIMULATION: with the check removed the unread page reads
back as [245, 248, 251, 254, ...] -- frame data where a descriptor belongs.
First time one of today's RX faults has been reproducible on this side rather
than inferred from the console.  T4 also checks the receiver un-latches once
RRP advances, so a full ring cannot kill RX permanently.

Fixed a test artifact in the same pass: rx_overflow fires on the very tick
that consumes the EOF byte, inside send_frame, so polling for a 1-cycle pulse
afterwards missed it and a correctly-dropped frame looked like a stall.  Both
pulses are now latched in the tick helper.

netboot also clears IR (including RBFI) at init so a stale bit is not
inherited from an earlier run.

--seeds 8: clk 28.4-31.4 MHz, EXI 55.2-62.7 MHz, 8/8 PASS.  Best seed 4
(clk 31.39, EXI 61.85), flashed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 12:36:07 +02:00
Roflin 86cd5d1abd Fix two dropped-write bugs in RXFrameAssembler's SPRAM handshake
Both from one mistake: eth_wr_valid is scheduled with m.d.sync and so is LOW
for the whole first cycle in a state, while eth_wr_ready was tested
immediately -- and `ready` is usually already high on entry.  Both
valid.eq(1) and valid.eq(0) then fired in the same cycle, the later won, the
write was never issued, and it was dropped SILENTLY.

1. WRITE_LEN_HI dropped the descriptor's HIGH length byte, leaving it at its
   placeholder 0x00.  Hardware read a descriptor of 00 00 00 01 -- a 1-byte
   frame length, impossible for ethernet -- and rejected the ring.

2. WRITE_PLACEHOLDER counted a phantom first write, advancing wr_addr without
   issuing one.  Only THREE placeholder bytes were written and wr_addr ended
   at desc_base+3, so the frame body started at descriptor byte 3 and EVERY
   RECEIVED FRAME WAS SHIFTED ONE BYTE EARLY.  The GC's ethertype test then
   read the wrong offset and silently dropped every frame: the host saw "no
   ACK" while the ring filled and RWP advanced normally.

Both now advance on `valid & ready`, and the body offset is set explicitly
(desc_base + 4) rather than relying on an incremented pointer, so it cannot
drift with the handshake again.

WHY NO TEST CAUGHT THIS, which is the more important half:

  - The testbench drove the RX stream OPEN-LOOP -- one byte per tick, never
    checking rx_ready, which is low through IDLE and the whole placeholder
    write.  It silently lost bytes, including the SOF byte.  A real master
    must hold each byte until accepted.
  - It only ever asserted on rwp and rx_irq.  It declared a spram_writes list
    and never filled it, so NOTHING had ever checked what was written.
  - Every frame it sent was short, and a short frame's high length byte is
    legitimately zero -- so a dropped high byte was indistinguishable from a
    correct one.

The testbench now respects back-pressure, records every write, and checks the
descriptor bytes and the body offset.  T2 sends a 300-byte frame specifically
so the high length byte is non-zero.  Mutation-tested: restoring either bug
fails T2 with got [0,0,0,48] want [0,0,1,48] -- the same shape as the
hardware's 00 00 00 01 -- while T1 (short frame) still passes, which is
exactly the blind spot that let this ship.

Bring-up's 342-byte DHCP frame read a correct 00 00 01 5A only because the
arbiter happened to hold `ready` low on that entry cycle; the domain merge
and the SPRAM response-FIFO removal changed that timing.

--seeds 8: clk 27.8-32.2 MHz, EXI 54.9-64.5 MHz, 8/8 PASS.  Best seed 8
(clk 32.22, EXI 62.25), flashed.  Gateware only -- the .dol is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 12:04:00 +02:00
Dennis Brentjes 8d0ab1d948 Added full design created with Claude 2026-06-13 18:35:38 +02:00