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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
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.
|
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
|
(The ID tests report SKIP, not FAIL, once `id_phase` has been consumed —
|
||||||
that clears `id_phase` — reflash or reset the FPGA to restore it.)
|
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
|
**Timing closes on EVERY seed (re-measured 2026-10-10).** `python -m
|
||||||
exi_bba.synth --seeds 8`:
|
exi_bba.synth --seeds 8`:
|
||||||
|
|||||||
@@ -11,6 +11,27 @@ static void msleep(u32 ms) { usleep(ms * 1000); }
|
|||||||
|
|
||||||
/* ───────────────────────── suite 1: identity ───────────────────────── */
|
/* ───────────────────────── 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)
|
static int t_devid(char *m, size_t n)
|
||||||
{
|
{
|
||||||
u8 b[4];
|
u8 b[4];
|
||||||
@@ -20,8 +41,18 @@ static int t_devid(char *m, size_t n)
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
if ((rc = bba_devid(b)) < 0) { snprintf(m, n, "EXI error %d", rc); return 1; }
|
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]);
|
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)
|
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];
|
u8 b[8];
|
||||||
int i, at = -1, rc;
|
int i, at = -1, rc;
|
||||||
u16 h = 0x0000;
|
u16 h = 0x0000;
|
||||||
/* Same one-shot as t_devid. Without this guard a full run reports a
|
/* Fast path for a reset this run issued. Note this flag can be STALE
|
||||||
* FAILURE here purely because an earlier suite issued the NCRA reset
|
* (see iface_alive): it is clear after a netbooted update even though the
|
||||||
* that clears id_phase -- address 0 then reads back NCRA, which is
|
* FPGA's id_phase is long gone, which is why the result is also checked
|
||||||
* correct behaviour, not a fault. */
|
* against iface_alive() below rather than trusted to this. */
|
||||||
if (g_ncra_reset_done) {
|
if (g_ncra_reset_done) {
|
||||||
snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA");
|
snprintf(m, n, "skipped: id_phase is a one-shot; reflash/reset the FPGA");
|
||||||
return 2;
|
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",
|
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],
|
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);
|
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 ───────────────────────── */
|
/* ───────────────────────── suite 5: ethernet ───────────────────────── */
|
||||||
|
|||||||
Reference in New Issue
Block a user