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:
@@ -305,11 +305,36 @@ What has been ruled out:
|
|||||||
clearing `sp_pending` in `HEADER0` fixed a real latent bug but left the
|
clearing `sp_pending` in `HEADER0` fixed a real latent bug but left the
|
||||||
ratio unchanged.
|
ratio unchanged.
|
||||||
|
|
||||||
⚠️ **THE MISSING TEST:** a `bba_top`-level test that performs an EXI DMA read
|
✅ **REPRODUCED IN SIMULATION 2026-10-09** — `bba_top` **T9** performs EXI DMA
|
||||||
from the SPRAM ring **while** the ethernet side writes frames into it. ETH
|
reads from the ring **while** the ethernet side writes frames into it, which no
|
||||||
preempts EXI reads, so only under that overlap can a read response be delayed
|
earlier test did (T6 waits for the frame to finish first, so reads and writes
|
||||||
past the end of its transaction. No simulation does this, which is why the
|
never overlapped).
|
||||||
offset is still unexplained. Write that before guessing again.
|
|
||||||
|
```
|
||||||
|
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
|
### ⚠️ Simulation rate — always re-run at the hardware rate
|
||||||
|
|
||||||
|
|||||||
@@ -644,6 +644,45 @@ if __name__ == "__main__":
|
|||||||
errors.append(f"T7 panel: EXI-activity LED not lit (led=0b{leds:05b})")
|
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)")
|
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.
|
# 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
|
# 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
|
# the length until the NCRA ST write, i.e. until after the GC had
|
||||||
|
|||||||
Reference in New Issue
Block a user