fix(exi_capture): TX pop threshold must follow the header length
tx_fifo.r_en popped from txld_cnt >= 2, hard-coded for the old invented 2-byte header where the data phase began at slot 2. The real protocol's register space uses a 4-byte header, so data starts at slot 4 -- two bytes were being consumed before the GC clocked any data, and every register read came back wrong. On hardware: HIPR and NWAYS both read 0x00, and the device ID returned 01 80 c0 c0 (stale bytes, including our own header command bytes 0x80/0xC0 echoing back out of the FIFO). The threshold is now derived from byte 0 bit 7, which is exactly what selects the header form: register space -> 4 slots, command space -> 2 slots. Also removes the pad-slot filler byte the register file was pushing. That was compensating for this bug rather than fixing it -- it made the simulation pass while hardware still failed, which is how it slipped through. With the pop threshold correct the filler is wrong, and both the unit and integration testbenches agree without it. All suites pass: bba_register_file, bba_top, exi_capture, exi_j4, spi_mode3_slave, tx_frame_drain, spram_arbiter, rx_frame_assembler, w5100_parallel_master. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+19
-3
@@ -133,7 +133,7 @@ class ExiCapture(Elaboratable):
|
||||
# completion ≥2 → a data byte finished → pop to advance the head
|
||||
# The first data byte (data0) is served live from the head without a
|
||||
# pop; the pop after it advances the head to data1's response, etc.
|
||||
txld_cnt = Signal(2)
|
||||
txld_cnt = Signal(3) # counts header slots; data starts at hdr_slots
|
||||
|
||||
m.d.comb += spi.tx_byte.eq(tx_fifo.r_data)
|
||||
|
||||
@@ -165,13 +165,29 @@ class ExiCapture(Elaboratable):
|
||||
m.d.capture += drain.eq(self._tx_depth + 1)
|
||||
with m.Elif(drain != 0):
|
||||
m.d.capture += drain.eq(drain - 1)
|
||||
# Pops must start at the FIRST DATA SLOT, which depends on the header
|
||||
# length -- and the real protocol has two header forms (see
|
||||
# docs/exi_protocol_real.md):
|
||||
# byte0 bit7 = 1 register space, 4-byte header -> data at slot 4
|
||||
# byte0 bit7 = 0 command space, 2-byte header -> data at slot 2
|
||||
# This was hard-coded to 2, which is right only for the old invented
|
||||
# 2-byte header; against a 4-byte header it consumes two bytes before
|
||||
# the GC clocks any data and every read comes back shifted.
|
||||
hdr_slots = Signal(3, init=2)
|
||||
first_byte = Signal(init=1)
|
||||
with m.If(spi.frame_start):
|
||||
m.d.capture += first_byte.eq(1)
|
||||
with m.Elif(spi.rx_valid & first_byte):
|
||||
m.d.capture += [first_byte.eq(0),
|
||||
hdr_slots.eq(Mux(spi.rx_byte[7], 4, 2))]
|
||||
|
||||
m.d.comb += tx_fifo.r_en.eq(
|
||||
(spi.tx_load & (txld_cnt >= 2)) | (drain != 0)
|
||||
(spi.tx_load & (txld_cnt >= hdr_slots)) | (drain != 0)
|
||||
)
|
||||
|
||||
with m.If(spi.frame_start):
|
||||
m.d.capture += txld_cnt.eq(0)
|
||||
with m.Elif(spi.tx_load & (txld_cnt < 3)):
|
||||
with m.Elif(spi.tx_load & (txld_cnt < 5)):
|
||||
m.d.capture += txld_cnt.eq(txld_cnt + 1)
|
||||
|
||||
return m
|
||||
|
||||
Reference in New Issue
Block a user