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>
This commit is contained in:
+26
-14
@@ -420,17 +420,16 @@ if __name__ == "__main__":
|
||||
return miso
|
||||
|
||||
async def exi_read(ctx, addr, length):
|
||||
"""EXI immediate read: 2-byte header, clock-idle gap, then `length` bytes."""
|
||||
hdr0 = (addr >> 6) & 0x7F
|
||||
# The header length field is only 2 bits ([1:0]); mask it so a long
|
||||
# (DMA) read doesn't overflow length-1 into the addr[5:0] bits. For
|
||||
# SPRAM reads the field is ignored anyway — the stream runs until CS.
|
||||
hdr1 = ((addr & 0x3F) << 2) | ((length - 1) & 0x3)
|
||||
"""Register-space read, REAL protocol (docs/exi_protocol_real.md):
|
||||
4-byte header [0x80, ah, al, 0x00] then a data phase that runs until
|
||||
CS. The trailing 0x00 is the device's prefetch window."""
|
||||
ctx.set(dut.exi_cs_n, 0)
|
||||
ctx.set(dut.exi_clk, 1)
|
||||
await ctx.tick("capture").repeat(HALF)
|
||||
await spi_byte(ctx, hdr0)
|
||||
await spi_byte(ctx, hdr1)
|
||||
await spi_byte(ctx, 0x80)
|
||||
await spi_byte(ctx, (addr >> 8) & 0xFF)
|
||||
await spi_byte(ctx, addr & 0xFF)
|
||||
await spi_byte(ctx, 0x00)
|
||||
# EXI_Imm clock-idle gap: the core decodes the header and prefetches
|
||||
# responses into the tx FIFO before the GC clocks the data phase.
|
||||
await ctx.tick("capture").repeat(HALF * 12)
|
||||
@@ -440,19 +439,32 @@ if __name__ == "__main__":
|
||||
return result
|
||||
|
||||
async def exi_write(ctx, addr, data):
|
||||
"""EXI immediate write: 2-byte header then the data bytes."""
|
||||
hdr0 = 0x80 | ((addr >> 6) & 0x7F)
|
||||
hdr1 = ((addr & 0x3F) << 2) | (len(data) - 1)
|
||||
"""Register-space write: 4-byte header [0xC0, ah, al, 0x00] + data."""
|
||||
ctx.set(dut.exi_cs_n, 0)
|
||||
ctx.set(dut.exi_clk, 1)
|
||||
await ctx.tick("capture").repeat(HALF)
|
||||
await spi_byte(ctx, hdr0)
|
||||
await spi_byte(ctx, hdr1)
|
||||
await spi_byte(ctx, 0xC0)
|
||||
await spi_byte(ctx, (addr >> 8) & 0xFF)
|
||||
await spi_byte(ctx, addr & 0xFF)
|
||||
await spi_byte(ctx, 0x00)
|
||||
for b in data:
|
||||
await spi_byte(ctx, b)
|
||||
ctx.set(dut.exi_cs_n, 1)
|
||||
await ctx.tick("capture").repeat(HALF)
|
||||
|
||||
async def exi_devid(ctx):
|
||||
"""EXI_GetID: write 0x0000 (two bytes) then clock four read bytes."""
|
||||
ctx.set(dut.exi_cs_n, 0)
|
||||
ctx.set(dut.exi_clk, 1)
|
||||
await ctx.tick("capture").repeat(HALF)
|
||||
await spi_byte(ctx, 0x00)
|
||||
await spi_byte(ctx, 0x00)
|
||||
await ctx.tick("capture").repeat(HALF * 12)
|
||||
out = [await spi_byte(ctx, 0x00) for _ in range(4)]
|
||||
ctx.set(dut.exi_cs_n, 1)
|
||||
await ctx.tick("capture").repeat(HALF)
|
||||
return out
|
||||
|
||||
# ── W5100 indirect-bus slave model (drives w5100_data_i) ─────────────
|
||||
# Pre-loads a known MACRAW packet in the RX buffer so we can verify the full
|
||||
# ethernet→SPRAM→GC path. Same protocol as the W5100ParallelMaster bench.
|
||||
@@ -522,7 +534,7 @@ if __name__ == "__main__":
|
||||
await ctx.tick("capture").repeat(20)
|
||||
|
||||
# T1: device ID — read 4 bytes from addr 0 → 0x04 0x02 0x02 0x00
|
||||
dev = await exi_read(ctx, 0x0000, 4)
|
||||
dev = await exi_devid(ctx)
|
||||
print(f"T1 device ID: {[f'0x{b:02X}' for b in dev]}")
|
||||
if dev != [0x04, 0x02, 0x02, 0x00]:
|
||||
errors.append(f"T1 device ID: got {dev}")
|
||||
|
||||
Reference in New Issue
Block a user