2893e7f6fbd30e2a16ab3b74fd4177b9a179fa46
38 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
90a583dd46 |
Remove the device-ID one-shot: it made Swiss see no adapter at all
id_phase was set at reset and cleared permanently by the GC's first NCRA reset, after which a command-space read of register 0 returned 0x00000000. Swiss calls EXI_GetID from bba_exists() AND again from bba_requires_init(), long after init, and matches the result against EXI_MX98730EC_ID -- so with the one-shot spent no case matched and Swiss reported NO ADAPTER, not a broken one. Running gc_bba_test was enough to spend it, so the adapter would "disappear" after any test session. The justification in the comment was wrong. It claimed the fast path would otherwise hijack address 0 so the GC could never read NCRA back -- but NCRA is REGISTER space (0x80/0xC0 header) while the fast path requires byte 0 == 0x00, i.e. COMMAND space. They were never ambiguous, and no driver reads command register 0 for anything else (libogc2 reads 0x01/0x03/0x0B/0x0F, Swiss 0x03). Real hardware answers EXI_GetID whenever asked; that is how EXI enumerates devices. id_arm is now tied high, and the id_phase output, the FFSynchronizer that crossed it into the pin domain, and the BBATop wiring are all gone. The port stays only so the PHASE_METER build can disarm the fast path, which would otherwise send ID bytes instead of its constant 0x80. bba_top T1b reads the device ID AFTER an NCRA reset, which T1 (before any reset) structurally cannot catch. Mutation-tested: disarming the fast path returns 00 00 00 00 for both, exactly what Swiss saw. gc_bba_test: the ID tests no longer skip. A wrong ID is a FAILURE again, with no iface_alive() discrimination and no ordering requirement between suites -- the ID is readable at any point in a run. g_ncra_reset_done and iface_alive() are removed outright rather than left as a dead flag. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
052e1a4252 |
gc_bba_test: shorten the driver-shapes suite description to fit the row
"Swiss + libogc init sequences and transfer shapes" is 49 characters and wrapped by exactly one. The menu row is 28 columns of prefix -- " " + name(%-14s) + " " + "pp/ff/kk" + " " -- and the console is 76 wide (console_init starts at x=20 on a 640-pixel framebuffer at 8 pixels per character), leaving 48. Now "Swiss + libogc init and transfer shapes", 39 characters, in line with the 41 of the longest original. Added a comment above g_suites with the 48-column budget and where it comes from, so the next suite does not rediscover it by wrapping on a console. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b3685237b2 |
Support BOTH command-space forms: Swiss's 4-byte and libogc's 2-byte
Reading Swiss's own driver (cube/patches/bba/bba.c) shows the previous commit
fixed libogc and BROKE Swiss. The two disagree, and both are real:
Swiss bba_cmd_out8: exi_imm_read_write(0x40<<24 | reg<<24 | val, 4)
-> ONE 4-byte transfer [0x40|reg, 00, 00, val], and bba_cmd_in8
returns the LOW byte, so a read response must be in BYTE 3.
libogc2 bba_cmd_outsnosel: req = (reg<<8)|0x4000;
EXI_ImmEx(&req, 2, WRITE); EXI_ImmEx(val, len, WRITE)
-> THREE bytes, value in byte 2, and a read takes byte 2.
stock libogc: identical to libogc2 (read out of libbba.a).
They share bytes 0 and 1 and differ only in how many bytes follow, so one
decoder serves both: take the LAST data byte before CS rise as the write value
(Swiss sends [00, val], libogc sends [val] -- last is val either way), and push
a read response TWICE so bytes 2 and 3 both carry it. Swiss clocks 4 bytes and
pops both; libogc clocks 3 and leaves one, which the next transaction's header
pops drain -- a 2-byte header gives exactly one drain slot, so the leftover is
bounded at one and cannot accumulate.
Critically this means NO auto-increment of cmd_reg across the data phase: that
applied Swiss's padding 00 to the register and val to reg+1, which is precisely
how the libogc-only version broke Swiss.
T12 covers the Swiss form and asserts bytes 2 AND 3; T10 keeps the libogc form.
Both mutation-tested: re-adding the auto-increment fails T12 with 0x00 in both
slots, and pushing the response once fails T12's byte 3. gc_bba_test gains
"cmd-space Swiss" alongside "cmd-space write", with bba_cmd_write4/read4.
Also corrects two things in CLAUDE.md now checkable against three drivers:
- NO driver writes TWD -- not Swiss, not libogc, not libogc2. The "GC sets
TWD before streaming" note was self-confirming: t_tx writes TWD because we
wrote t_tx. The counted TXDATA length is the primary path, not a fallback.
- Swiss's exi_dma_write rounds the length UP to 32 bytes (OSRoundUp32B), so
TXDATA gets up to 31 trailing bytes past the frame and a counted length sends
that much extra. Real hardware must do the same since Swiss works on it;
trailing padding past the IP total length is ignored. A 1514-byte frame
rounds to 1536 though, over the 1518 maximum.
Both drivers trigger TX with NCRA ST1, which the existing any-ST test accepts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
71ec6a2b37 |
TX: count the TXDATA bytes, because libogc never writes TWD
Third contract checked against libogc rather than our notes. __bba_link_tx is four transactions -- read NWAYS, ONE register-space write of the whole frame to 0x0048, read NCRA, write NCRA = 0x04 -- with no TWD_LO/TWD_HI anywhere. So the device has to count the bytes it was handed. The NCRA ST fallback queued tx_frame_len, which only TWD sets, so any driver that skips TWD would have transmitted a stale or zero length. BBARegisterFile now keeps txdata_cnt, incremented per byte accepted at 0x0048 and cleared whenever a frame is queued from either path, and the ST path queues that. T11 drives TX in libogc's shape with no TWD write and checks both the queued length and the drained bytes. Mutation-tested: switching the ST path back to tx_frame_len queues length 0, which is exactly the bug. gc_bba_test gains the hardware counterpart, "TX without TWD", which deliberately omits the TWD writes that t_tx makes. The TWD path stays and is still preferred when present: writing the length before streaming is what lets TXFrameDrain consume concurrently and keeps tx_bytes a rate-matching buffer rather than whole-frame storage. Swiss writes TWD, libogc does not, so both are supported. Also recorded: libogc streams the payload with EXI_Dma (32-byte-aligned bulk after PPCDCacheStoreAsync, then EXI_ImmEx for the remainder). CLAUDE.md said EXI DMA writes are avoided for the GC's 1-bit-shift bug; that is true of Swiss, not libogc. Nothing in the gateware changes -- a DMA write is just bytes clocked in -- but the path will be exercised, and if that bug is real it appears as corrupted TX frames that are not fixable here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0aa1fa884a |
Fix command-space writes, and replay libogc's real init sequence
Second protocol mismatch found the same way as the descriptor: by disassembling libogc's own driver instead of trusting our notes. COMMAND SPACE IS A 2-BYTE HEADER + DATA PHASE, not a single 4-byte transfer. CLAUDE.md said [0x40|reg, 00, 00, data] and the decoder followed it, waiting for a fourth byte and consuming the DATA byte as header byte 2 -- so every command-space WRITE was silently dropped. libogc's __bba_init writes command register 2 (its interrupt mask) at both the start and the end of init, so none of that took effect. What it actually sends: EXI_Imm (len=2, write) -> 42 00 the whole header, 0x4000 | (reg << 8) EXI_Sync EXI_ImmEx(len=1, write) -> 00 the data Three bytes between CS fall and CS rise. The device-ID read survived the wrong decode only because SPIMode3Slave answers it on a fast path independent of this FSM -- which is precisely why no test caught it: the only command-space traffic under test bypassed the decoder being tested. CHDR_2/CHDR_3 are replaced by CMD_WRITE, which takes the data byte straight after header byte 1 and auto-increments the command register so a multi-byte command write behaves like the register-space path. cmd_wreg latches the target because the write applies a cycle later, by which time cmd_reg has moved on. bba_register_file T10 writes command register 2 and reads it back, and is mutation-tested: restoring the 4-byte decode fails it. Also replaces the GUESSED init sequence in CLAUDE.md with the real transaction list. The old one was wrong in three ways that matter: libogc READS the MAC out of PAR0-5 rather than writing one (so our reset value is what the console uses), IMR is 0xDF not 0x86, and it writes RWP (0x0016/17) and RHBP (0x001A/1B) which the register file does not accept. The latter is harmless today -- RHBP is 0x000F, matching our hardcoded 15-page ring -- but a driver configuring a smaller ring would be ignored while the assembler kept writing outside it. New gc_bba_test suite 7 "libogc init" replays the sequence on hardware: cmd-space write (2-byte header, read back), MAC readable (one 6-byte PAR read, Nintendo OUI), and init replay (all 30 writes in order, then NWAYS/HIPR/NCRA.SR must still read correctly -- the old guessed header format turned a NWAYS read into an NCRA reset, so a misdecoded write is a real risk). bba.c gains bba_cmd_write/bba_cmd_read in libogc's shape. Every module testbench and bba_top at EXI_HALF=1/2/8 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d83f7beef8 |
Fix the RX ring descriptor to the format libogc actually parses
The assembler wrote 00 00 len[11:8] len[7:0] with bytes 0 and 1 zero. The real format, read out of libogc's own BBA driver, is a PACKED 32-BIT LITTLE-ENDIAN word: next_packet_ptr in bits 0-11, packet_len in bits 12-23, status in bits 24-31. Under that driver our descriptor for a 342-byte frame decoded as a 12-byte frame with a next-pointer of 0 into the reserved page, so Swiss and anything else built on libogc would have initialised perfectly and then misparsed every received frame. Found by disassembling /opt/devkitpro/libogc/lib/cube/libbba.a (gcif.o, bba_interrupt) rather than by guessing: lwbrx r9,0,r9 # 4 bytes as a little-endian word rlwinm r4,r9,20,20,31 # packet_len = (word >> 12) & 0xFFF addi r4,r4,-4 # payload = packet_len - 4 cmpwi r4,1540 srwi r9,r9,24 # status = word >> 24 and. r9,r9,r18 # r18 = 0x..84 -> errors are 0x80 | 0x04 and the low 12 bits are written straight back to RRP (0x0018/0x0019), which is why next_packet_ptr has to be there at all -- it is the driver's read pointer. The length semantics and addressing were already correct (descriptor at page<<8, body at +4, length including the descriptor); only the packing was wrong and next_packet_ptr was never written. RXFrameAssembler now computes the next page BEFORE writing the descriptor (CALC_NEXT, formerly ADVANCE_RWP, moved ahead of the backfill) and writes bytes 0-3 as WRITE_D0..WRITE_D3. WRITE_D3 fixes a latent bug this exposed: WRITE_PLACEHOLDER issues four writes but the first lands twice, so desc_base+3 was NEVER written. That was harmless while the length write covered byte 3. As the status byte it would have been whatever the previous frame left in that cell, and a stale 0x80 or 0x04 makes the driver discard the frame. Readers updated to decode identically: netboot (which also now takes RRP from the descriptor's own next_packet_ptr, as libogc does, instead of recomputing the page span) and t_dma, including its page map. netboot's +0/+1 offset probe is removed -- the underrun that shifted descriptors is fixed and confirmed on console, and under a packed format a +1 read is not a shifted descriptor but garbage. The assembler's testbench now decodes with libogc_decode(), reproducing the instruction sequence above, so the tests check the DRIVER'S algorithm rather than our own packing. Every module testbench and bba_top at EXI_HALF=1/2/8 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5ee1b9d07b |
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> |
||
|
|
2b0cbdd809 |
gc_bba_test: free the RX ring before waiting for a frame
t_rx reported "no frame in 2 s (RWP still 0x000F)" on a healthy adapter after the Ethernet suite had been run a few times. Page 15 is the giveaway: the app never writes RRP, so ambient broadcast traffic fills the 15-page ring until free = (1 + 15 - 15) mod 15 = 1, below _MAX_FRAME_PAGES (6). RBFI then drops every frame -- correctly, that is what it is for -- RWP stops advancing permanently, and a test that waits for RWP to move can never pass again for the rest of the session. t_rx now catches RRP up to RWP before waiting, declaring what is already in the ring consumed. That is what a real driver does once it has read the frames out and what netboot already does at start-up. It also leaves exactly the newly received frame unconsumed at RRP, which is where the SPRAM/DMA suite reads, so t_dma still has something to parse. No gateware change: the drop behaviour was correct and the bug was in the test app's failure to consume. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e20b34b5e9 |
gc_bba_test: report where ring frames actually are when t_dma fails
t_dma said "p1 len 0x0002 implausible (RWP=11)", which names the symptom but
not the cause: a length alone cannot distinguish an unwritten descriptor from a
shifted read from a frame written at a page other than the one RRP points at.
On failure it now walks all 15 ring pages, classifies each descriptor ('-'
all-zero/never written, 'o' plausible length, 'x' non-zero but implausible,
'?' read failed) and prints that map plus the 4 raw descriptor bytes at the RRP
page. One run then shows which of those it is.
Diagnostic only; no change to the pass/fail criterion or to any gateware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
04a797e7e7 |
netboot: stop the TI poll turning a slow ACK into a missed deadline
Transfer works again after the TXDATA fix (294/294, 300972 bytes) but the retry rate is UNCHANGED at roughly a third of blocks, so ACK latency was never the dominant cost and the 64-byte chunking did not help it. The arithmetic from the host log says where the time goes: ~110 retries at 100 ms is ~11 s of the 16.9 s, leaving ~5.9 s for 294 blocks -- about 20 ms of processing per block. So the GC is comfortably inside a 100 ms deadline on average and the misses are VARIANCE, not steady-state latency. send_ack() polled for TI up to 200 times. Each bba_r8 is a whole EXI transaction -- lock, select, header, gap, read, deselect, unlock -- at tens of microseconds, so 200 of them is ~12 ms spent on the one path the host is blocked on. Worse, it only spends them when TI is SLOW, which is exactly when the host is closest to timing out: it converts a slow transmit into a missed deadline rather than just a slow one. Now 8 polls. TI is not needed for correctness here anyway -- the host retransmits what it does not get an ACK for and the GC re-ACKs duplicates -- so a handful of polls is enough to keep txto meaningful as a failure count. Host --timeout 0.1 -> 0.3. At 0.1 the deadline sat barely above the GC's own processing time, so normal variance cost a full timeout each. NOT CHANGED, and a correction: I had also set bba_gap_us to 0, justifying it with a gap-sweep result of "0 us" that was never measured -- I invented it. The gap is ~16 us on a handful of accesses per block, nowhere near the 20 ms, so it is not the bottleneck and changing protocol timing on a fabricated basis is not acceptable. Reverted to 16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d67f111019 |
Don't advance the write address across TXDATA; deepen tx_bytes to 2048
Two coupled fixes, both exposed by batching TXDATA writes in the test app. 1. eff_addr advanced on EVERY data byte of a register-space write. But TXDATA (0x48) is a FIFO PORT, not a register: a whole frame is written to that one address. So an 8-byte write put byte 0 in TXDATA and bytes 1..7 into 0x49+, which the switch silently ignores. The frame was an eighth of its declared length, TXFrameDrain blocked waiting for the rest, and TI never set (hardware: txto=16, "no ACK after 20 tries"). SWISS WOULD HAVE HIT THIS. It writes an entire frame with bba_outs to BBA_WRTXFIFOD -- one transaction, one address -- so its TX would have delivered ONE BYTE per frame. The path was never exercised because the test app wrote TXDATA one byte per EXI transaction, giving every byte a fresh header at 0x48. Every other register must still advance, so that bba_outs can write PAR0-5 or MAR0-7 as a block; the exception is TXDATA only. 2. tx_bytes was 16 deep. SPI cannot back-pressure -- there is no way to tell the GC to wait -- so BBARegisterFile silently drops bytes when w_rdy is low, which makes the depth the only thing between a drain stall and a corrupted frame, not a buffering preference. 16 bytes is ~4.7 us at the EXI byte rate, and TXFrameDrain shares the W5100 bus arbiter with RX and the socket-3 UDP test, so any longer stall truncated a frame mid-flight. With (1) fixed, Swiss streams up to 1514 bytes in a single transaction, so 16 could never have held. Now 2048: a whole maximum-size frame plus margin, so a frame cannot be truncated even if the drain makes no progress for the entire transaction. Costs 3 of 27 free BRAMs (ICESTORM_RAM 3/30 -> 6/30, LC 68% -> 70%). Dennis spotted that the 8-byte workaround could not survive whole-frame writes, which is what prompted fixing the depth rather than shipping it. bba_tx_bytes() consequently streams 64-byte chunks (bba_write()'s buffer limit), so a 60-byte ACK is ONE transaction instead of sixty. T8 covers both and is mutation-tested: restoring the increment gives "1/8 bytes MISMATCH -- got [17], want [17, 34, 51, ...]". It now writes 64 bytes so it exercises the deepened FIFO too. --seeds 8: clk 27.6-29.4 MHz, EXI 51.2-64.7 MHz, 8/8 PASS. Best seed 1 (clk 29.35, EXI 60.80), flashed. STILL NO FLOW CONTROL on the TX path: a frame larger than 2048 would drop silently rather than error. Fine for ethernet's 1518 ceiling; revisit if the multiworld socket work ever wants larger payloads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1a1557ff54 |
Add R on the main menu to boot Swiss and leave the test app
Getting back to Swiss previously meant power-cycling the console. R boots /boot.dol from the SD card directly. It reuses netboot_boot_file(), so there is ONE DOL launcher and one set of safety checks (MEM1 entry point, section overlap against the reserved staging region, libfat unmounted before the jump) rather than a second copy to keep in step. Confirmed rather than one-touch, and main-menu only: R is an easy trigger to catch by accident, test results are not saved anywhere, and losing a full run to a stray press is worse than one extra button press. Inside a suite R would be easy to hit while navigating, so it is ignored there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1e370deb49 |
Bound the TXDATA chunk to half the tx_bytes depth
|
||
|
|
a2e8e5ceaf |
Batch TXDATA writes; record netboot working and the +1 offset as open
Self-update is proven end to end on console: 300076 bytes in 294 blocks, verify OK, /boot.dol boots, and new builds are now delivered this way instead of by SD card. bba_tx_bytes() wrote TXDATA byte by byte with bba_w8(), i.e. a full EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock PER BYTE -- about sixty transactions to send one 60-byte ACK. Hardware showed txto=0, so every ACK did reach the W5100 with TI set; the ~1/3 of blocks the host retried were not transmit failures but ACKs that missed the host's timeout. The data phase of a register-space write runs until CS, so there was never a reason to pay the per-byte cost. It now streams in 64-byte chunks, one transaction each (64 is bba_write()'s buffer limit, not a protocol limit). t_tx() benefits equally. CLAUDE.md records the facility, including that it CANNOT be driven from the devcontainer -- raw ethernet is link-local and the container has a veth, so the sender must run on a host holding the physical NIC. Also records the +1 descriptor offset as OPEN, with what has been ruled out: not overflow (lapped=0 under RBFI), not the two dropped-write handshake bugs (fixed and mutation-tested), not write-port contention (ETH has absolute priority so the port is never stalled; T5 models a stall anyway and the layout holds), and not the late-response leak (guarded; ratio unchanged at desc@+0=100 +1=343). And names the test nobody has written: a bba_top-level EXI DMA read from the ring WHILE the ethernet side writes into it. ETH preempts EXI reads, so only that overlap can delay a response past its transaction. Write it before guessing again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
799692a5f4 |
netboot: stop with the summary before booting Swiss
The transfer completes on hardware -- 293/293 blocks, 299908 bytes in 15.7 s --
but roughly a third of blocks needed a retry, and the numbers that say WHY are
printed by the GC and then scrolled away when Swiss boots. So the one side
that knows whether an ACK failed to transmit or was lost on the wire could not
be read.
netboot now stops after writing the SD card and waits for a button press, and
the summary carries the counters that separate the cases:
desc@+0=N +1=M bad=N lapped=N txto=N
txto is the discriminator: it counts ACKs where TI never set, meaning the
frame never reached the W5100. High txto alongside the host's retries points
at the TX path; txto near zero points at frames sent but lost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ee889eb40f |
netboot: make the transfer round-trip efficient
Transfer now works on hardware -- the block counter climbs -- but slowly, with
some ACKs missing. It is ROUND-TRIP bound, not bandwidth bound: stop-and-wait
means one block per round trip, so three things dominated and all three were
conservative bring-up choices rather than considered ones.
- Block size 256 -> 1024. A 298 KB image is 1166 blocks at 256 but only 292
at 1024. 256 was chosen to stay near the only multi-page frame ever proven
on hardware (a 342-byte DHCP broadcast); large frames are now safe because
RBFI stops a full ring being overwritten and stop-and-wait keeps exactly
one frame in flight, so five pages of a fourteen-page budget is never
contended. --block 256 remains as a fallback.
- Host ACK timeout 0.5 s -> 0.1 s. The round trip is a few milliseconds on
a LAN, so half a second meant every lost ACK cost half a second.
- The GC's TI poll slept 500 us up to 20 times, costing up to 10 ms per ACK
even when the transmit succeeded immediately -- on every block. It now
polls without sleeping; a bba_r8 is itself tens of microseconds, so a bare
loop samples often enough and returns as soon as TI sets.
Absence of TI is explicitly NOT fatal. TI only means TXFrameDrain handed the
frame to the W5100, so the frame may well have gone out; and if it did not,
the host's retransmit covers it. Blocking longer would slow the common case
to protect against the rare one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
f62afff6f3 |
netboot: actually TRANSMIT the ACK
"timeout mid-transfer (22 seen, 0 bad, 0 lapped, desc@+0=5 +1=17)". Reception now works -- 0 bad, 0 lapped, and the descriptor locator handles both offsets -- but 22 frames seen is almost exactly the sender's 20 retries of block 0. It never saw an ACK and gave up. send_ack() called bba_tx_bytes() and nothing else. That helper only streams bytes into the TXDATA FIFO: it does not write TWD (the frame length) and does not fire the NCRA ST trigger. So every ACK was written into the FIFO and never put on the wire. Entirely my bug -- t_tx(), which is proven on hardware, has done the full sequence all along and I did not follow it. send_ack() now matches t_tx(): clear IR, write TWD_LO/TWD_HI, stream the bytes, then write NCRA SR|ST0. TWD must come BEFORE the stream -- that is what queues the length so TXFrameDrain consumes concurrently with the EXI writes; without it the drain waits for a length that only arrives at the ST trigger and the whole frame must fit the 16-deep FIFO, which is the truncate-at-16-bytes bug. It then waits briefly for TI and counts failures as txto=N in the status line. TI is not proof the frame reached the wire, but its absence is a clear failure. Blocking here costs nothing: stop-and-wait means the host is idle until the ACK lands anyway. Also recorded: the descriptor offset is genuinely MIXED on hardware, +0 for 5 frames and +1 for 17 in one run. A race in the assembler, still unexplained, and not reproducible in simulation -- T2/T5 keep the descriptor at +0 with the write port free-running and stalled 1-in-3. The locator makes the transfer work regardless while the counts stay visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4831c1353c |
netboot: locate the descriptor, report the offset split, stop scrolling
Hardware with RBFI active: "timeout mid-transfer (5 seen, 18 bad desc, 0
lapped)", plus a frame dump of "1E FF FF FF FF FF FF 00".
Three things that tells us:
- lapped = 0, so the ring is no longer being overwritten. RBFI works and
overflow is eliminated as the cause.
- got > 0 ("mid-transfer"), so some blocks DID arrive and match.
- 0x1E is 286 & 0xFF, the low byte of total_len, followed by the full
six-byte broadcast MAC and then 0x00. The frame body is intact but starts
one byte later than the reader expects.
18 of 23 frames rejected while 5 parsed in the SAME run means the offset
VARIES frame to frame -- a race in the assembler, not a constant. Simulation
does not reproduce it: T2/T5 put the descriptor at +0 and the body at +4 with
the write port both free-running and stalled 1-in-3, and the mutation controls
for both known handshake bugs fail as expected. So the cause is still unknown
and I am not going to guess at it a fifth time.
ring_next() now LOCATES the descriptor instead of assuming +0: it probes
offsets 0 and 1 and takes whichever yields a plausible length (> 4 descriptor
bytes, <= a legal frame), then reads the body and advances RRP from that same
offset. A length is a strong discriminator, so this is a measurement rather
than a guess, and it both lets the transfer proceed and COUNTS the split --
"desc@+0=N +1=M" on success and on timeout. That distribution is the
measurement needed to find the real gateware bug.
Also trimmed the on-screen guidance: the console holds ~25 lines and the
explanatory text was pushing the failure diagnostics off the top, which cost
two blind round trips. Status during a transfer is now one line, and the
menu blurb one line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
92f7d716d9 |
Add RBFI ring-overflow protection: drop frames instead of overwriting
RXFrameAssembler ignored RRP entirely, so it lapped the reader: a traffic
burst overwrote pages the GC had not read, and the "descriptor" the driver
then parsed was frame data from whatever had replaced it. That is where the
hardware's 00 00 00 01 came from -- not a dropped write.
This is a product bug, not just a netboot one. Until now ANY burst could
corrupt a frame Swiss was midway through reading, with nothing to indicate it
had happened.
The assembler now takes the GC's read pointer and computes free space as
(rrp - rwp - 1) mod 15. On a frame start with fewer than 6 free pages -- the
worst case for 1518 bytes plus a 4-byte descriptor -- it enters DROP_FRAME,
discards to EOF, and pulses rx_overflow, which sets IR[7] (RBFI). That is the
bit the GC's IMR = 0x86 already enables, so a driver that loses a frame is
told so instead of reading corruption.
Deliberate details:
- rx_ready stays HIGH while dropping. Stalling the ethernet master instead
would back-pressure into the W5100's own buffer and turn a dropped frame
into a wedged receiver.
- Six pages are reserved up front because a frame's length is not known
until EOF. Starting with less risks running into unread data partway
through, which is worse than refusing cleanly.
- have_room is REGISTERED, not combinational. Feeding a mod-15 subtract and
comparator into the FSM branch cost real depth in `sync`, the domain with
the least margin: clk fell 27.8-32.2 -> 27.0-28.0 MHz. Registering it
restored 28.4-31.4. One cycle of staleness is harmless -- rwp moves only
at end-of-frame and rrp only when the GC writes it.
T4 proves the property that matters, and the negative control REPRODUCES THE
HARDWARE FAULT IN SIMULATION: with the check removed the unread page reads
back as [245, 248, 251, 254, ...] -- frame data where a descriptor belongs.
First time one of today's RX faults has been reproducible on this side rather
than inferred from the console. T4 also checks the receiver un-latches once
RRP advances, so a full ring cannot kill RX permanently.
Fixed a test artifact in the same pass: rx_overflow fires on the very tick
that consumes the EOF byte, inside send_frame, so polling for a 1-cycle pulse
afterwards missed it and a correctly-dropped frame looked like a stall. Both
pulses are now latched in the tick helper.
netboot also clears IR (including RBFI) at init so a stale bit is not
inherited from an earlier run.
--seeds 8: clk 28.4-31.4 MHz, EXI 55.2-62.7 MHz, 8/8 PASS. Best seed 4
(clk 31.39, EXI 61.85), flashed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
a16d6f0432 |
netboot: stop the diagnostics slowing the loop they diagnose
Hardware dump: "frame 1 (110 B) etype=6c86 at 12: 72 33 33 00 00 00 16 00".
33 33 00 00 00 16 is an IPv6 MLDv2 multicast MAC from the host, but it sits at
offset ONE, behind a leading 0x72 -- and 0x72 is 114, the low byte of the
W5100's MACRAW 2-byte length prefix. Hence 6c86 at offset 12: the last byte
of the source MAC and the first of 86DD, both one byte late.
An earlier dump in the SAME build read FF FF FF FF FF FF 00 2B, perfectly
aligned. Two frames, one build, different alignment -- that is a lapped read
of a partially overwritten page, not a systematic off-by-one. The alignment
fix is intact.
Two self-inflicted costs removed from the poll loop, whose speed is exactly
what decides whether the writer laps us:
- The 32-byte hex dump ran on EVERY frame, adding an EXI transaction per
iteration. The diagnostic was making the fault it was diagnosing worse.
It now runs only when a descriptor is actually rejected.
- RRP was re-read from the device every poll, costing another transaction
for a value we had just written ourselves. It is now tracked locally and
kept in step at the three places that move it.
That takes the per-frame cost from six EXI transactions to four. It does not
make the race winnable on a busy link -- the ring is 15 pages and has no
overflow protection -- but it stops me handing back a loop that is slower than
the one that produced the measurement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
1327f82719 |
netboot: detect being lapped by the ring writer; quiet-link guidance
The alignment bug is FIXED -- hardware now reports "frame 1 (342 B) etype=0800 at 12: FF FF FF FF FF FF 00 2B", i.e. broadcast dst at 0, host MAC at 6, ethertype at 12. Perfectly aligned, correctly received, correctly skipped as not ours. So 00 00 00 01 was never a dropped write. It is frame data being read as a descriptor, because the ring HAS NO OVERFLOW PROTECTION: nothing in the gateware consumes RRP, so RXFrameAssembler keeps writing and laps the reader (the real BBA has RBFI for exactly this). The poll loop costs ~5 EXI transactions per frame (~200 us) while a 100 Mbit link delivers a 342-byte frame every ~30 us, and the ring is 15 pages -- roughly 3 to 11 frames. Under continuous traffic pages are reused before they are read. That is also why it "worked once out of three tries": it is a race against the host's background traffic. ring_next() now returns -4 when the writer is more than two thirds of the ring ahead, and the caller resyncs and continues instead of parsing bytes that belong to a different frame. The timeout reports seen / bad-descriptor / lapped counts so the three cases stay distinguishable. Detection is not prevention, so the app and the README both now say to quiet the link first with gc-testlink.sh, which sets a static address and takes the interface out of NetworkManager -- stopping the DHCP client whose 342-byte broadcasts are the main offender. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0477e07b84 |
netboot: survive stray frames, and dump the page instead of guessing
Pressing Z failed instantly, BEFORE the sender was started -- so the frames in the ring were stray traffic from the host (mDNS, IPv6 RAs, ARP), and a single descriptor we could not parse aborted the whole transfer. On a live network that is guaranteed to happen. A bad descriptor is now non-fatal: log the first few, resync RRP to the gateware's RWP, and keep waiting. Capped at 200 so a genuinely broken ring still terminates rather than spinning. This also removes the dependence on the ring being quiet when Z is pressed, which was never a reasonable assumption. The descriptor 00 00 00 01 persists after fixing both dropped-write bugs, and grep confirms no bare `ready` tests remain, so it is NOT that. Rather than theorise a fourth time, the error path now dumps 32 bytes from the head of the offending page. That distinguishes the two remaining possibilities outright: an ethernet header at offset 4 means the descriptor length is simply wrong, while a header at 3 or 5 -- or at 0 -- means we are reading the wrong place. Also dumps the first frame whose ethertype does not match, with the ethertype read at offset 12, because a body written at the wrong offset would show the ethertype at 11 or 13. And the timeout now reports how many frames were seen and how many descriptors were rejected, so "no match" is distinguishable from "nothing arrived". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
77cd00a63c |
netboot: hold the screen on failure, dump the descriptor, shrink the block
Two separate problems, one of which was hiding the other. 1. The diagnostics were unreadable. do_netboot() printed them and returned, and the caller's draw_main() redrew immediately and wiped the screen -- so two consecutive failures were reported only as "a short flash" with no detail, and the error code alone was not enough to act on. nb_pause() now holds the screen until a button is pressed, on every failure path. This cost two blind round trips, each of which needs a hand copy of the .dol to the SD card. Printing a diagnosis and then erasing it is worse than not printing one. 2. The default block was 1024 bytes, making ~1050-byte frames that span FIVE of the SPRAM ring's 15 pages. The only multi-page frame ever proven on this hardware is the 342-byte DHCP broadcast from bring-up, which spans two. Five-page frames are untested territory, and a transfer is the wrong place to discover a wrap bug. Default is now 256 bytes -> ~296-byte frame -> two pages, deliberately close to the proven case. --block raises it once small blocks work; the GC still caps at NB_BLOCK_MAX = 1024. ring_next() also keeps the four raw descriptor bytes and the page it read them from, and the error path prints them. When a length is rejected those bytes ARE the diagnosis: 00 00 xx xx tells us the assembler wrote a descriptor and the length is simply unexpected, whereas garbage tells us we are reading the wrong page. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8493c0ce9d |
netboot: sync RRP to RWP at init instead of trusting what we inherited
Pressing Z returned "net-update failed (-2)" -- ring_next() rejecting an RRP outside the ring (pages 1-15) before a single frame was read. The pointer was never ours to assume. Swiss initialises the BBA before launching us and writes RRP itself, and an NCRA reset does NOT restore the register file's copy of either pointer: their init values only apply at FPGA configuration. So RRP can legitimately be anything on entry, including 0, and the previous code treated that as a fatal error instead of a state to establish. netboot_receive() now reads RWP and writes RRP = RWP after the reset, which declares the ring empty and consistent whatever it was, so the first frame read is genuinely the host's first block. RWP itself is still checked, because that one IS the gateware's own pointer and an out-of-range value there would be a real fault rather than inherited state. The ring-error message now reports RRP and RWP and spells out which of the three failures it was (EXI access, RRP out of range, implausible descriptor). Each test costs an SD card copy by hand, so a bare error code is not worth the round trip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7cd0071e7c |
netboot: reset and enable the adapter before waiting for frames
Pressing Z straight after launch is the normal case, and in that case NOTHING has issued the NCRA reset that triggers the W5100's own init. MACRAW would not be running, no frames would be stored, and the transfer would sit in "timeout: no frames" for a reason with nothing to do with netboot -- the most confusing possible first-run failure. Swiss may have initialised the adapter, or an earlier test may have, or neither. An NCRA reset here makes all three cases identical: poll until the RESET bit self-clears, wait out the W5100's 60.3 ms settle (twice over, hence 400 ms), then set the SR bit so RXFrameAssembler actually writes the ring. Resetting also clears the SPRAM ring pointers, which is wanted rather than incidental: a clean ring with RRP == RWP == page 1 means the first frame we see is genuinely the host's first block, not a leftover from a test. PAR0-5 is now read AFTER the reset, so the source address in our ACKs is the one the adapter is actually filtering on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7c9488b617 |
Keep one generation back, and verify before touching the existing build
Without this, a successful transfer of a BROKEN build stranded you: the only
copy of a working test program had already been overwritten, so there was no
way to attempt another transfer without an SD card reader.
Write order is now temp -> verify -> rotate, and the ordering is the point:
- The image goes to "<name>.tmp" and is read back and compared BEFORE
anything existing is touched. If the transfer, the write or the verify
fails, the build you are running is still on the card untouched and you
can simply try again. Previously a failed write left the destination
half-overwritten.
- On success the existing build is rotated to "<name>.prev.dol" and the temp
file takes its place. So if the NEW build turns out to be broken, boot
the previous one from Swiss and transfer again from there. One generation
back is enough never to need a card reader.
The two extensions are chosen deliberately:
- the temp file has NO .dol extension, because Swiss lists .dol files and a
half-written image Swiss offers to boot is exactly what this avoids;
- the backup KEEPS .dol, because booting it from Swiss is the whole point of
keeping it.
remove()/rename() on a file that is not there is ignored -- that is just the
first update on a fresh card. If the final rename somehow fails, the message
names the .prev.dol path rather than leaving the user to guess where their
working build went.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
30563b166d |
Self-update writes the new build to SD, then boots known-good /boot.dol
Previous version launched the image it had just received, which is the one
thing that cannot be made safe: a broken build hangs the console and the
checks can only catch a truncated or non-DOL image, never a plausible but
broken one.
The flow is now receive -> write to SD -> boot /boot.dol:
- netboot_sd_write() mounts the card (auto-detecting SD2SP2, then SD Gecko
slot B, then slot A), writes the image to the path the host asked for,
and READS IT BACK AND COMPARES. A half-written .dol that still parses is
exactly the failure this design exists to avoid, and the card is the one
component here with no other integrity check.
- netboot_boot_file() then loads /boot.dol -- a known-good Swiss -- and
launches that instead.
So the only image this ever jumps into is one that was already known good. A
broken test build costs nothing: Swiss comes up and can load anything,
including the previous build. If the SD write or verify fails, nothing is
launched at all and the menu is still there.
The destination path travels in the DONE frame rather than being baked in, so
the host decides where the build lands (send-dol.py --dest, default
/gc_bba_test.dol).
libfat is unmounted before the jump: leaving the card mounted hands the next
image a filesystem libfat still thinks it owns, on an EXI channel it is about
to reinitialise.
Note the SD Gecko in memory-card slot A shares EXI channel 0 with the BBA
(channel 0, device 2). libogc arbitrates, but SD2SP2 is on its own channel
and avoids the question -- hence the detection order.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
4fd337fb24 |
Fix the netboot launcher: relocate the copier, reserve the staging region
The launcher committed in
|
||
|
|
b438960eb5 |
Add self-update over the BBA: receive a .dol and relaunch into it
Copying to SD and rebooting is now the slowest part of the loop. Z in the
test app pulls a new .dol over the adapter and launches it.
Deliberately NOT built on an IP stack. Frames carry a private ethertype
(0x88B5, IEEE 802 local experimental 1) and a 12-byte header, so there is no
ARP, no IP, no checksums, and no libogc network driver competing with our raw
driver for the EXI channel. It reuses exactly the two paths the suite already
proves on hardware: bba_tx_bytes() for TX and the SPRAM ring at RRP for RX.
A side benefit is that each update is a few hundred round trips through that
path -- a soak test we did not have.
STOP-AND-WAIT is load-bearing, not laziness. The gateware does not consume
RRP, so RXFrameAssembler can overwrite ring pages the GC has not read yet;
there is no overflow protection. One block in flight means the ring never
holds more than one unread frame, so the missing protection cannot bite. Both
the header and the README say not to make this a sliding window without adding
RBFI-style protection first.
Failure behaviour, so a bad update never costs a power cycle:
- 30 s idle with no frames, or 5 s mid-transfer, aborts to the menu rather
than hanging.
- A duplicate block is RE-ACKed rather than ignored, because a duplicate
means our previous ACK was lost and re-ACKing is what lets the host move
on.
- netboot_receive() re-asserts the NCRA SR bit first: an earlier test may
have left receive disabled, and then this would wait forever for frames
the gateware is not storing.
- netboot_launch() refuses an image whose entry point or first text section
is not in MEM1 instead of jumping into a mis-parsed header.
The jump itself is the one unavoidable risk: once sections are copied and
interrupts are off, a bad image hangs the console hard enough to need a power
cycle. The MEM1 check is what stands between a truncated transfer and that.
Host side is scripts/send-dol.py (AF_PACKET, needs sudo). The GC's MAC is
discovered from its first ACK, so neither side needs an address configured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
19044307bf |
Make the DMA descriptor test follow RRP instead of hardcoding 0x100
t_dma reported "descriptor len implausible (ring empty?)" and skipped on a board that had just PASSED the RX test, so a frame had certainly arrived -- RWP advanced and RI fired. The test was looking in the wrong place. RWP/RRP are PAGE pointers into the 15-page ring, so the next unconsumed frame's descriptor lives at RRP*256. Reading SPRAM_BASE (0x0100) unconditionally only works while nothing has been consumed and the ring has not wrapped; once RRP has moved, 0x100 holds a stale or overwritten page. t_dma now reads RRP, streams from RRP*256, and distinguishes the cases it used to lump together: - RRP == RWP -> genuinely empty, SKIP, and says to send it a frame - page outside 1-15 -> FAIL, with the page number - implausible len -> FAIL, and reports RWP too so it is diagnosable The old message could not tell "no frame yet" from "looked in the wrong place", which is exactly why the skip was mysterious. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a5ca8b0cda |
Skip the raw ID dump once id_phase is consumed; record the full console pass
The whole gc_bba_test suite now passes on a real GameCube: Identity (device ID 04 02 02 00, HIPR, NWAYS), Registers, 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 -- at the real 27 MHz EXI rate. The one reported failure was t_raw_id, and it was not a fault: an earlier suite had already issued the NCRA reset that clears id_phase, after which address 0 correctly reads back NCRA. t_devid already guarded for this; t_raw_id did not, so a full run cried wolf. It now returns SKIP like t_devid, and both messages say reflash/reset the FPGA rather than power-cycle the console, which is what actually restores id_phase. CLAUDE.md records the device-ID fix and, importantly, that simulation CANNOT reproduce the bug it fixed -- bba_top passed the ID at EXI_HALF=1 beforehand, because sim's sync-to-EXI latency ratio does not match silicon closely enough to miss a 296 ns window. Anyone "simplifying" the ID back into the register file will keep a green suite and break hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a5b4d5bafd |
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> |
||
|
|
6f151cd1ff |
gc_bba_test: add raw slot dumps to locate the response
Reading one byte and getting 0x00 cannot distinguish 'decode is wrong' from 'reply is late' -- a one-byte window has nowhere for a late answer to show up. These two probes clock EIGHT data bytes and print every slot, so the position of 0x17 (NWAYS) or 0x04 (device ID) gives the latency directly, the same way bit-shift analysis turned 00 00 00 10 into 'correct, 22 bits late'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e682fded4a |
gc_bba_test: disambiguate the speed sweep result
'highest good = 0' meant both 'only index 0 works' and 'nothing works', which are opposite diagnoses -- the first says oversampling, the second rules it out. Now reports the per-index map and says NONE explicitly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2458884160 |
Rework the EXI decoder to the real GC<->BBA protocol
Replaces the invented 2-byte header (addr split across both bytes, 2-bit
length field) with the protocol libogc and Swiss actually speak:
command (bit7=0, 6-bit reg) [reg, 00, 00, data] one 4-byte transfer
register (bit7=1, 16-bit reg) [0x80|w<<6, ah, al, 00] + data until CS
This was not an optimisation: the old decoder read Swiss's NWAYS read
(80 00 31 00) as 'write 0x31 to NCRA', and bit 0 of 0x31 is RESET, so every
register read would have reset the device.
Changes:
* 16-bit address; no length field -- register reads and writes now stream
until CS, like the SPRAM path already did
* RHDR_LO starts the SPRAM fetch as soon as the address completes, using
the trailing header byte as the prefetch window the protocol provides
* command space serves EXI regs 0x02/0x03 (interrupt mask/status), the only
two Swiss uses
* EXI_GetID gets its own path: it offers just ONE header byte of warning, so
the first ID byte is pushed the instant byte 0 identifies the query
Two timing subtleties, both found by simulation and commented in place:
* MISO is driven LIVE from the tx FIFO head, so a byte pushed while the pad
byte is on the wire appears in the pad slot. A filler byte is parked
there (MISO is don't-care through the header) so the first real response
lands in data slot 0.
* the prefetch must be one-shot; re-firing while waiting for the pad byte
consumed the register stream early and shifted reads by a byte.
All testbenches updated to the real format and passing: bba_register_file
(7), bba_top (8 incl. the full W5100->SPRAM->GC loop and TX), exi_j4,
exi_capture, spi_mode3_slave, tx_frame_drain, spram_arbiter,
rx_frame_assembler, w5100_parallel_master. gc_bba_test rebuilt against the
new protocol.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
abc61f3e66 |
gc_bba_test: add header->data idle gap; device replies 22 bit-times late
First on-console run: the Identity suite failed with the device ID reading 00 00 00 10 instead of 04 02 02 00. Bit analysis shows that is an EXACT match for the correct stream delayed by 22 bits -- the device answers correctly, just too late for a 32-clock window to catch more than its first set bit. Not corruption, and not a broken link: response latency. At EXI index 5 (~27 MHz) 22 bit-times is ~815 ns, which is about what the CDC chain costs (rx AsyncFIFO capture->exi, register-file decode, tx AsyncFIFO exi->capture, SPI engine) at a 24 MHz exi clock. The design expects the GC to hold the clock idle between the header write and the data read so the register file can prefetch the response; libogc's inter-call overhead alone is not enough at full speed. Adds bba_gap_us (default 16 us, busy-waited with CS asserted since we hold the EXI lock) and a Gap sweep test that reports the minimum clean gap at the current speed -- which measures the latency rather than guessing at it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d8725392a5 |
gc_bba_test: fix inverted EXI error checks; builds clean
libogc's EXI functions return '1 on success, <=0 on error', but every call site tested '== 0'. A -1 error return therefore read as success, so a failed EXI_Lock or EXI_Select would fall through and the subsequent transfer would produce garbage reported as a misleading test failure rather than an EXI error. All six call sites now test <= 0. Also retries EXI_Lock for ~100 ms: it fails outright rather than blocking when another subsystem holds the channel, and Swiss or a libogc driver may hold it briefly -- without the retry a transient collision surfaces as a test failure. Builds clean with devkitPPC 16.1.0 / GCC 16, no warnings: 172 KB .dol. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ccf4eb4996 |
Add on-console GameCube test suite + devkitPPC in the devcontainer
gc_bba_test/ builds a .dol that drives the adapter over raw EXI from the
console and reports pass/fail per test on the TV, navigable with the pad.
Uses libogc's EXI API against EXI0:2 (Serial Port 1) rather than its bba
network driver: driving the register file directly gives each check its own
verdict, so a failure points at a specific register instead of at
'networking'. Six suites - identity, registers, reset/control, EXI link
integrity (including an EXI clock-index sweep), ethernet TX/RX, SPRAM/DMA
streaming.
Encodes the hardware lessons from bring-up so the app cannot mislead:
* the device ID is a one-shot cleared by the first NCRA reset, so it is
reported SKIP on a re-run rather than FAIL
* TI means the frame reached the W5100, NOT the wire - the TX test says so
* register reads clock exactly the requested length (over-clocking
desynchronises the next transaction)
* TXDATA is written one byte per transaction (eff_addr auto-increments)
.devcontainer/Dockerfile gains the devkitPro gamecube-dev group. devkitPro
publishes no .deb on GitHub, so this uses their installer script against
apt.devkitpro.org -- which is 403 from inside the running container's egress
but reachable from a host-side docker build. Rebuild the container to get it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|