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>
This commit is contained in:
@@ -1219,6 +1219,68 @@ parallel master is the default — see "W5100 vs W5500".)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## RX Ring Descriptor — the format GC software actually expects
|
||||||
|
|
||||||
|
✅ **Corrected 2026-10-10 from libogc's own driver.** The 4 bytes at each ring
|
||||||
|
page start are a **packed 32-bit little-endian word**, not a byte-wise layout:
|
||||||
|
|
||||||
|
| bits | field |
|
||||||
|
|---|---|
|
||||||
|
| 0–11 | `next_packet_ptr` — the PAGE the NEXT frame starts at |
|
||||||
|
| 12–23 | `packet_len` — total bytes, **including** these 4 |
|
||||||
|
| 24–31 | `status` — `0x80` and `0x04` are errors; `0x00` is good |
|
||||||
|
|
||||||
|
```
|
||||||
|
Byte 0: next_packet_ptr[7:0]
|
||||||
|
Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8]
|
||||||
|
Byte 2: packet_len[11:4]
|
||||||
|
Byte 3: status
|
||||||
|
```
|
||||||
|
|
||||||
|
⚠️ **This was WRONG until 2026-10-10 and nothing we had built could have
|
||||||
|
caught it.** The assembler wrote `00 00 len[11:8] len[7:0]` and every reader we
|
||||||
|
had — `netboot`, `t_dma`, the testbenches — was written by us to match it, so
|
||||||
|
the whole stack was self-consistent and green while being incompatible with the
|
||||||
|
only driver that matters. The error was found by disassembling
|
||||||
|
`/opt/devkitpro/libogc/lib/cube/libbba.a` (`gcif.o`, `bba_interrupt`):
|
||||||
|
|
||||||
|
```
|
||||||
|
lwbrx r9,0,r9 # the 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 # ... must be <= 1540
|
||||||
|
srwi r9,r9,24 # status = word >> 24
|
||||||
|
and. r9,r9,r18 # r18 = 0x..84 -> errors are 0x80 | 0x04
|
||||||
|
lwz r9,cur_descr # and the low 12 bits go STRAIGHT to RRP:
|
||||||
|
# byte 0 -> 0x0018 (RRP_LO)
|
||||||
|
rlwinm r9,r31,24,28,31 # bits 11:8 -> 0x0019 (RRP_HI)
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the old format a 342-byte frame (`00 00 01 5A`) decodes as
|
||||||
|
`packet_len = 16` → an 12-byte frame, with `next_packet_ptr = 0` pointing at
|
||||||
|
the reserved page. The length semantics and addressing were already right
|
||||||
|
(descriptor at `page<<8`, body at `+4`, length includes the descriptor); only
|
||||||
|
the packing was wrong, and `next_packet_ptr` was never written at all.
|
||||||
|
|
||||||
|
**Lesson worth keeping:** a green suite proves self-consistency, not
|
||||||
|
compatibility. For anything an external driver parses, check it against that
|
||||||
|
driver — the binary is on disk and `objdump` settles in minutes what guessing
|
||||||
|
could not. This is the third time a *guessed* interface cost real hardware
|
||||||
|
debugging (after the EXI header format and the Mode 2/3 mix-up).
|
||||||
|
|
||||||
|
`rx_frame_assembler`'s testbench now decodes descriptors with
|
||||||
|
`libogc_decode()`, which reproduces the instruction sequence above, so the
|
||||||
|
tests check the driver's algorithm rather than our own packing.
|
||||||
|
|
||||||
|
⚠️ **The status byte must be written EXPLICITLY** (`WRITE_D3`). The placeholder
|
||||||
|
issues four writes but the first lands twice, so `desc_base+3` is never
|
||||||
|
written; that was harmless while the length write covered byte 3. Left
|
||||||
|
unwritten it hands the driver whatever was in that SPRAM cell, and a stale
|
||||||
|
`0x80` or `0x04` discards the frame as an error. Simulation flags it as an
|
||||||
|
unwritten cell; real SPRAM has no such tell.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## SPRAM Notes
|
## SPRAM Notes
|
||||||
|
|
||||||
- iCE40UP5K has 128 KB SPRAM (SB_SPRAM256KA, 16-bit wide).
|
- iCE40UP5K has 128 KB SPRAM (SB_SPRAM256KA, 16-bit wide).
|
||||||
|
|||||||
@@ -553,14 +553,28 @@ Each page is 256 bytes. A received frame may span multiple pages.
|
|||||||
|
|
||||||
**Frame descriptor (first 4 bytes of first page):**
|
**Frame descriptor (first 4 bytes of first page):**
|
||||||
|
|
||||||
|
A **packed 32-bit little-endian word**, not a byte-wise layout:
|
||||||
|
|
||||||
```
|
```
|
||||||
Byte 0: LRPS value (Last Received Packet Status — set to 0x00 or actual status)
|
bits 0–11 : next_packet_ptr — the PAGE at which the NEXT frame starts
|
||||||
Byte 1: 0x00
|
bits 12–23 : packet_len — total bytes, INCLUDING these 4
|
||||||
Byte 2: frame_length[15:8] (big-endian, includes descriptor bytes)
|
bits 24–31 : status — 0x80 and 0x04 are errors; 0x00 means good
|
||||||
Byte 3: frame_length[7:0]
|
|
||||||
Bytes 4+: raw ethernet frame data (DA, SA, EtherType, payload, FCS)
|
Byte 0: next_packet_ptr[7:0]
|
||||||
|
Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8]
|
||||||
|
Byte 2: packet_len[11:4]
|
||||||
|
Byte 3: status
|
||||||
|
Bytes 4+: raw ethernet frame data (DA, SA, EtherType, payload)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
⚠️ Read out of libogc's own BBA driver (`libbba.a`, `gcif.o`,
|
||||||
|
`bba_interrupt`) — `lwbrx` loads the four bytes as a little-endian word,
|
||||||
|
`rlwinm r4,r9,20,20,31` takes `packet_len = (word >> 12) & 0xFFF`, `srwi
|
||||||
|
r9,r9,24` takes the status, and the low 12 bits are written straight back to
|
||||||
|
`RRP`. This spec previously said `00 00 len_hi len_lo`, which that driver
|
||||||
|
decodes as a 12-byte frame with a next-pointer of 0 for a 342-byte frame. Do
|
||||||
|
not "simplify" it back to a plain big-endian length.
|
||||||
|
|
||||||
**Flow:**
|
**Flow:**
|
||||||
|
|
||||||
1. Wait for `W5500SPIMaster` to signal frame available (`rx_sof` pulse).
|
1. Wait for `W5500SPIMaster` to signal frame available (`rx_sof` pulse).
|
||||||
|
|||||||
+6
-1
@@ -626,8 +626,13 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
rwp = await exi_read(ctx, 0x16, 1) # RX write pointer (page)
|
rwp = await exi_read(ctx, 0x16, 1) # RX write pointer (page)
|
||||||
total_len = len(RX_FRAME) + 4
|
total_len = len(RX_FRAME) + 4
|
||||||
|
RX_NEXT_PAGE = 2 # one-page frame written at page 1
|
||||||
got = await exi_read(ctx, 0x0100, total_len) # descriptor + frame
|
got = await exi_read(ctx, 0x0100, total_len) # descriptor + frame
|
||||||
want = [0x00, 0x00, (total_len >> 8) & 0xFF, total_len & 0xFF] + RX_FRAME
|
# The descriptor is a packed little-endian word, as libogc's driver
|
||||||
|
# reads it: next_packet_ptr[11:0] | packet_len[23:12] | status[31:24].
|
||||||
|
# See the format note in rx_frame_assembler.py.
|
||||||
|
_desc_word = ((total_len & 0xFFF) << 12) | (RX_NEXT_PAGE & 0xFFF)
|
||||||
|
want = [(_desc_word >> (8 * i)) & 0xFF for i in range(4)] + RX_FRAME
|
||||||
print(f"T6 RWP=0x{rwp[0]:02X} (want 0x02)")
|
print(f"T6 RWP=0x{rwp[0]:02X} (want 0x02)")
|
||||||
print(f"T6 SPRAM[0x100]: {[f'0x{b:02X}' for b in got]}")
|
print(f"T6 SPRAM[0x100]: {[f'0x{b:02X}' for b in got]}")
|
||||||
print(f"T6 expected : {[f'0x{b:02X}' for b in want]}")
|
print(f"T6 expected : {[f'0x{b:02X}' for b in want]}")
|
||||||
|
|||||||
+135
-31
@@ -11,17 +11,50 @@ Page wrap: after 0x0F → 0x01 (skip 0x00).
|
|||||||
|
|
||||||
Frame descriptor (4 bytes at page start)
|
Frame descriptor (4 bytes at page start)
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
Byte 0: LRPS (last received packet status) — 0x00
|
A PACKED 32-BIT LITTLE-ENDIAN WORD, not a byte-wise layout:
|
||||||
Byte 1: 0x00
|
|
||||||
Byte 2: total_length[15:8] (big-endian; includes 4 descriptor bytes)
|
bits 0–11 : next_packet_ptr — the PAGE at which the NEXT frame starts
|
||||||
Byte 3: total_length[7:0]
|
bits 12–23 : packet_len — total bytes, INCLUDING these 4
|
||||||
|
bits 24–31 : status — errors are 0x80 and 0x04; 0x00 means good
|
||||||
|
|
||||||
|
so the four bytes at the page start are
|
||||||
|
|
||||||
|
Byte 0: next_packet_ptr[7:0]
|
||||||
|
Byte 1: packet_len[3:0] << 4 | next_packet_ptr[11:8]
|
||||||
|
Byte 2: packet_len[11:4]
|
||||||
|
Byte 3: status
|
||||||
Bytes 4+: raw ethernet frame
|
Bytes 4+: raw ethernet frame
|
||||||
|
|
||||||
|
⚠️ This is NOT a guess and must not be "simplified" back to a plain
|
||||||
|
big-endian length. It was read out of libogc's own BBA driver
|
||||||
|
(`/opt/devkitpro/libogc/lib/cube/libbba.a`, `gcif.o`, `bba_interrupt`), which
|
||||||
|
is the driver GC software actually runs:
|
||||||
|
|
||||||
|
lwbrx r9,0,r9 # load the 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 # ... which must be <= 1540
|
||||||
|
srwi r9,r9,24 # status = word >> 24
|
||||||
|
and. r9,r9,r18 # r18 = 0x...84, so errors are 0x80 | 0x04
|
||||||
|
|
||||||
|
and it writes the low 12 bits straight back to RRP as the next read pointer:
|
||||||
|
|
||||||
|
lwz r9,cur_descr # byte 0 -> register 0x0018 (RRP_LO)
|
||||||
|
rlwinm r9,r31,24,28,31 # bits 11:8 -> register 0x0019 (RRP_HI)
|
||||||
|
|
||||||
|
The length semantics and addressing already matched (descriptor at page<<8,
|
||||||
|
body at +4, length including the descriptor); the PACKING did not, and
|
||||||
|
`next_packet_ptr` was never written at all. Our previous
|
||||||
|
`00 00 len[11:8] len[7:0]` decodes under that driver as packet_len = 16 and
|
||||||
|
next_packet_ptr = 0 for a 342-byte frame — a 12-byte frame and a read pointer
|
||||||
|
into the reserved page.
|
||||||
|
|
||||||
Write sequence
|
Write sequence
|
||||||
--------------
|
--------------
|
||||||
1. Issue 4 SPRAM writes of 0x00 (placeholder descriptor).
|
1. Issue 4 SPRAM writes of 0x00 (placeholder descriptor).
|
||||||
2. For each byte received from W5500, issue one SPRAM write.
|
2. For each byte received from W5500, issue one SPRAM write.
|
||||||
3. After EOF: rewrite descriptor bytes 2 and 3 with actual length.
|
3. After EOF: compute the next page, then rewrite descriptor bytes 0, 1 and 2
|
||||||
|
with the packed next_packet_ptr + packet_len. Byte 3 (status) stays 0x00.
|
||||||
4. Advance RWP, push to rx_wptr FIFO, pulse rx_irq.
|
4. Advance RWP, push to rx_wptr FIFO, pulse rx_irq.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -112,6 +145,10 @@ class RXFrameAssembler(Elaboratable):
|
|||||||
data_ctr = Signal(12)
|
data_ctr = Signal(12)
|
||||||
# Total length = data_ctr + 4
|
# Total length = data_ctr + 4
|
||||||
total_len = Signal(12)
|
total_len = Signal(12)
|
||||||
|
# The page the NEXT frame will start at. Computed before the
|
||||||
|
# descriptor is written, because the descriptor carries it as
|
||||||
|
# next_packet_ptr -- libogc writes those 12 bits straight to RRP.
|
||||||
|
next_page = Signal(12)
|
||||||
|
|
||||||
# Descriptor base (rwp*256) — saved when frame starts
|
# Descriptor base (rwp*256) — saved when frame starts
|
||||||
desc_base = Signal(16)
|
desc_base = Signal(16)
|
||||||
@@ -207,7 +244,7 @@ class RXFrameAssembler(Elaboratable):
|
|||||||
# RWP advanced, but the GC's ethertype test read the wrong
|
# RWP advanced, but the GC's ethertype test read the wrong
|
||||||
# offset and silently dropped every frame -- "no ACK" from the
|
# offset and silently dropped every frame -- "no ACK" from the
|
||||||
# host with no other sign of trouble. The matching
|
# host with no other sign of trouble. The matching
|
||||||
# descriptor bug (same cause, in WRITE_LEN_HI) showed up as a
|
# descriptor bug (same cause, in the length write) showed up as a
|
||||||
# descriptor of 00 00 00 01.
|
# descriptor of 00 00 00 01.
|
||||||
#
|
#
|
||||||
# The first byte is written twice as a consequence of
|
# The first byte is written twice as a consequence of
|
||||||
@@ -247,10 +284,10 @@ class RXFrameAssembler(Elaboratable):
|
|||||||
# Compute pages used: ceil((data_ctr + 4) / 256)
|
# Compute pages used: ceil((data_ctr + 4) / 256)
|
||||||
# = (total_len + 255) >> 8 = total_len[11:8] + (total_len[7:0] != 0)
|
# = (total_len + 255) >> 8 = total_len[11:8] + (total_len[7:0] != 0)
|
||||||
m.d.sync += pages_used.eq(total_len[8:12] + (total_len[:8] != 0))
|
m.d.sync += pages_used.eq(total_len[8:12] + (total_len[:8] != 0))
|
||||||
m.next = "WRITE_LEN_HI"
|
m.next = "CALC_NEXT"
|
||||||
|
|
||||||
# ⚠️ Both length writes must advance on `valid & ready`, NOT on
|
# ⚠️ All three descriptor writes must advance on `valid & ready`,
|
||||||
# `ready` alone. `valid` is scheduled with m.d.sync and so is low
|
# NOT on `ready` alone. `valid` is scheduled with m.d.sync and so is low
|
||||||
# for the whole first cycle in the state, while `ready` is tested
|
# for the whole first cycle in the state, while `ready` is tested
|
||||||
# immediately -- and coming out of WAIT_LAST_WRITE `ready` is
|
# immediately -- and coming out of WAIT_LAST_WRITE `ready` is
|
||||||
# usually already high. Testing `ready` alone therefore fired both
|
# usually already high. Testing `ready` alone therefore fired both
|
||||||
@@ -264,32 +301,72 @@ class RXFrameAssembler(Elaboratable):
|
|||||||
# happens to hold `ready` high on the entry cycle, which is why
|
# happens to hold `ready` high on the entry cycle, which is why
|
||||||
# bring-up's 342-byte DHCP frame read a correct 00 00 01 5A and
|
# bring-up's 342-byte DHCP frame read a correct 00 00 01 5A and
|
||||||
# this went unnoticed.
|
# this went unnoticed.
|
||||||
with m.State("WRITE_LEN_HI"):
|
with m.State("CALC_NEXT"):
|
||||||
# Overwrite descriptor byte 2 with total_len[15:8]
|
# The descriptor carries next_packet_ptr, so the next page must
|
||||||
m.d.sync += self.eth_wr_addr.eq(desc_base + 2)
|
# be known BEFORE the descriptor is written -- this state used
|
||||||
m.d.sync += self.eth_wr_data.eq(total_len[8:12])
|
# to come after it (as ADVANCE_RWP).
|
||||||
|
# next_rwp = ((rwp - 1 + pages_used) % 15) + 1
|
||||||
|
next_rwp_raw = Signal(8)
|
||||||
|
m.d.comb += next_rwp_raw.eq(rwp + pages_used)
|
||||||
|
with m.If(next_rwp_raw > _RX_PAGE_LAST):
|
||||||
|
m.d.sync += next_page.eq(next_rwp_raw - _PAGES_TOTAL)
|
||||||
|
with m.Else():
|
||||||
|
m.d.sync += next_page.eq(next_rwp_raw)
|
||||||
|
m.next = "WRITE_D0"
|
||||||
|
|
||||||
|
with m.State("WRITE_D0"):
|
||||||
|
# Descriptor byte 0 = next_packet_ptr[7:0]
|
||||||
|
m.d.sync += self.eth_wr_addr.eq(desc_base + 0)
|
||||||
|
m.d.sync += self.eth_wr_data.eq(next_page[0:8])
|
||||||
m.d.sync += self.eth_wr_valid.eq(1)
|
m.d.sync += self.eth_wr_valid.eq(1)
|
||||||
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
||||||
m.d.sync += self.eth_wr_valid.eq(0)
|
m.d.sync += self.eth_wr_valid.eq(0)
|
||||||
m.next = "WRITE_LEN_LO"
|
m.next = "WRITE_D1"
|
||||||
|
|
||||||
with m.State("WRITE_LEN_LO"):
|
with m.State("WRITE_D1"):
|
||||||
# Overwrite descriptor byte 3 with total_len[7:0]
|
# Descriptor byte 1 = packet_len[3:0] << 4 | next_ptr[11:8].
|
||||||
|
# Cat puts its FIRST argument in the low bits.
|
||||||
|
m.d.sync += self.eth_wr_addr.eq(desc_base + 1)
|
||||||
|
m.d.sync += self.eth_wr_data.eq(
|
||||||
|
Cat(next_page[8:12], total_len[0:4]))
|
||||||
|
m.d.sync += self.eth_wr_valid.eq(1)
|
||||||
|
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
||||||
|
m.d.sync += self.eth_wr_valid.eq(0)
|
||||||
|
m.next = "WRITE_D2"
|
||||||
|
|
||||||
|
with m.State("WRITE_D2"):
|
||||||
|
# Descriptor byte 2 = packet_len[11:4]. Byte 3 (status) keeps
|
||||||
|
# the placeholder 0x00, which libogc reads as "no error".
|
||||||
|
m.d.sync += self.eth_wr_addr.eq(desc_base + 2)
|
||||||
|
m.d.sync += self.eth_wr_data.eq(total_len[4:12])
|
||||||
|
m.d.sync += self.eth_wr_valid.eq(1)
|
||||||
|
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
||||||
|
m.d.sync += self.eth_wr_valid.eq(0)
|
||||||
|
m.next = "WRITE_D3"
|
||||||
|
|
||||||
|
with m.State("WRITE_D3"):
|
||||||
|
# Descriptor byte 3 = status = 0x00 ("no error": libogc tests
|
||||||
|
# it against 0x84).
|
||||||
|
#
|
||||||
|
# ⚠️ This MUST be written explicitly and cannot rely on the
|
||||||
|
# placeholder. WRITE_PLACEHOLDER issues four writes but the
|
||||||
|
# first lands twice (eth_wr_addr trails wr_addr by a cycle),
|
||||||
|
# so desc_base+3 is NEVER written. That was harmless while
|
||||||
|
# the length write covered byte 3; now byte 3 is the status
|
||||||
|
# and leaving it would hand libogc whatever the previous
|
||||||
|
# frame -- or power-on -- left in that SPRAM cell, and any
|
||||||
|
# frame landing on a stale 0x80 or 0x04 would be discarded as
|
||||||
|
# an error. The simulation model reports it as an unwritten
|
||||||
|
# cell; real SPRAM has no such tell.
|
||||||
m.d.sync += self.eth_wr_addr.eq(desc_base + 3)
|
m.d.sync += self.eth_wr_addr.eq(desc_base + 3)
|
||||||
m.d.sync += self.eth_wr_data.eq(total_len[:8])
|
m.d.sync += self.eth_wr_data.eq(0x00)
|
||||||
m.d.sync += self.eth_wr_valid.eq(1)
|
m.d.sync += self.eth_wr_valid.eq(1)
|
||||||
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
with m.If(self.eth_wr_valid & self.eth_wr_ready):
|
||||||
m.d.sync += self.eth_wr_valid.eq(0)
|
m.d.sync += self.eth_wr_valid.eq(0)
|
||||||
m.next = "ADVANCE_RWP"
|
m.next = "ADVANCE_RWP"
|
||||||
|
|
||||||
with m.State("ADVANCE_RWP"):
|
with m.State("ADVANCE_RWP"):
|
||||||
# next_rwp = ((rwp - 1 + pages_used) % 15) + 1
|
m.d.sync += rwp.eq(next_page)
|
||||||
next_rwp_raw = Signal(8)
|
|
||||||
m.d.comb += next_rwp_raw.eq(rwp + pages_used)
|
|
||||||
with m.If(next_rwp_raw > _RX_PAGE_LAST):
|
|
||||||
m.d.sync += rwp.eq(next_rwp_raw - _PAGES_TOTAL)
|
|
||||||
with m.Else():
|
|
||||||
m.d.sync += rwp.eq(next_rwp_raw)
|
|
||||||
m.next = "PUSH_WPT"
|
m.next = "PUSH_WPT"
|
||||||
|
|
||||||
with m.State("PUSH_WPT"):
|
with m.State("PUSH_WPT"):
|
||||||
@@ -465,6 +542,27 @@ if __name__ == "__main__":
|
|||||||
base = page << 8
|
base = page << 8
|
||||||
return [mem.get(base + i) for i in range(4)]
|
return [mem.get(base + i) for i in range(4)]
|
||||||
|
|
||||||
|
def pack_desc(total, next_ptr, status=0):
|
||||||
|
"""The 4 descriptor bytes libogc's driver expects."""
|
||||||
|
word = (status << 24) | ((total & 0xFFF) << 12) | (next_ptr & 0xFFF)
|
||||||
|
return [(word >> (8 * i)) & 0xFF for i in range(4)]
|
||||||
|
|
||||||
|
def libogc_decode(d):
|
||||||
|
"""Decode exactly as libogc's bba_interrupt does, so these tests
|
||||||
|
check the driver's ALGORITHM rather than our own packing:
|
||||||
|
|
||||||
|
lwbrx -> little-endian word from the 4 bytes
|
||||||
|
rlwinm r4,r9,20,20,31 -> packet_len = (word >> 12) & 0xFFF
|
||||||
|
srwi r9,r9,24 -> status = word >> 24
|
||||||
|
word & 0xFFF -> next_packet_ptr
|
||||||
|
|
||||||
|
Returns (payload_len, next_ptr, status_is_error)."""
|
||||||
|
if any(b is None for b in d):
|
||||||
|
return (None, None, None)
|
||||||
|
word = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24)
|
||||||
|
return (((word >> 12) & 0xFFF) - 4, word & 0xFFF,
|
||||||
|
bool((word >> 24) & 0x84))
|
||||||
|
|
||||||
def body(page, n):
|
def body(page, n):
|
||||||
base = (page << 8) + 4
|
base = (page << 8) + 4
|
||||||
return [mem.get(base + i) for i in range(n)]
|
return [mem.get(base + i) for i in range(n)]
|
||||||
@@ -479,8 +577,9 @@ if __name__ == "__main__":
|
|||||||
await send_frame(f1)
|
await send_frame(f1)
|
||||||
check("T1 rwp advance", await wait_irq(), 2)
|
check("T1 rwp advance", await wait_irq(), 2)
|
||||||
total1 = len(f1) + 4
|
total1 = len(f1) + 4
|
||||||
check("T1 descriptor", descriptor(1),
|
check("T1 descriptor", descriptor(1), pack_desc(total1, 2))
|
||||||
[0x00, 0x00, (total1 >> 8) & 0xFF, total1 & 0xFF])
|
check("T1 descriptor via libogc decode", libogc_decode(descriptor(1)),
|
||||||
|
(len(f1), 2, False))
|
||||||
# The body MUST start at base+4. This is the check that catches a
|
# The body MUST start at base+4. This is the check that catches a
|
||||||
# placeholder write being dropped: with only three placeholder bytes
|
# placeholder write being dropped: with only three placeholder bytes
|
||||||
# written the body landed at base+3 and every frame was shifted one
|
# written the body landed at base+3 and every frame was shifted one
|
||||||
@@ -500,8 +599,9 @@ if __name__ == "__main__":
|
|||||||
await send_frame(f2)
|
await send_frame(f2)
|
||||||
rwp2 = await wait_irq()
|
rwp2 = await wait_irq()
|
||||||
total2 = len(f2) + 4
|
total2 = len(f2) + 4
|
||||||
check("T2 descriptor (len > 255)", descriptor(2),
|
check("T2 descriptor (len > 255)", descriptor(2), pack_desc(total2, 4))
|
||||||
[0x00, 0x00, (total2 >> 8) & 0xFF, total2 & 0xFF])
|
check("T2 descriptor via libogc decode", libogc_decode(descriptor(2)),
|
||||||
|
(len(f2), 4, False))
|
||||||
check("T2 body at base+4", body(2, len(f2)), f2)
|
check("T2 body at base+4", body(2, len(f2)), f2)
|
||||||
# 304 bytes spans two pages, so page 2 -> page 4.
|
# 304 bytes spans two pages, so page 2 -> page 4.
|
||||||
check("T2 rwp advance (2 pages)", rwp2, 4)
|
check("T2 rwp advance (2 pages)", rwp2, 4)
|
||||||
@@ -564,8 +664,11 @@ if __name__ == "__main__":
|
|||||||
check("T5 stored despite stalls", rwp5, "stored")
|
check("T5 stored despite stalls", rwp5, "stored")
|
||||||
pg5 = (ctx.get(dut.rx_wptr_w_data) - 2) & 0xFF # page it was written to
|
pg5 = (ctx.get(dut.rx_wptr_w_data) - 2) & 0xFF # page it was written to
|
||||||
total5 = len(f5) + 4
|
total5 = len(f5) + 4
|
||||||
|
next5 = ctx.get(dut.rx_wptr_w_data)
|
||||||
check("T5 descriptor under stall", descriptor(pg5),
|
check("T5 descriptor under stall", descriptor(pg5),
|
||||||
[0x00, 0x00, (total5 >> 8) & 0xFF, total5 & 0xFF])
|
pack_desc(total5, next5))
|
||||||
|
check("T5 descriptor via libogc decode", libogc_decode(descriptor(pg5)),
|
||||||
|
(len(f5), next5, False))
|
||||||
check("T5 body at base+4 under stall", body(pg5, len(f5)), f5)
|
check("T5 body at base+4 under stall", body(pg5, len(f5)), f5)
|
||||||
stall[0] = 1
|
stall[0] = 1
|
||||||
await tick(4)
|
await tick(4)
|
||||||
@@ -586,8 +689,9 @@ if __name__ == "__main__":
|
|||||||
await send_frame(f6)
|
await send_frame(f6)
|
||||||
check("T6 rwp back to page 1 after NCRA reset", await wait_irq(), 2)
|
check("T6 rwp back to page 1 after NCRA reset", await wait_irq(), 2)
|
||||||
total6 = len(f6) + 4
|
total6 = len(f6) + 4
|
||||||
check("T6 frame lands at page 1", descriptor(1),
|
check("T6 frame lands at page 1", descriptor(1), pack_desc(total6, 2))
|
||||||
[0x00, 0x00, (total6 >> 8) & 0xFF, total6 & 0xFF])
|
check("T6 descriptor via libogc decode", libogc_decode(descriptor(1)),
|
||||||
|
(len(f6), 2, False))
|
||||||
check("T6 body at page 1 base+4", body(1, len(f6)), f6)
|
check("T6 body at page 1 base+4", body(1, len(f6)), f6)
|
||||||
|
|
||||||
await tick(4)
|
await tick(4)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ static u8 s_last_desc[4];
|
|||||||
/* How often the descriptor was found at each page offset. The assembler is
|
/* How often the descriptor was found at each page offset. The assembler is
|
||||||
* supposed to put it at +0 always; hardware shows it sometimes landing at +1,
|
* supposed to put it at +0 always; hardware shows it sometimes landing at +1,
|
||||||
* varying frame to frame, so count both rather than assume either. */
|
* varying frame to frame, so count both rather than assume either. */
|
||||||
static u32 s_shift_hits[2];
|
static u32 s_desc_ok;
|
||||||
/* ACKs where TI never set -- the frame did not reach the W5100. */
|
/* ACKs where TI never set -- the frame did not reach the W5100. */
|
||||||
static u32 s_tx_timeouts;
|
static u32 s_tx_timeouts;
|
||||||
static u8 s_last_dump[32];
|
static u8 s_last_dump[32];
|
||||||
@@ -60,7 +60,7 @@ static int ring_next(u8 *buf, u32 cap)
|
|||||||
{
|
{
|
||||||
u8 wp[2], hdr[8];
|
u8 wp[2], hdr[8];
|
||||||
u32 rpage, wpage, dlen, flen, addr;
|
u32 rpage, wpage, dlen, flen, addr;
|
||||||
u32 shift;
|
u32 desc, nextp;
|
||||||
|
|
||||||
if (bba_read(REG_RWP_LO, wp, 2) < 0) return -1;
|
if (bba_read(REG_RWP_LO, wp, 2) < 0) return -1;
|
||||||
rpage = s_rrp;
|
rpage = s_rrp;
|
||||||
@@ -87,25 +87,24 @@ static int ring_next(u8 *buf, u32 cap)
|
|||||||
memcpy(s_last_desc, hdr, 4);
|
memcpy(s_last_desc, hdr, 4);
|
||||||
s_last_page = rpage;
|
s_last_page = rpage;
|
||||||
|
|
||||||
/* LOCATE the descriptor rather than assume it is at +0.
|
/* The descriptor is a PACKED 32-BIT LITTLE-ENDIAN WORD -- the format
|
||||||
|
* libogc's own BBA driver reads, so this is what GC software expects:
|
||||||
*
|
*
|
||||||
* It should always be at +0: four bytes [00 00 len_hi len_lo] followed
|
* bits 0-11 next_packet_ptr : the PAGE the NEXT frame starts at
|
||||||
* immediately by the frame. On hardware it sometimes sits at +1 instead,
|
* bits 12-23 packet_len : total bytes, INCLUDING these 4
|
||||||
* and which one varies frame to frame -- 18 of 23 frames were rejected
|
* bits 24-31 status : 0x80 and 0x04 are errors
|
||||||
* for an implausible length while 5 parsed fine, in the same run. That
|
|
||||||
* is a race in the assembler, not a constant offset, and the gateware bug
|
|
||||||
* behind it is not yet found.
|
|
||||||
*
|
*
|
||||||
* Taking the length from whichever offset yields a plausible one lets the
|
* See the note in exi_bba/rx_frame_assembler.py for the disassembly this
|
||||||
* transfer proceed either way AND measures the split, which is the
|
* was taken from. The old reader assumed [00 00 len_hi len_lo] and
|
||||||
* measurement needed to find the real cause. A length is a strong check:
|
* probed offsets +0 and +1 to work around the tx-FIFO-underrun bug that
|
||||||
* it must exceed the 4 descriptor bytes and fit a legal frame. */
|
* shifted descriptors; that bug is fixed (confirmed on console, +1=0) and
|
||||||
for (shift = 0; shift <= 1; shift++) {
|
* the probe is gone -- under the packed format a +1 read is not a shifted
|
||||||
dlen = ((u32)hdr[shift + 2] << 8) | hdr[shift + 3];
|
* descriptor, just garbage. */
|
||||||
if (dlen > 4 && dlen <= 1518 + 4) break;
|
desc = (u32)hdr[0] | ((u32)hdr[1] << 8) |
|
||||||
}
|
((u32)hdr[2] << 16) | ((u32)hdr[3] << 24);
|
||||||
if (shift > 1) {
|
dlen = (desc >> 12) & 0xFFF;
|
||||||
dlen = ((u32)hdr[2] << 8) | hdr[3]; /* report the nominal one */
|
nextp = desc & 0xFFF;
|
||||||
|
if (((desc >> 24) & 0x84) || dlen <= 4 || dlen > 1518 + 4) {
|
||||||
/* Only now is a hex dump worth an EXI transaction. Doing it for
|
/* Only now is a hex dump worth an EXI transaction. Doing it for
|
||||||
* EVERY frame (as an earlier version did) added a round trip to the
|
* EVERY frame (as an earlier version did) added a round trip to the
|
||||||
* poll loop that is already losing the race against the writer --
|
* poll loop that is already losing the race against the writer --
|
||||||
@@ -113,7 +112,7 @@ static int ring_next(u8 *buf, u32 cap)
|
|||||||
bba_stream((u16)addr, s_last_dump, sizeof(s_last_dump));
|
bba_stream((u16)addr, s_last_dump, sizeof(s_last_dump));
|
||||||
return -3;
|
return -3;
|
||||||
}
|
}
|
||||||
s_shift_hits[shift]++;
|
s_desc_ok++;
|
||||||
flen = dlen - 4;
|
flen = dlen - 4;
|
||||||
if (flen > cap) flen = cap;
|
if (flen > cap) flen = cap;
|
||||||
|
|
||||||
@@ -122,16 +121,18 @@ static int ring_next(u8 *buf, u32 cap)
|
|||||||
* one DMA read covers it. */
|
* one DMA read covers it. */
|
||||||
{
|
{
|
||||||
static u8 tmp[1536 + 4];
|
static u8 tmp[1536 + 4];
|
||||||
u32 want = shift + dlen;
|
u32 want = dlen;
|
||||||
if (want > sizeof(tmp)) want = sizeof(tmp);
|
if (want > sizeof(tmp)) want = sizeof(tmp);
|
||||||
if (bba_stream((u16)addr, tmp, want) < 0) return -1;
|
if (bba_stream((u16)addr, tmp, want) < 0) return -1;
|
||||||
memcpy(buf, tmp + shift + 4, flen);
|
memcpy(buf, tmp + 4, flen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Advance RRP by the pages this frame occupied, wrapping 1..15. */
|
/* Advance RRP to the descriptor's OWN next_packet_ptr, which is what
|
||||||
|
* libogc does, rather than recomputing the page span here. The writer
|
||||||
|
* put that value in the descriptor, so the two cannot disagree. */
|
||||||
{
|
{
|
||||||
u32 pages = (shift + dlen + 255) >> 8;
|
u32 next = nextp;
|
||||||
u32 next = ((rpage - 1 + pages) % 15) + 1;
|
if (next < 1 || next > 15) next = 1;
|
||||||
if (bba_w8(REG_RRP_LO, (u8)next) < 0) return -1;
|
if (bba_w8(REG_RRP_LO, (u8)next) < 0) return -1;
|
||||||
s_rrp = next;
|
s_rrp = next;
|
||||||
}
|
}
|
||||||
@@ -381,11 +382,10 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
|
|||||||
if (idle > (got ? 5000u : 30000u)) {
|
if (idle > (got ? 5000u : 30000u)) {
|
||||||
if (status) {
|
if (status) {
|
||||||
snprintf(line, sizeof(line),
|
snprintf(line, sizeof(line),
|
||||||
"%s (%u seen, %u bad, desc+0=%u +1=%u, txto=%u)",
|
"%s (%u seen, %u bad, desc ok=%u, txto=%u)",
|
||||||
got ? "timeout mid-transfer" : "timeout: no match",
|
got ? "timeout mid-transfer" : "timeout: no match",
|
||||||
(unsigned)seen, (unsigned)bad,
|
(unsigned)seen, (unsigned)bad,
|
||||||
(unsigned)s_shift_hits[0],
|
(unsigned)s_desc_ok,
|
||||||
(unsigned)s_shift_hits[1],
|
|
||||||
(unsigned)s_tx_timeouts);
|
(unsigned)s_tx_timeouts);
|
||||||
status(line);
|
status(line);
|
||||||
}
|
}
|
||||||
@@ -433,9 +433,8 @@ int netboot_receive(void *dst, u32 maxlen, void (*status)(const char *msg))
|
|||||||
* W5100) versus sent-but-lost. The host reports the
|
* W5100) versus sent-but-lost. The host reports the
|
||||||
* retries; only the GC knows which kind they were. */
|
* retries; only the GC knows which kind they were. */
|
||||||
snprintf(line, sizeof(line),
|
snprintf(line, sizeof(line),
|
||||||
"desc@+0=%u +1=%u bad=%u lapped=%u txto=%u",
|
"desc ok=%u bad=%u lapped=%u txto=%u",
|
||||||
(unsigned)s_shift_hits[0],
|
(unsigned)s_desc_ok,
|
||||||
(unsigned)s_shift_hits[1],
|
|
||||||
(unsigned)bad, (unsigned)lapped,
|
(unsigned)bad, (unsigned)lapped,
|
||||||
(unsigned)s_tx_timeouts);
|
(unsigned)s_tx_timeouts);
|
||||||
status(line);
|
status(line);
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ static int t_rx(char *m, size_t n)
|
|||||||
static int t_dma(char *m, size_t n)
|
static int t_dma(char *m, size_t n)
|
||||||
{
|
{
|
||||||
u8 buf[64], rp[2], wp[2];
|
u8 buf[64], rp[2], wp[2];
|
||||||
u32 dlen, page, addr;
|
u32 dlen, page, addr, desc, nextp;
|
||||||
if (bba_read(REG_RRP_LO, rp, 2) < 0) { snprintf(m, n, "RRP read failed"); return 1; }
|
if (bba_read(REG_RRP_LO, rp, 2) < 0) { snprintf(m, n, "RRP read failed"); return 1; }
|
||||||
if (bba_read(REG_RWP_LO, wp, 2) < 0) { snprintf(m, n, "RWP read failed"); return 1; }
|
if (bba_read(REG_RWP_LO, wp, 2) < 0) { snprintf(m, n, "RWP read failed"); return 1; }
|
||||||
page = ((u32)rp[1] << 8) | rp[0];
|
page = ((u32)rp[1] << 8) | rp[0];
|
||||||
@@ -417,11 +417,18 @@ static int t_dma(char *m, size_t n)
|
|||||||
snprintf(m, n, "stream read at 0x%04X failed", (unsigned)addr);
|
snprintf(m, n, "stream read at 0x%04X failed", (unsigned)addr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
/* 4-byte ring descriptor; its length field INCLUDES the descriptor. */
|
/* The 4-byte ring descriptor is a PACKED 32-BIT LITTLE-ENDIAN WORD, the
|
||||||
dlen = ((u32)buf[2] << 8) | buf[3];
|
* format libogc's own driver reads:
|
||||||
if (dlen > 4 && dlen <= 1518) {
|
* bits 0-11 next_packet_ptr (page), 12-23 packet_len (INCLUDING these
|
||||||
snprintf(m, n, "p%u len 0x%04X = %u bytes, etype %02X%02X",
|
* 4 bytes), 24-31 status (0x80 and 0x04 are errors).
|
||||||
(unsigned)page, (unsigned)dlen, (unsigned)(dlen - 4),
|
* Decoded here exactly as bba_interrupt does. */
|
||||||
|
desc = (u32)buf[0] | ((u32)buf[1] << 8) |
|
||||||
|
((u32)buf[2] << 16) | ((u32)buf[3] << 24);
|
||||||
|
dlen = (desc >> 12) & 0xFFF;
|
||||||
|
nextp = desc & 0xFFF;
|
||||||
|
if (((desc >> 24) & 0x84) == 0 && dlen > 4 && dlen <= 1518 + 4) {
|
||||||
|
snprintf(m, n, "p%u len %u B, next p%u, etype %02X%02X",
|
||||||
|
(unsigned)page, (unsigned)(dlen - 4), (unsigned)nextp,
|
||||||
buf[16], buf[17]);
|
buf[16], buf[17]);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -442,9 +449,12 @@ static int t_dma(char *m, size_t n)
|
|||||||
map[pg - 1] = '?';
|
map[pg - 1] = '?';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dl = ((u32)d[2] << 8) | d[3];
|
u32 w = (u32)d[0] | ((u32)d[1] << 8) |
|
||||||
if (d[0] == 0 && d[1] == 0 && dl == 0) map[pg - 1] = '-';
|
((u32)d[2] << 16) | ((u32)d[3] << 24);
|
||||||
else if (dl > 4 && dl <= 1518) map[pg - 1] = 'o';
|
dl = (w >> 12) & 0xFFF;
|
||||||
|
if (w == 0) map[pg - 1] = '-';
|
||||||
|
else if (((w >> 24) & 0x84) == 0 &&
|
||||||
|
dl > 4 && dl <= 1518 + 4) map[pg - 1] = 'o';
|
||||||
else map[pg - 1] = 'x';
|
else map[pg - 1] = 'x';
|
||||||
}
|
}
|
||||||
map[15] = 0;
|
map[15] = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user