docs: MISO timing solved on hardware; command-space ID latency still open

Console confirms the full-cycle-early launch: speed sweep ok=[1,1,1,1,1,1],
per-speed dump 0:1700 through 5:1700, raw reg dump 0x17 in slot 0.  Register
reads are correct at every EXI speed index including 27 MHz.

Records what the PHASE_METER measurement showed, why three rounds of
delay-cutting could not work (9 ns removed, byte-identical result each time,
so the remaining cost is pad + global clock insertion + IOB + pad, which no
RTL change reaches), and the testbench rule that falls out of it: a master
must sample MISO in the CLK-LOW phase or a zero-delay simulation reads the
NEXT bit and reports everything one bit early while hardware is correct.

Also records the one remaining failure as OPEN rather than fixed: the
command-space device-ID response is one byte late at 27 MHz because a 2-byte
header leaves only one byte time (296 ns) for a round trip that costs
~280-320 ns, and the inter-transfer gap cannot help because the clock is
paused across it.  HEADER0 already pushes in the same cycle it consumes byte
0, so the FSM side is already as early as possible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:42:12 +02:00
parent 7ac0cbe66c
commit 5db3b7751a
+71
View File
@@ -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.**