diff --git a/CLAUDE.md b/CLAUDE.md index 4a52fca..2e97c5e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -265,6 +265,77 @@ hold across parts and temperature, and 3× oversampling needs 81 MHz, which this part cannot close. `SPIMode3Slave` has no `mode2` parameter any more — there is only one correct convention and it is enforced by mutation tests. +### ✅ MISO timing SOLVED 2026-10-09 — launch a full cycle early + +**Confirmed on a console:** speed sweep `ok=[1,1,1,1,1,1]`, per-speed dump +`0:1700 … 5:1700`, raw reg dump `0x17` in slot 0. Register reads are correct +at **every** EXI speed index including 5 (27 MHz, the real hardware rate). + +The engine drives MISO on the **RISING** edge carrying the bit the host will +sample at the NEXT rising edge, so the output path gets a **FULL period +(37 ns)** rather than the half period (18.5 ns) a falling-edge launch gives. +`tx_out` is clocked on the rising edge and loads at `rx_bit == 7`; there is no +`exiclk_n` domain any more — one clock edge, one phase reference. + +**Why a half period was not enough, and why no RTL change could fix it.** +~9 ns was removed from the fabric output path in three steps — the EXI_J4 pad +merge, then the `Mux(cs_n, 1, …)` LUT, then the entire fabric route by moving +the output into an `SB_IO` register — and the hardware result stayed **byte +identical** every time (`0B 80`). Nine nanoseconds out of 18.5 cannot change +nothing if propagation were the whole story. What remains is irreducible: +input pad + **global clock-network insertion** + IOB clock-to-Q + output pad, +plus the host's own setup. So the fix was to double the budget, not shrink the +delay. + +`PHASE_METER` is what settled it: `0:8080 1:8080 2:8080 3:8080 4:8080 5:C040` +— the bit engine's own byte phase, perfect to 16 MHz and one bit late at 27. +The 8-slot dump read `C0 40 40 40 C0 40 40 40`: a uniform one-bit lag with +extra highs at bits 0 and 32, where libogc splits the read into two 4-byte +transfers and MISO tri-states to its pull-up between them. + +⚠️ **Testbench consequence.** A master must sample MISO in the **CLK-LOW +phase** (the value latched ON the rising edge). Sampling after raising the +clock sees the NEXT bit in a zero-delay simulation, so the suite would report +everything one bit early while hardware was correct. Both `bba_top` and +`spi_mode3_slave` now sample in the low phase. + +⚠️ **This relies on the delay landing in a window:** MISO must change late +enough to clear the host's hold time on the previous bit, and within a full +period for its setup. The measured insertion delay (~12–20 ns against 37 ns) +sits comfortably inside. If a LOW speed index ever regresses while 27 MHz +works, that is the hold edge of the window — the launch is too early — not a +setup problem. + +Side effect: EXI-domain fmax rose 41.6–49.7 → **62.1–65.4 MHz**, because the +setup path into the IOB register became posedge→posedge against a full period +instead of posedge→negedge against a half. Same doubling, seen internally. + +### ⚠️ OPEN: command-space (device-ID) response is marginal at 27 MHz + +`t_raw_id` at index 5 returns `00 04 02 02 00 02 02 02` — the ID is **correct +but one byte late**. Cause is latency, not phase: a command-space header is +only 2 bytes, so the response must be in the tx FIFO by the **last rising edge +of the header transfer** (one byte time, 296 ns at 27 MHz). The clock pauses +after that edge, so the inter-transfer gap CANNOT help — there are no edges +during it for anything to update. The round trip is rx `AsyncFIFO` (~125 ns) + +one sync cycle + tx `AsyncFIFO` (~110 ns) ≈ 280–320 ns against 296 ns, i.e. +**marginal by construction**. `HEADER0` already pushes ID byte 0 in the same +cycle it consumes byte 0, so the FSM side cannot be made earlier. + +Register-space reads have the same one-byte-time budget and DO work, so the +command-space path is only slightly slower — consistent with a marginal miss. + +Note `t_raw_id` is harsher than anything the GC does: it omits the `gap()` +that `bba_devid` uses AND clocks 8 bytes for a 4-byte query (the documented +over-clocking artefact — hence the trailing `02 02 02`). Establish whether +`bba_devid` (Identity suite) passes before treating this as fatal. + +Fix direction if it is real: shorten the CDC round trip, e.g. replace the tx +`AsyncFIFO` with a 2-deep ping-pong CDC (see the lightweight-TX-CDC note). +Pre-pushing the ID at CS-fall does NOT work — the engine's unconditional +per-byte pops would eat it, and the header length is not known until byte 0 +has been decoded. + ### ⚠️ TX bit phase MUST come from the rising-edge counter (fixed 2026-10-03) **Never give the TX shift register its own free-running falling-edge counter.**