diff --git a/CLAUDE.md b/CLAUDE.md index 373a02e..e4c8e56 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -88,8 +88,18 @@ 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.) +(The ID tests report SKIP, not FAIL, once `id_phase` has been consumed — +reflash the FPGA to restore it. They decide that from the hardware, via +`iface_alive()`: HIPR and NWAYS are hardwired and independent of `id_phase`, so +if both read correctly the EXI path is fine and only a spent `id_phase` can +explain a wrong answer at address 0. + +⚠️ The `g_ncra_reset_done` flag alone is NOT sufficient, and this bites every +time a build is delivered over the wire: netboot replaces the DOL and restarts +the app with the flag clear, while the FPGA keeps running with `id_phase` +already spent by the previous run. The app then queries address 0, gets NCRA +back — correct behaviour — and called it a FAILURE. Software state cannot stand +in for hardware state across a self-update.) **Timing closes on EVERY seed (re-measured 2026-10-10).** `python -m exi_bba.synth --seeds 8`: diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index 440ac3e..b8c1af4 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -11,6 +11,27 @@ static void msleep(u32 ms) { usleep(ms * 1000); } /* ───────────────────────── suite 1: identity ───────────────────────── */ +/* Is the EXI link demonstrably healthy apart from address 0? + * + * Needed because `id_phase` is a ONE-SHOT in the FPGA, cleared by the first + * NCRA reset, and g_ncra_reset_done only tracks resets THIS RUN of the test + * app issued. That flag is software state standing in for hardware state, and + * the two come apart as soon as a build is delivered over the wire: netboot + * replaces the DOL and restarts the app with the flag clear, while the FPGA + * keeps running with id_phase already spent by the previous run. The ID tests + * then query address 0, get NCRA back -- correct behaviour -- and report a + * FAILURE for it. Reflashing the FPGA is the only thing that restores it. + * + * HIPR and NWAYS are hardwired constants in the register file and do not + * depend on id_phase, so if BOTH read correctly the EXI path is fine and a + * wrong answer at address 0 can only be a spent id_phase. If they are wrong + * too, something real is broken and the test must still fail. */ +static int iface_alive(void) +{ + return bba_r8(REG_HIPR) == HIPR_PRESENT && + bba_r8(REG_NWAYS) == NWAYS_LINKUP; +} + static int t_devid(char *m, size_t n) { u8 b[4]; @@ -20,8 +41,18 @@ static int t_devid(char *m, size_t n) return 2; } if ((rc = bba_devid(b)) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; } + if (b[0]==0x04 && b[1]==0x02 && b[2]==0x02 && b[3]==0x00) { + snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)", + b[0], b[1], b[2], b[3]); + return 0; + } + if (iface_alive()) { + snprintf(m, n, "%02X %02X %02X %02X: id_phase spent, reflash FPGA", + b[0], b[1], b[2], b[3]); + return 2; + } snprintf(m, n, "%02X %02X %02X %02X (want 04 02 02 00)", b[0], b[1], b[2], b[3]); - return (b[0]==0x04 && b[1]==0x02 && b[2]==0x02 && b[3]==0x00) ? 0 : 1; + return 1; } static int t_hipr(char *m, size_t n) @@ -238,10 +269,10 @@ 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. */ + /* Fast path for a reset this run issued. Note this flag can be STALE + * (see iface_alive): it is clear after a netbooted update even though the + * FPGA's id_phase is long gone, which is why the result is also checked + * against iface_alive() below rather than trusted to this. */ if (g_ncra_reset_done) { snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA"); return 2; @@ -256,7 +287,10 @@ static int t_raw_id(char *m, size_t n) snprintf(m, n, "%02X %02X %02X %02X %02X %02X %02X %02X | 0x04 %s%d", b[0],b[1],b[2],b[3],b[4],b[5],b[6],b[7], at < 0 ? "absent" : "at slot ", at < 0 ? 0 : at); - return at == 0 ? 0 : 1; + if (at == 0) + return 0; + /* Same discrimination as t_devid: a spent id_phase is not a fault. */ + return iface_alive() ? 2 : 1; } /* ───────────────────────── suite 5: ethernet ───────────────────────── */