diff --git a/CLAUDE.md b/CLAUDE.md index 125f1f5..e92b937 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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