diff --git a/CLAUDE.md b/CLAUDE.md index 2e97c5e..ecfbb2c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,6 +82,15 @@ The `exi_bba/` module tree is **fully implemented** with simulation testbenches. All modules elaborate without errors and pass their unit tests, including `bba_top` at the real 27 MHz EXI rate (`EXI_HALF=1`). +✅ **FULL TEST SUITE PASSES ON A REAL CONSOLE (2026-10-09)** — every suite in +`gc_bba_test`: Identity (device ID `04 02 02 00`, HIPR, NWAYS), Registers, +Reset/control, EXI integrity (speed sweep `ok=[1,1,1,1,1,1]`, per-speed dump +clean at all six indices, raw reg dump `0x17` in slot 0), Ethernet, and +SPRAM/DMA. The EXI front end runs at the real 27 MHz hardware rate. + +(`Raw ID dump` reports SKIP once an earlier suite has issued the NCRA reset +that clears `id_phase` — reflash or reset the FPGA to restore it.) + **Timing closes on EVERY seed (2026-10-03).** `python -m exi_bba.synth --seeds 8`: @@ -310,31 +319,53 @@ Side effect: EXI-domain fmax rose 41.6–49.7 → **62.1–65.4 MHz**, because t 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 +### ✅ Device ID answered by the BIT ENGINE (fixed 2026-10-09) -`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. +`bba_devid` returned `00 04 02 02` on a console — the ID correct but **one +byte late** — and it failed in its REAL shape (16 µs `gap()`, exactly four +bytes clocked), so it was not a probe artefact. Register-space reads were +fine throughout; only command space was affected. -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. +**Cause was 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 possible. -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. +⚠️ **The inter-transfer gap cannot help, and this is the non-obvious part.** +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 µs gap made no difference, and why "give it more idle time" +is not a fix. **Pre-pushing 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. -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. +**Fix: take the round trip out of the path.** The device ID is a hardwired +constant and belongs to the physical interface, not the register file, and +`byte 0 == 0x00` identifies the query exactly (bit 7 clear = command space, +bit 6 clear = read, reg 0). `SPIMode3Slave` 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 was **removed**, not kept as a fallback: left in, +it pushes bytes a 2-byte header has too few pops to drain — exactly how an +earlier version made the *next* enumeration come back a slot late. + +Tests: `spi_mode3_slave` **T7** (reply is `04 02 02 00` with the tx FIFO +deliberately feeding `0x00`, so only the fast path can answer) and **T8** (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** asserts the FSM pushes +NOTHING for an ID query. + +⚠️ **Simulation cannot reproduce this bug.** `bba_top` passed the device ID at +`EXI_HALF=1` *before* the fix, because the sync domain's latency relative to +the EXI clock in simulation does not match silicon closely enough to miss a +296 ns window. T7/T8 prove the fast path works; they do not prove it is +necessary. Do not "simplify" the ID back into the register file because the +suite stays green — it will, and hardware will not. ### ⚠️ TX bit phase MUST come from the rising-edge counter (fixed 2026-10-03) diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index dd8cae6..5e1f8bf 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -16,7 +16,7 @@ static int t_devid(char *m, size_t n) u8 b[4]; int rc; if (g_ncra_reset_done) { - snprintf(m, n, "skipped: one-shot, needs a console power cycle"); + snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA"); return 2; } if ((rc = bba_devid(b)) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; } @@ -238,6 +238,14 @@ static int t_raw_id(char *m, size_t n) u8 b[8]; int i, at = -1, rc; u16 h = 0x0000; + /* Same one-shot as t_devid. Without this guard a full run reports a + * FAILURE here purely because an earlier suite issued the NCRA reset + * that clears id_phase -- address 0 then reads back NCRA, which is + * correct behaviour, not a fault. */ + if (g_ncra_reset_done) { + snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA"); + return 2; + } if ((rc = begin_raw()) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; } EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL); EXI_Sync(BBA_EXI_CHAN);