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>
SP1 DO/DI naming is console-perspective (verified against SD Gecko and
SD2SP2 wiring): pin 6 DO = GC data out = adapter MOSI input, pin 9 DI =
GC data in = adapter MISO output. The design doc's SP1 table had the
roles reversed and the schematic followed it, putting the FPGA's MISO
driver on the GC-driven DO line (bus contention).
Fix keeps all PCB routing identical:
- Swap net names EXI_MOSI(_RAW) <-> EXI_MISO(_RAW) across exi/Fpga/root
sheets and the PCB (also renames the D2 PUSB3AB6 channel assignments).
- Swap R21 (22R) <-> R25 (33R) identities between the two footprint
positions so 33R stays on the FPGA-driven MISO line, 22R on the MOSI
input.
- Correct the SP1 pin table + MISO-tristate note in
docs/gc_bba_fpga_design.md (root cause).
Resulting board pin map (for the future board PCF): EXI_MOSI = FPGA
pin 4 (IOB_8a, input), EXI_MISO = pin 3 (IOB_9b, output).
Verified: ERC 0, DRC 0 errors with schematic parity 0, netlist shows
J3.6(DO)->R21->pin 4 and J3.9(DI)->R25->pin 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>