Record the 16-bit SPRAM response as falsified, and why

Returning both bytes of the SPRAM word per request was the one approach that
looked like it addressed the real constraint rather than working around it.
Implemented fully (16-bit exi_rsp_data + exi_rsp_odd in SPRAMArbiter, sp_buf/
sp_cnt pair-pushing in BBARegisterFile, address advance by 2, tests updated) and
it made things WORSE: bba_top T9 went 6/6 / 3/6 / 1/6 at EXI_HALF=8/2/1 against
the baseline's 6/6 / 6/6 / 1/6, breaking HALF=2, leaving the DE DE duplication
at the hardware rate, and adding a new fault where the first read after an idle
period returns all zeros. Reverted.

The reason is worth keeping: the budget only doubles if the next request issues
while the second byte is still being pushed, and it cannot, because that byte
sits in a holding register the prefetch guard has to wait on. The sequence stays
request -> response -> push -> push -> request, so round-trip latency per pair
is unchanged and the first byte of each pair faces the same deadline as before.
The constraint is unhidden round-trip latency, not bytes per round trip.

Also corrects the recorded T9 ratio at EXI_HALF=1 from 2/6 to 1/6, which is what
the committed test deterministically prints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 09:18:50 +02:00
parent bd4c69c5f2
commit 007c4d40f9
+18 -12
View File
@@ -313,11 +313,11 @@ 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
EXI_HALF=1 T9 concurrent DMA reads: 1/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
1-of-6 good is the same order as the console ratio (`desc@+0=100 +1=343`,
23% good), and the corruption is exactly the hardware symptom — **one extra byte inserted
at the front**, shifting everything:
```
@@ -356,21 +356,27 @@ absolute priority and preempt it) → SPRAM (1 cycle) → response → push →
over budget under a frame write. Only ONE request may be in flight, so the
latency is never hidden.
⚠️ **THREE FIXES TRIED AND FALSIFIED — do not repeat them:**
⚠️ **FOUR FIXES TRIED AND FALSIFIED — do not repeat them:**
| attempt | result |
|---|---|
| **Fair (round-robin) arbitration** instead of strict ETH priority | still 2/6. Bounds the wait to ~2 cycles but does not close the gap, and it breaks `spram_arbiter` T3. |
| **Fair (round-robin) arbitration** instead of strict ETH priority | still 1/6. Bounds the wait to ~2 cycles but does not close the gap, and it breaks `spram_arbiter` T3. |
| **Deepen `tx_fifo` 2 → 8** for cushion | **0/6**, with leading zeros. The engine drains leftovers with ONE POP PER HEADER BYTE, so a deeper FIFO leaks stale bytes into the data phase. `tx_depth=2` is deliberate — keep it shallow. |
| **Allow 2 outstanding requests** to pipeline the latency | **0/6 at every rate**, including `HALF=8` which previously passed. With no response FIFO, responses must be consumed the cycle they are valid; two in flight loses bytes. The existing comment was right. |
| **Return both SPRAM bytes per request** (16-bit response + `exi_rsp_odd`, two pushes per round trip) | **WORSE: 6/6 / 3/6 / 1/6** at `HALF=8/2/1` against the baseline's 6/6 / 6/6 / 1/6 — it broke `HALF=2`, left the `DE DE` duplication at `HALF=1`, and introduced a new bug where the FIRST read after an idle period returns all zeros. Reverted. |
**The promising direction, not yet attempted:** SPRAM is **16 bits wide**, and
the arbiter currently reads a word and returns a single byte selected by
address bit 0. Returning **both** bytes per request would let the register file
push two bytes per round trip, doubling the budget to 592 ns per request and
making it comfortable. That needs changes to `SPRAMArbiter`, the register
file's response handling, and their tests — the only approach found so far
that addresses the actual constraint rather than working around it.
**Why doubling the payload did not work, which is the useful part.** The budget
per round trip doubles to 592 ns only if the next request issues while the
second byte is still being pushed. It cannot: the second byte lives in a
holding register (`sp_buf`/`sp_cnt`) and the prefetch guard must wait for
`sp_cnt == 0`, or the next response overwrites a byte that has not been pushed
yet. So the sequence is still request → response → push → push → request, and
the *latency* per round trip is unchanged while the deadline for the first byte
of each pair is exactly as tight as before. **The constraint is round-trip
latency that nothing hides, not bytes per round trip** — so any fix must either
overlap requests with pushes (which needs a real response FIFO, the thing whose
absence killed the 2-outstanding attempt) or remove the round trip from the
critical path altogether.
### ⚠️ Simulation rate — always re-run at the hardware rate