From a5b4d5bafddebeca77b840faa12bf90b7ecdbf75 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Fri, 9 Oct 2026 10:23:41 +0200 Subject: [PATCH] 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 --- gc_bba_test/source/tests.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/gc_bba_test/source/tests.c b/gc_bba_test/source/tests.c index 82486ca..dd8cae6 100644 --- a/gc_bba_test/source/tests.c +++ b/gc_bba_test/source/tests.c @@ -186,6 +186,36 @@ static int t_gap_sweep(char *m, size_t n) 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) { /* 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}}, {"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}}, + {"Per-speed dump","first 2 slots at EVERY speed - does the shift move?",t_raw_sweep, T_PENDING, {0}}, }; static test_t s_eth[] = { {"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}, {"Registers", "register file is readable and writable", s_regs, 4}, {"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}, {"SPRAM/DMA", "bulk reads stream correctly from the ring", s_spram, 2}, };