Take the TX bit phase from the rising-edge counter, not a falling-edge one

The bit engine had TWO independent phase references: rx_bit counting rising
edges and tx_bit counting falling edges.  Those only stay in step while the
host emits exactly as many falls as rises, and it does not -- a transfer that
begins on a RISING edge (CS asserted while CLK is already low, or the clock
gating between two back-to-back host transfers) delivers a rise with no
matching fall.  tx_bit then slips one bit and never recovers.

RX never notices, because the rising count always equals the host's bit count.
So the header decodes perfectly, the right register is selected, and ONLY MISO
is wrong -- which is what made this look like an output-edge or output-delay
problem for two rounds of debugging.

Measured on a GameCube: every MISO byte shifted right by 2 bits, identically
at every EXI speed index.  NWAYS read 05 C0 (= 0x17 with two leading zero
bits), device ID 01 00 80 80, speed sweep ok=[0,0,0,0,0,0].

Two diagnoses were wrong before this one, and both were ruled out by evidence
rather than argument:
  - NOT propagation delay.  Index 0 is 1 MHz, a 1000 ns bit time; no output
    path fails there.  Speed-independence is the tell.
  - NOT the output edge.  The old rising-edge build and the new falling-edge
    build returned BYTE-IDENTICAL 05 C0.  Flipping the edge would have moved
    it; it did not, so the slip is upstream of the output stage.

What settled it was a measurement.  PHASE_METER=1 builds a diagnostic,
non-functional bitstream whose engine transmits a constant 0x80 per byte
instead of the FIFO head, taking the register file and both byte FIFOs out of
the MISO path so the host reads the engine's byte PHASE directly: 0x80
aligned, 0x40 one bit late, 0x20 two bits late.  Hardware read 0x20 in all
eight slots, localising the fault to the bit engine in a single run.

Fix: the shift register loads on rx_bit == 0.  One phase reference, on the
edge the host counts, so the two cannot drift.

Regression test (spi_mode3_slave T5) asserts CS while CLK is LOW so the
transaction begins on a rising edge.  The old counter fails it by exactly one
bit: A5 3C comes back as 52 9E.  EVERY earlier testbench asserted CS with CLK
high, so falls and rises always matched and the slip was structurally
invisible -- the same blind spot as the Mode 2/3 mix-up, which was invisible
to a master that parked MOSI early.  Vary the starting edge in SPI tests.

Timing after the fix, EXI_J4=1, --seeds 8: clk 26.9-31.1 MHz, EXI 45.8-61.7
MHz, 8/8 PASS.  Best seed 4 (clk 31.07, EXI 56.70), flashed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 19:04:52 +02:00
parent 91c3bf0ece
commit d35688390e
5 changed files with 122 additions and 12 deletions
+41
View File
@@ -244,6 +244,47 @@ 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.
### ⚠️ 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.**
`SPIMode3Slave` takes its byte phase from `rx_bit`, the RISING-edge counter —
the same edge the host counts bits on. A separate `tx_bit` counting falling
edges only stays in step while the host emits exactly as many falls as rises,
and it does not: a transfer that begins on a RISING edge (CS asserted while
CLK is already low, or the clock gating between two back-to-back host
transfers) delivers a rise with no matching fall. `tx_bit` then slips one bit
and never recovers. RX never notices, because the rising count always equals
the host's bit count — so the header decodes perfectly and only MISO is wrong.
**Symptom on console:** every MISO byte shifted right by 2 bits, *identically
at every EXI speed index*. A NWAYS read returned `05 C0` (= `0x17` with two
leading zero bits) and the device ID `01 00 80 80`. The speed sweep read
`ok=[0,0,0,0,0,0]`.
**How it was found, after two wrong diagnoses.** It is not propagation delay:
index 0 is 1 MHz, a 1000 ns bit time, and no output path fails there —
speed-independence is the tell. It is not the output edge either: the old
rising-edge build and the new falling-edge build returned *byte-identical*
`05 C0`, and an edge flip would have moved it. What settled it was a
measurement, not more theory — see PHASE_METER below.
**`PHASE_METER=1 python -m exi_bba.synth`** builds a DIAGNOSTIC,
NON-FUNCTIONAL bitstream in which the bit engine transmits a constant `0x80`
for every byte instead of the FIFO head. The register file and both byte FIFOs
drop out of the MISO path, so whatever the host reads back is purely the
engine's byte phase: `0x80` aligned, `0x40` one bit late, `0x20` two bits late.
On hardware it read `0x20` in all 8 slots, which localised the fault to the bit
engine and ruled out everything upstream in one run. Re-flash a normal build
afterwards.
**Regression test:** `spi_mode3_slave` T5 asserts CS while CLK is LOW, so the
transaction begins on a rising edge and carries one fewer falling edge. The
old counter fails it by exactly one bit (`A5 3C` → `52 9E`). Every earlier
testbench asserted CS with CLK high, so falls and rises always matched and the
slip was *structurally invisible* — the same failure of imagination as the
Mode 2/3 mix-up, which was invisible to a master that parked MOSI early. When
adding SPI tests, vary the edge the transaction starts on.
### EXI on the J4 debug header (`EXI_J4=1`) — bench host without SP1
`EXI_J4=1` lets a bench host (the Pi) drive EXI through the unpopulated J4