From 5ee1b9d07b017eaabdf502082f8b08247729ceca Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 10 Oct 2026 10:17:41 +0200 Subject: [PATCH] gc_bba_test: tell a spent id_phase from a real fault using the hardware The ID tests guarded on g_ncra_reset_done, which is software state standing in for hardware state. The two come apart exactly when 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 reported a FAILURE for it. That is why the ID tests would sometimes skip and sometimes fail for the same expected condition. Both tests now ask the hardware instead. 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 demonstrably fine and a wrong answer at address 0 can only be a spent id_phase -> SKIP, with "reflash FPGA" in the message. If they are wrong too, something real is broken and the test still FAILS. The flag is kept only as a fast path for a reset issued during the same run, with a comment saying it can be stale. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 14 ++++++++++-- gc_bba_test/source/tests.c | 46 +++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 8 deletions(-) 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 ───────────────────────── */