Reset both SPRAM ring pointers to page 1, not 0
The DMA descriptor test reported "RRP page 0 outside ring pages 1-15" on a board whose RX path was working (t_rx passed: RWP advanced and RI fired), so the pointer itself was wrong. RXFrameAssembler's write pointer resets to _RX_PAGE_FIRST = page 1 -- the ring is pages 1-15 and page 0 holds the register region -- but the register file's r_rwp_lo and r_rrp_lo were plain Signal(8) and reset to 0. Two consequences: the register file and the assembler disagreed about where the ring starts, and a driver reading its first frame from RRP=0 gets the REGISTER page instead of the frame, which is at page 1. It hid because RRP == RWP == 0 looks self-consistently empty, so nothing complained until something actually read the pointer. RRP is driver-owned (the GC writes it after consuming frames) but its RESET value is ours, and RRP == RWP == 1 is the correct initial state: empty, pointing at the ring start. T1b locks down both reset values AND imports the assembler's constant to assert it matches, because two copies of a page number in two modules is exactly how the off-by-one got in. --seeds 8: clk 28.x-32.2 MHz, EXI 56-60 MHz, 8/8 PASS. Best seed 8, flashed. NOT fixed here, and worth separate attention: nothing in the gateware consumes RRP, so RXFrameAssembler can overwrite pages the GC has not read yet -- there is no ring-overflow protection (the real BBA has RBFI for this). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,10 @@ _RWP_LO = 0x16
|
||||
_RWP_HI = 0x17
|
||||
_RRP_LO = 0x18
|
||||
_RRP_HI = 0x19
|
||||
|
||||
# First page of the SPRAM RX ring (pages 1-15; page 0 is the register region).
|
||||
# Must match rx_frame_assembler._RX_PAGE_FIRST.
|
||||
_RX_PAGE_FIRST = 0x01
|
||||
_PAR0 = 0x20
|
||||
_PAR1 = 0x21
|
||||
_PAR2 = 0x22
|
||||
@@ -183,8 +187,19 @@ class BBARegisterFile(Elaboratable):
|
||||
r_ncra = Signal(8)
|
||||
r_imr = Signal(8)
|
||||
r_ir = Signal(8)
|
||||
r_rwp_lo = Signal(8)
|
||||
r_rrp_lo = Signal(8)
|
||||
# Both ring pointers reset to the ring's FIRST PAGE, not to 0.
|
||||
# The SPRAM ring is pages 1-15 (page 0 holds the register region), and
|
||||
# RXFrameAssembler's own write pointer resets to page 1 -- so a 0 here
|
||||
# disagreed with the assembler and, worse, told the GC driver to read
|
||||
# its first frame from page 0. The frame is at page 1; page 0 is the
|
||||
# register page. Hardware symptom: "RRP page 0 outside ring pages
|
||||
# 1-15" from the test app's DMA descriptor check, on a board whose RX
|
||||
# path was otherwise working.
|
||||
# RRP is driver-owned (the GC writes it after consuming frames), but
|
||||
# its RESET value is ours, and RRP == RWP == 1 is the correct initial
|
||||
# state: empty, and pointing at the start of the ring.
|
||||
r_rwp_lo = Signal(8, init=_RX_PAGE_FIRST)
|
||||
r_rrp_lo = Signal(8, init=_RX_PAGE_FIRST)
|
||||
# PAR0–5 reset to a valid Nintendo OUI MAC (00:09:BF:00:00:01) so the
|
||||
# device has a sane source MAC even before the GC driver programs its
|
||||
# own. PAR0 is the first MAC octet.
|
||||
@@ -720,6 +735,28 @@ if __name__ == "__main__":
|
||||
if result != []:
|
||||
errors.append(f"T1 device-ID query must push nothing, got {result}")
|
||||
print(f"T1 device-ID query pushes nothing: {result == []}")
|
||||
|
||||
# T1b: both ring pointers must come out of reset at the ring's FIRST
|
||||
# PAGE, not 0. RRP == RWP == 1 is "empty, pointing at the start of
|
||||
# the ring"; 0 points at the register page, and a driver that read its
|
||||
# first frame from RRP=0 would get the register region instead of the
|
||||
# frame -- which is what the DMA descriptor check caught on hardware.
|
||||
for name, a in (("RWP", _RWP_LO), ("RRP", _RRP_LO)):
|
||||
got = await exi_read(ctx, a, 1)
|
||||
if got != [_RX_PAGE_FIRST]:
|
||||
errors.append(f"T1b {name} reset: expected "
|
||||
f"[{_RX_PAGE_FIRST}], got {got}")
|
||||
print(f"T1b {name} at reset: {got} (want [{_RX_PAGE_FIRST}])")
|
||||
|
||||
# ...and the constant must not drift from the assembler's, which owns
|
||||
# the write pointer. Two copies of a page number is exactly how the
|
||||
# off-by-one got in.
|
||||
from exi_bba.rx_frame_assembler import _RX_PAGE_FIRST as _ASM_FIRST
|
||||
if _RX_PAGE_FIRST != _ASM_FIRST:
|
||||
errors.append(f"T1b ring-first-page mismatch: register file "
|
||||
f"{_RX_PAGE_FIRST} vs assembler {_ASM_FIRST}")
|
||||
print(f"T1b ring first page agrees with assembler: "
|
||||
f"{_RX_PAGE_FIRST == _ASM_FIRST}")
|
||||
await ctx.tick("sync").repeat(4)
|
||||
|
||||
# T2: Write and read back PAR0-PAR3
|
||||
|
||||
Reference in New Issue
Block a user