Compat TODO items 3 and 4. Every register libogc2 writes now stores writes and
reads back, with the documented hardware reset defaults: NCRB (0x01), BP
(0x0A/0B), TLBP (0x0C/0D), RXINTT (0x14/15), RHBP (0x1A/1B), NWAYC (0x30,
default 0x84), GCA (0x32), MISC (0x3D, default 0x3C), MISC2 (0x50). BP and
RHBP reset to OUR layout (page 1, page 15) so a driver reading them back
without writing sees something consistent with where the ring actually is.
Previously all of these read 0x00 and dropped writes, so a driver doing
read-modify-write or sanity-checking a default saw zero -- and libogc2 does
read NWAYC before writing it.
Deliberately still reading 0x00: LTPS, LRPS, TWP, TRP, TXFIFOCNT. Those are
STATUS registers, not configuration, so storing what was written to them would
be a lie rather than a default. Drivers read LRPS/LTPS only on error bits and
0x00 reads as "no error", which is truthful for a device that does not detect
them.
NCRB, BP and RHBP are also exported as BBARegisterFile.ncrb/.bp/.rhbp for the
RX-filtering work (item 1) and the ring-geometry check (item 2). Nothing
consumes them yet -- the decode lands first so the behaviour that uses it is a
separate, testable change.
T15 checks the defaults, replays libogc2's twelve init writes in its order and
reads every one back, and asserts the NCRB export is 0x52 with PR=0, PM=0,
AB=1 -- i.e. that the real BBA delivers own-MAC + broadcast only, which is the
fact item 1 has to act on. Mutation-tested: dropping NWAYC's 0x84 default
fails it, as does dropping NCRB writes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Records the differences between this implementation and what real GC software
expects, with the evidence and the driver source for each, so they are worked
through rather than rediscovered. Open items: no hardware RX filtering,
hardcoded ring geometry, NCRB undecoded, and registers that read 0x00 where
real hardware has non-zero defaults. Also records what is deliberately NOT to
be implemented (the device-initiated challenge/response) and that two of our
registers (TWD, HIPR) do not exist on real hardware.
Notes explicitly that games use the official Nintendo SDK driver, which none of
the three readable drivers is, so "no open driver does this" is weaker evidence
than "the hardware has no such register".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Read from the two drivers that will actually talk to this device: libogc
(libdb/uIP/bba.c) and Swiss (cube/patches/bba/bba.c). The implemented 2-byte
header with a 2-bit length field does not match either.
The real protocol has two address spaces selected by bit 7 of byte 0, and no
length field at all -- the data phase runs until CS:
COMMAND (6-bit reg) one 4-byte readwrite; response in byte 3
REGISTER (16-bit reg) [0x80|w<<6, reg>>8, reg&0xFF, 0x00] + data phase
NCRA, IR, NWAYS, RWP, RRP and PAR all live in the register space. The
mismatch is destructive rather than merely non-functional: Swiss reading
NWAYS sends 80 00 31 00, which the current decoder reads as 'write 0x31 to
NCRA' -- and bit 0 of 0x31 is RESET. Every register read would reset the
device. The device-ID query is the one case where the two formats coincide,
which is exactly why enumeration worked on hardware and nothing else would
have.
Also explains the 22-bit response latency measured on console: the trailing
0x00 byte in the real header is the device's prefetch window, and the current
format spends those bits on the length field, leaving zero slack. Adopting
the real format is required for compatibility anyway and recovers ~296 ns of
the ~815 ns deficit.
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>