Add a per-speed raw dump to the GC test app

Removing ~9 ns from the MISO path (EXI_J4 merge, the CS mux, then the whole
fabric route via an SB_IO output register) left the hardware result BYTE
IDENTICAL: 0B 80 at index 5, ok=[1,1,1,1,1,0].  A ~9 ns reduction against an
18.5 ns half-period budget cannot change nothing if propagation were the
cause, so it is not, despite being speed-dependent.

t_raw_sweep reads the same 8 slots at EVERY speed index and reports the first
two bytes per index, so the shift can be seen as a curve rather than a single
point: a shift that appears or grows with clock rate is a timing problem, one
that is constant across indices is structural.  Run against a PHASE_METER
bitstream it reads the bit engine's byte phase directly at each speed, with
the register file and both byte FIFOs out of the path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-09 10:23:41 +02:00
parent 2f081a3e69
commit a5b4d5bafd
+32 -1
View File
@@ -186,6 +186,36 @@ static int t_gap_sweep(char *m, size_t n)
return 0; return 0;
} }
static int t_raw_sweep(char *m, size_t n)
{
/* The SAME 8-slot read at EVERY speed index, reported as the first two
* bytes per index. This is the measurement that distinguishes the two
* remaining explanations for a one-bit MISO lag:
* - a shift that APPEARS or GROWS with clock rate is a timing problem;
* - a shift that is constant across indices is structural.
* Run it against a PHASE_METER bitstream and every byte should read 80
* (aligned) / 40 (one bit late) / 20 (two bits late), with the register
* file and both byte FIFOs out of the path entirely. */
u32 saved = bba_speed, s;
int off = 0, bad = 0;
m[0] = 0;
for (s = 0; s <= EXI_SPEED32MHZ; s++) {
u8 b[8];
bba_speed = s;
if (bba_read(REG_NWAYS, b, sizeof(b)) < 0) {
off += snprintf(m + off, n - off, "%u:ERR ", (unsigned)s);
bad++;
continue;
}
off += snprintf(m + off, n - off, "%u:%02X%02X ",
(unsigned)s, b[0], b[1]);
if (b[0] != NWAYS_LINKUP)
bad++;
}
bba_speed = saved;
return bad ? 1 : 0;
}
static int t_raw_reg(char *m, size_t n) static int t_raw_reg(char *m, size_t n)
{ {
/* Clock EIGHT data bytes for a one-byte register read and show them all. /* Clock EIGHT data bytes for a one-byte register read and show them all.
@@ -361,6 +391,7 @@ static test_t s_exi[] = {
{"Gap sweep", "min header->data idle gap the device needs", t_gap_sweep, T_PENDING, {0}}, {"Gap sweep", "min header->data idle gap the device needs", t_gap_sweep, T_PENDING, {0}},
{"Raw reg dump", "8 slots of a NWAYS read - WHERE does 0x17 land?", t_raw_reg, T_PENDING, {0}}, {"Raw reg dump", "8 slots of a NWAYS read - WHERE does 0x17 land?", t_raw_reg, T_PENDING, {0}},
{"Raw ID dump", "8 slots of the device-ID query - where is 0x04?", t_raw_id, T_PENDING, {0}}, {"Raw ID dump", "8 slots of the device-ID query - where is 0x04?", t_raw_id, T_PENDING, {0}},
{"Per-speed dump","first 2 slots at EVERY speed - does the shift move?",t_raw_sweep, T_PENDING, {0}},
}; };
static test_t s_eth[] = { static test_t s_eth[] = {
{"MAC readback", "PAR0-5 hold a Nintendo-OUI MAC", t_mac_read, T_PENDING, {0}}, {"MAC readback", "PAR0-5 hold a Nintendo-OUI MAC", t_mac_read, T_PENDING, {0}},
@@ -376,7 +407,7 @@ suite_t g_suites[] = {
{"Identity", "device enumerates and reports itself", s_identity, 3}, {"Identity", "device enumerates and reports itself", s_identity, 3},
{"Registers", "register file is readable and writable", s_regs, 4}, {"Registers", "register file is readable and writable", s_regs, 4},
{"Reset/control", "NCRA reset and control bits behave", s_reset, 2}, {"Reset/control", "NCRA reset and control bits behave", s_reset, 2},
{"EXI integrity", "the EXI link is error-free, and how fast", s_exi, 5}, {"EXI integrity", "the EXI link is error-free, and how fast", s_exi, 6},
{"Ethernet", "frames get to and from the W5100", s_eth, 3}, {"Ethernet", "frames get to and from the W5100", s_eth, 3},
{"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2}, {"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2},
}; };