Reproduce the +1 descriptor offset in simulation (bba_top T9)

First time one of this session's RX faults has been reproducible on this side
rather than inferred from a console.

T9 performs EXI DMA reads from the SPRAM ring WHILE the ethernet side writes
frames into it.  No earlier test did: T6 waits for the frame to complete before
reading, so reads and writes never overlapped -- and ETH has ABSOLUTE priority
in SPRAMArbiter, so only under that overlap can an EXI read be preempted.

    EXI_HALF=8  6/6 byte-exact
    EXI_HALF=2  6/6 byte-exact
    EXI_HALF=1  2/6 byte-exact      <-- the hardware rate

2-of-6 matches the console ratio closely (desc@+0=100 +1=343, 23% good), and
the corruption is exactly the hardware symptom -- ONE EXTRA BYTE inserted at
the front, shifting everything:

    expected: 00 00 00 0C DE AD BE EF
    got:      00 00 00 00 0C DE AD BE

That it is INSERTED rather than wrong is the useful part: an extra push into
the tx FIFO is a COUNT mismatch in the request/response path, not corrupted
SPRAM data.  It only shows at 27 MHz because the read has to complete inside
the protocol's turnaround window while ETH preempts it.

This is the bug to chase first, ahead of the residual ACK retries: netboot
only works because its reader probes both offsets.  Swiss does not probe -- it
would read a frame length of 1 -- so real networking on this adapter is broken
while our own tooling reports success.

bba_top now FAILS at EXI_HALF=1, deliberately.  The failure is real and the
test is the reproduction; do not relax T9 to make the suite green.  HALF=8 and
HALF=2 still pass for unrelated work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 20:05:48 +02:00
parent 8691b09676
commit 2eba68a013
2 changed files with 69 additions and 5 deletions
+30 -5
View File
@@ -305,11 +305,36 @@ What has been ruled out:
clearing `sp_pending` in `HEADER0` fixed a real latent bug but left the
ratio unchanged.
⚠️ **THE MISSING TEST:** a `bba_top`-level test that performs an EXI DMA read
from the SPRAM ring **while** the ethernet side writes frames into it. ETH
preempts EXI reads, so only under that overlap can a read response be delayed
past the end of its transaction. No simulation does this, which is why the
offset is still unexplained. Write that before guessing again.
✅ **REPRODUCED IN SIMULATION 2026-10-09** — `bba_top` **T9** performs EXI DMA
reads from the ring **while** the ethernet side writes frames into it, which no
earlier test did (T6 waits for the frame to finish first, so reads and writes
never overlapped).
```
EXI_HALF=8 T9 concurrent DMA reads: 6/6 byte-exact
EXI_HALF=2 T9 concurrent DMA reads: 6/6 byte-exact
EXI_HALF=1 T9 concurrent DMA reads: 2/6 byte-exact <-- the hardware rate
```
2-of-6 good matches the console ratio closely (`desc@+0=100 +1=343`, 23% good),
and the corruption is exactly the hardware symptom — **one extra byte inserted
at the front**, shifting everything:
```
expected: 00 00 00 0C DE AD BE EF
got: 00 00 00 00 0C DE AD BE
```
An INSERTED byte means an extra push into the tx FIFO, not a corrupted one, so
the fault is a count mismatch somewhere in the request/response path — not the
SPRAM data. It only appears at 27 MHz because the read must finish inside the
protocol's turnaround window while ETH (which has absolute arbiter priority) is
preempting it.
⚠️ **`bba_top` therefore FAILS at `EXI_HALF=1` until this is fixed.** That is
deliberate: the failure is real and the test is the reproduction. Do not
"fix" it by relaxing T9. `EXI_HALF=8` and `EXI_HALF=2` still pass, so use
those while working on unrelated changes.
### ⚠️ Simulation rate — always re-run at the hardware rate
+39
View File
@@ -644,6 +644,45 @@ if __name__ == "__main__":
errors.append(f"T7 panel: EXI-activity LED not lit (led=0b{leds:05b})")
print(f"T7 panel led=0b{leds:05b} (bit1=EXI activity, expect 1)")
# ── T9: EXI DMA READ **WHILE** THE ETHERNET SIDE WRITES THE RING ──
# This is the condition hardware actually runs in and that no test has
# ever modelled: T6 above waits for the frame to finish before reading,
# so reads and writes never overlap.
#
# ETH has ABSOLUTE priority in SPRAMArbiter -- eth_wr_ready is asserted
# combinationally whenever eth_wr_valid is high -- so an EXI read is
# preempted whenever a frame byte is being written. If a read request
# is lost, or a response is delayed past the end of its transaction,
# the GC's byte stream shifts.
#
# On console the ring descriptor lands at page offset +1 instead of +0
# for ~77% of frames (desc@+0=100 +1=343), varying frame to frame, and
# netboot only works because its reader probes both offsets. Swiss
# does not probe: it would read a frame length of 1 and the RX path
# would be broken for real networking. So this test exists to
# reproduce that, not merely to add coverage.
#
# Page 1 still holds T6's completed frame, so its expected contents are
# known exactly while a SECOND frame is being written behind it.
ctx.set(dut.w5100_int_n, 0) # another frame arriving
mismatches = 0
first_bad = None
for attempt in range(6):
again = await exi_read(ctx, 0x0100, total_len)
if again != want:
mismatches += 1
if first_bad is None:
first_bad = again
ctx.set(dut.w5100_int_n, 1)
await ctx.delay(HP * (HALF * 4))
print(f"T9 concurrent DMA reads: {6 - mismatches}/6 byte-exact")
if mismatches:
print(f"T9 first bad read: {[f'0x{b:02X}' for b in first_bad[:8]]}")
print(f"T9 expected : {[f'0x{b:02X}' for b in want[:8]]}")
errors.append(f"T9 DMA read corrupted by concurrent ETH writes "
f"({mismatches}/6 reads wrong)")
# T8: GC → wire TX of a frame LONGER than the 16-deep tx_bytes FIFO.
# This is the case that shipped broken: TXFrameDrain used to wait for
# the length until the NCRA ST write, i.e. until after the GC had