77cd00a63cbdbe5834659ec7f8716faeb2accc7e
9 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7587636b48 |
Answer the device-ID query from the bit engine, not the register file
bba_devid fails on a console with 00 04 02 02 -- the ID correct but one byte
late -- and it fails in its REAL shape, with the 16 us gap and exactly four
bytes clocked, so this is not the t_raw_id probe artefact. The register suite
passes, so register-space reads are fine; only command space is affected.
Cause is latency, not phase. A command-space header is two bytes, so the
reply must be in the tx FIFO by the LAST RISING EDGE of the header transfer:
one byte time, 296 ns at 27 MHz. The round trip is rx AsyncFIFO (~125 ns) +
a sync cycle + tx AsyncFIFO (~110 ns) = 280-320 ns, marginal by construction.
HEADER0 already pushed in the same cycle it consumed byte 0, so the FSM side
was already as early as it can be.
The inter-transfer gap cannot rescue it: the load happens on that last rising
edge and the clock is PAUSED afterwards, so no edge exists during the gap for
anything to update. That is why bba_devid's 16 us gap makes no difference.
Pre-pushing the ID at CS-fall does not work either -- the engine's
unconditional per-byte pops would eat it, and the header length is not known
until byte 0 has been decoded, which is the latency in question.
So take the round trip out of the path. The device ID is a hardwired constant
and belongs to the physical interface rather than to the register file, and
byte 0 == 0x00 identifies the query exactly (bit 7 clear = command space, bit
6 clear = read, reg 0). SPIMode3Slave now recognises it and answers
04 02 02 00 from the EXI clock domain, with no CDC at all. The only thing it
needs from sync is id_phase, a single bit that changes once, crossed with an
FFSynchronizer.
The register file's ID path is REMOVED rather than kept as a fallback: left
in, it would push bytes that a 2-byte header has too few pops to drain, which
is exactly how an earlier version made the NEXT enumeration come back a slot
late.
Tests follow the responsibility:
- spi_mode3_slave T7: the query returns 04 02 02 00 with the tx FIFO
deliberately feeding 0x00, so only the fast path can be answering.
- spi_mode3_slave T8: and it is GATED -- with id_arm low, 0x5A comes
through from the FIFO. Without this the fast path would hijack address 0
forever and the GC could never read NCRA back after its own reset.
- bba_register_file T1: retargeted to assert the FSM pushes NOTHING for an
ID query, which is the property that stops stale bytes corrupting the
following transaction.
Note what these tests do NOT establish: bba_top already passed the device ID
at EXI_HALF=1 before this change, because simulation cannot reproduce a
296 ns window miss -- the sync domain's latency relative to the EXI clock in
sim does not match silicon closely enough. T7/T8 prove the fast path works;
the evidence that it is NECESSARY is the hardware result plus the paused-clock
argument above.
--seeds 8: clk 28.4-31.0 MHz, EXI 56.7-64.4 MHz, 8/8 PASS. Best seed 2
(clk 30.98, EXI 59.47), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
7ac0cbe66c |
Launch MISO a full cycle early instead of chasing delay
PHASE_METER on a console settled what two rounds of delay-cutting could not.
Per-speed dump, first two slots per EXI speed index, with the register file
and both byte FIFOs out of the MISO path:
0:8080 1:8080 2:8080 3:8080 4:8080 5:C040
Perfect to 16 MHz, one bit late at 27 MHz. The 8-slot dump at 27 MHz read
C0 40 40 40 C0 40 40 40 -- a UNIFORM one-bit lag (ones at bits 1, 9, 17, 25,
33, ...), 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 in the gap. The ID
dump agreed exactly.
Decisive detail: ~9 ns had already been removed from the fabric output path
(the EXI_J4 pad merge, then the CS mux, then the entire fabric route by moving
the output into an SB_IO register) and the hardware result stayed BYTE
IDENTICAL each time. Nine nanoseconds out of an 18.5 ns budget cannot change
nothing if propagation were the whole story. What is left is the part no RTL
change can reach: input pad + GLOBAL CLOCK NETWORK INSERTION + IOB clock-to-Q
+ output pad, plus the host's own setup. There was no fabric delay left to
cut, so cutting delay was the wrong move.
So double the budget instead. MISO is now launched on the RISING edge
carrying the bit the host will sample at the NEXT rising edge, so the whole
path gets a FULL period (37 ns at 27 MHz) rather than half. This is safe in
both directions: the insertion delay that caused the problem now covers the
host's hold time on the previous bit, and a full period covers its setup.
- tx_out is clocked on the rising edge and loads at rx_bit == 7, one
bit-time earlier than a falling-edge shifter loading at rx_bit == 0.
- tx_hold and the entire exiclk_n domain are gone: the engine now has ONE
clock edge and ONE phase reference.
- synth.py's SB_IO output register moves to NEG_TRIGGER=0.
- Byte alignment is unchanged -- T2/T5 still land A5 and 3C in the same
slots. Only the sub-bit launch timing moved.
Testbench consequence, and it matters: bba_top's master sampled MISO AFTER
raising the clock. With a zero-delay simulation and a rising-edge launch that
reads the NEXT bit, so the suite would have reported everything one bit early
while hardware was correct. Both masters now sample in the CLK-LOW phase --
the value the host latches ON the rising edge -- which is also exactly what
the hardware notes say a probe must do.
T2 slot 0 now reads 0xFF (the CS-reset parking value that keeps MISO high
while deselected) instead of 0x00; it is a header byte the host ignores.
T6 re-anchored to the rising edge and still proves miso_next predicts
spi_miso bit for bit, so the SB_IO register stays equivalent to the fabric
model rather than an untested divergence.
--seeds 8: clk 28.9-32.1 MHz, EXI 62.1-65.4 MHz, 8/8 PASS. The EXI numbers
rise sharply (41.6-49.7 -> 62.1-65.4) because the setup path into the IOB
register is now posedge->posedge against a FULL period instead of
posedge->negedge against a half -- the same doubling, visible internally.
Best seed 3 (clk 32.09, EXI 62.78), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
2f081a3e69 |
Drive MISO from an SB_IO output register
Removing ~2 ns from the fabric MISO path (EXI_J4 merge + the CS mux) did NOT move the hardware result: the GC still returned a byte-identical 0B 80 at 27 MHz while reading correctly at 16 MHz and below. Identical rather than intermittent means the path is well outside the window, not marginally inside it, so trimming fabric delay further was not going to get there. MISO is source-synchronous -- driven on the falling edge, sampled by the GC on the next rising edge -- so the whole path must fit a HALF period, 18.5 ns at 27 MHz, minus global-buffer insertion, pad, connector and the GC's setup. Driving from fabric spent 8.4-11.5 ns of that on routing alone. An SB_IO output register sits in the IO tile: clock-to-pad is the IO flop's own delay with no fabric routing. nextpnr confirms the path left the fabric entirely -- miso_io.D_OUT_0 packs at die (9,0), the pad itself, SB_IO count is unchanged at 24, and no negedge -> <async> path is reported any more. What replaces it is a setup path INTO the IOB register (10.07 ns against a half-period), which is internal and has nothing stacked on top of it. PIN_TYPE was decoded from yosys's own SB_IO model rather than from memory, because wrong bits there produce a silently dead or mistimed output: [5:4]=10 tri-state, combinational OUTPUT_ENABLE [3]=0,[2]=1 dout = dout_q_0, single-rate REGISTERED output [1:0]=01 simple input (unused) NEG_TRIGGER=1 moves the IO registers to the FALLING edge. To keep simulation and silicon agreeing about MISO, the slave exposes miso_next -- the value the shift register WILL hold after the falling edge -- and spi_mode3_slave T6 proves it predicts spi_miso bit for bit. Without that the IOB register would be an untested sim/hardware divergence, which is the exact class of bug that has cost this design the most time. BEHAVIOUR CHANGE: MISO is now TRI-STATED when CS is high, held high by the pad's PULLUP, instead of actively driven high. EXI MISO is a SHARED bus -- SP1 is on channel 0 alongside memory-card slot A -- so driving it while deselected was wrong and could fight a card. --seeds 8: clk 28.5-30.7 MHz, EXI 41.6-49.7 MHz, 8/8 PASS. The EXI numbers fall (62 -> 49) because the posedge->negedge setup path into the IOB register is now charged to that domain at a half-period constraint; 41.6 MHz worst case is still 1.5x the 27 MHz target. Best seed 3 (clk 30.68, EXI 49.14), flashed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5f7f20db43 |
Cut the MISO output path: drop EXI_J4 and the CS mux; rank seeds by it
Remaining after the bit-phase fix was a SPEED-DEPENDENT failure -- the GC read
correctly at 1/2/4/8/16 MHz and failed only at index 5 (27 MHz), returning
0B 80 = 0x17 late by exactly ONE bit. Opposite signature to the phase slip it
was hiding under, and this one really is propagation delay.
MISO is source-synchronous: driven on the falling edge, sampled by the GC on
the next rising edge, so the whole path must fit a HALF period -- 18.5 ns at
27 MHz -- minus global-buffer insertion, pad, connector and the GC's setup.
The failing build measured ~10.5 ns of that internally. Critical path:
clk-to-q 1.39 tx_shift (3,12)
routing 3.60 tx_shift[7] (3,12) -> (5,5) to the CS mux
logic 1.21 Mux(cs_n, 1, ...) LUT
routing 4.79 miso (5,5) -> (17,0) to the J4 MISO pad
10.99 ns (2.60 logic, 8.39 routing)
Two removals, both of things that were pure cost here:
1. EXI_J4 is GONE entirely (exi_j4.py, the resource, the AND merge, the docs
section, scripts/pi-exi-wirecheck.py) -- recoverable from git history. The
AND put a LUT in the CLOCK path, denying the EXI clock pad its dedicated
global-buffer route, and MISO fanned out to the J4 pad at die (17,0) which
was the far end of the critical output path: 4.79 ns of routing to a header
with nothing plugged into it. 10.2-11.5 ns -> 9.3-9.6 ns.
2. The Mux(cs_n, 1, ...) LUT is gone from the MISO data path. It only parked
MISO high when deselected, which the flop's own reset value does for free:
tx_shift now has init=0xFF and CS is that domain's async reset, so MISO is
tx_shift[7] with nothing in between. Removes 1.21 ns of logic plus the
3.60 ns route feeding it and lets the placer sit the flop by the pad.
synth.py now PARSES AND RANKS BY the MISO clock-to-pad delay. Ranking by fmax
was how a board that failed at 27 MHz passed every seed at >=1.29x margin --
fmax was never the binding constraint for this design.
--seeds 8: clk 28.7-32.5 MHz, EXI 49.6-62.1 MHz, MISO 8.38-10.22 ns, 8/8 PASS.
Best seed 8 (clk 30.35, EXI 49.56, MISO 8.38 ns), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
d35688390e |
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>
|
||
|
|
91c3bf0ece |
Clock the EXI bit engine from the EXI pin: timing closes on all 8 seeds
Replaces the PLL-fed 54 MHz `capture` domain, which oversampled the GC's
27 MHz EXI clock 2x and so put BIT-level timing across a CDC boundary: the
engine's view of the clock lagged the pin by 2 FFSynchronizer stages plus a
register (~55 ns) against a 37 ns bit time, so MISO came out ~2 bits late and
the GC read 0x17 as 05 C0. No amount of oversampling fixes that -- 54 MHz
gives only 2 ticks per bit and the part cannot close the 81 MHz that 3x needs.
The engine is now clocked by the pin itself (pin 44 = global buffer input net
6, no board change), making MISO clock-to-Q. ExiCapture creates four domains:
exiclk (posedge, samples MOSI), exiclk_n (negedge, drives MISO), both
async-reset by CS, plus reset-less exififo_w/exififo_r for the byte FIFO sides
-- reset-less because resetting one side of an AsyncFIFO desynchronises its
gray pointers, and posedge because AsyncFIFO has a posedge-only reset CDC
internally and cannot host a negedge side.
Also removes the SPRAM response FIFO. The DMA read was one byte late because
the round trip did not fit the protocol's turnaround byte; the arbiter already
exposed valid/ready, so the FIFO bought a cycle of latency and nothing else.
The response is handled outside the FSM (the reply lands in RHDR_PAD, not
SPRAM_STREAM) with a one-entry hold for when tx_fifo is momentarily full.
The PLL was left instantiated but unused after the rewrite; removing it frees
the hard macro and pin 35 (which stays unconnected only because the board is
already built that way). This is what lifted the pass rate:
domain target before after
clk (sync) 24 MHz 28.1-30.2 26.4-32.2 8/8
capture_clk 54 MHz 2 of 8 PASS (gone)
EXI pin domain 27 MHz -- 45.6-64.4 8/8
Best seed 3: clk 32.18 MHz, EXI 55.86 MHz. The binding constraint has FLIPPED
-- `clk` (1.10-1.34x) is now tighter than the EXI domain (1.69-2.39x) -- so
synth.py ranks seeds by worst RELATIVE margin, not by the EXI number.
synth.py could not have scored this design at all: it required the vanished
capture_clk, and its regex '(\w+)' cannot match nextpnr's exi_clk_$glb_clk
(no $ in \w). add_clock_constraint was also landing on the wrong net -- under
EXI_J4 the clock is the AND of two pads, not either pad -- so nextpnr silently
fell back to a 12 MHz default and under-optimised the whole EXI domain.
Testbenches:
- spi_mode3_slave had lost its testbench entirely in the rewrite. Restored
with a strict Mode 3 master (changes MOSI on the falling edge, samples MISO
in the clock-low phase) and MUTATION-TESTED: flipping either clock edge in
the RTL makes it fail. Both of those bugs previously reached silicon
against a lax master that parked MOSI well before the edge.
- exi_j4 T2/T3 returned [0,4,2,2]. Real bug: the engine latches the FIFO head
at each byte boundary, so a 2-byte command header leaves only ONE drain
slot. DEVID streamed zeros until CS and left two stale bytes -- one too
many -- so the NEXT enumeration came back a slot late. DEVID now pushes
exactly four bytes and stops. Register-space reads may still over-push:
their 4-byte header gives three drain slots.
- Dead ports removed from SPIMode3Slave (hdr_bytes, frame_start, cs_active,
byte_idx) and the mode2 parameter is gone -- there is one correct edge
convention and it is now enforced by tests.
All 14 module suites pass, plus bba_top at EXI_HALF=1/2/8.
CLAUDE.md: rewrote the clocking, CDC inventory and module tables; marked the
2026-09-30 capture_clk measurements historical; and corrected the line in
"Critical Protocol Notes" that still stated the INVERTED edge convention
("MOSI sampled on falling, MISO driven on rising" is Mode 2) -- the original
implementation followed that doc error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a3b3e052bc |
WIP rewrite: clock the EXI bit engine from the EXI clock pin
Replaces 54 MHz oversampling with a slave clocked by EXI CLK itself:
exiclk posedge sample MOSI (Mode 3 samples on edge 2)
exiclk_n negedge drive MISO (must settle between the master's samples)
CS_N async reset for both
EXI CLK (pin 44) is at die (6,0) = global buffer net 6 on the 5k-sg48, so it
can drive a real clock network with no board change.
This removes the capture domain entirely. Bit-level timing no longer
crosses a clock boundary -- MISO is clock-to-Q instead of 3 ticks of a foreign
clock -- and the only crossings left are the byte FIFOs, which have a whole
byte-time (~296 ns) of slack rather than a fraction of a bit.
AsyncFIFO needs a posedge clock for its internal reset CDC, so the FIFO sides
use reset-less POSEDGE domains on the same pin; the engine pops on the posedge
and hands the byte to the negedge output stage through a register. The FIFOs
must not see CS: resetting one side of an AsyncFIFO desynchronises its gray
pointers.
Two bugs found and fixed along the way:
* one pop per byte boundary is all that is needed -- pops during the header
drain whatever the previous transaction left (replacing the CS-fall flush)
and the turnaround-byte push lands in data slot 0. No slot-threshold or
header-length logic at all.
* rx_byte/rx_valid had to become COMBINATIONAL. Registered, they need one
more clock edge, and after a transaction's final bit the clock stops and
CS rises -- so the last byte of every transaction was lost (a 4-byte write
landed as DE AD BE 00, a 34-byte TX as 15 bytes).
At the HARDWARE rate (EXI_HALF=1, 27 MHz) the device ID, NWAYS, PAR
read/write and the W5100->SPRAM RX path now all pass -- none of which has ever
worked at 27 MHz before. Testbenches now drive the SPI clock with real
delays, since there is no capture domain to count ticks against.
STILL BROKEN: the SPRAM/DMA stream is offset by one byte (its round trip
exceeds the single turnaround byte), and TX lands 15 of 34 bytes. Do not
flash this yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
afc10d7e5c |
fix(spi): sample MOSI on the RISING edge -- real Mode 3, not Mode 2
The docs call this 'Mode 3 (CPOL=1, CPHA=1)' and then describe sampling MOSI
on the FALLING edge. Idle-high plus sample-on-falling is Mode 2. Real Mode 3
samples on the second edge of each cycle, which with an idle-high clock is the
RISING one, and that is what the GameCube does.
Sampling on the same edge the GC drives MOSI latched the PREVIOUS bit, so
every byte arrived one bit late in both directions, at every clock speed:
the 0x80 register-space header byte was read as 0x00, which decodes as a
command-space device-ID query -- so NWAYS reads returned the device ID and the
speed sweep failed at every index. Hardware raw dump, first transaction after
an FPGA reset:
00 00 02 01 01 00 00 00 02 01 01 00 = 04 02 02 00 >> 1
The Pi probe never caught it because that script was written to match this
device, and every simulation testbench drives MOSI many capture ticks before
toggling the clock, so MOSI is stable at BOTH edges and the convention cannot
be distinguished. The bba_top master now models a true Mode 3 master (drive
on falling, sample on rising) so the suite can tell the difference.
SPIMode3Slave(mode2=True) restores the old behaviour for the Pi/J4 probe.
Known remaining issue: at EXI_HALF=1 (27 MHz, 2x oversampling) reads are still
shifted, because the 2-stage input FFSynchronizer costs 2 capture ticks which
at 27 MHz is a whole bit. That is a separate fault and only bites at 2x.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8d0ab1d948 | Added full design created with Claude |