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:
2026-10-03 12:27:31 +02:00
parent 2458884160
commit 399fbf02a4
2 changed files with 20 additions and 14 deletions
+1 -11
View File
@@ -360,13 +360,6 @@ class BBARegisterFile(Elaboratable):
spram_req.w_en.eq(1),
sp_addr.eq(full + 1),
outstanding.eq(1)]
# MISO is driven LIVE from the tx FIFO head, so a byte
# pushed now would show up during the pad slot rather than
# the first data slot. Park a filler byte there -- MISO is
# don't-care for the whole header -- so the real first
# response byte lands one slot later, where it belongs.
with m.If(~is_write & self.tx_rdy):
m.d.comb += [self.tx_data.eq(0x00), self.tx_en.eq(1)]
m.next = "RHDR_PAD"
with m.State("RHDR_PAD"):
@@ -650,10 +643,7 @@ if __name__ == "__main__":
await push_rx(ctx, (addr >> 8) & 0xFF)
await push_rx(ctx, addr & 0xFF)
await push_rx(ctx, 0x00) # turnaround / prefetch window
# The device parks a filler byte in the pad slot (MISO is don't-care
# for the whole header) so the first REAL response byte lands in data
# slot 0. This bench counts pushes rather than wire slots, so drop it.
result = (await collect_tx(ctx, length + 1))[1:]
result = await collect_tx(ctx, length)
ctx.set(reg.cs_active, 0)
await ctx.tick("exi").repeat(6)
return result