diff --git a/CLAUDE.md b/CLAUDE.md index f07c105..22e19a3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -246,10 +246,20 @@ Details and the recovery ladder are in `gc_bba_test/README.md`. Key points: veth and the frames go nowhere). - **Stop-and-wait is load-bearing** — see the RBFI note; one frame in flight means the ring is never contended. -- `bba_tx_bytes()` streams in 64-byte chunks, ONE EXI transaction each. Writing - byte-by-byte cost a full EXI_Lock/Select/Deselect/Unlock per byte — ~60 - transactions per ACK — and that latency, not any transmit failure, is what - made ~⅓ of blocks retry (`txto=0` on hardware: TI always set). +- `bba_tx_bytes()` streams in **8-byte** chunks, one EXI transaction each. + Writing byte-by-byte cost a full EXI_Lock/Select/Deselect/Unlock per byte — + ~60 transactions per ACK — and that latency, not any transmit failure, is + what made ~⅓ of blocks retry (`txto=0` on hardware: TI always set). + + ⚠️ **The chunk size is bounded by `tx_bytes`, which is 16 deep.** Within one + transaction the bytes arrive back-to-back at EXI speed, far faster than + `TXFrameDrain` starts consuming, and `BBARegisterFile` silently drops bytes + when `w_rdy` is low because SPI cannot back-pressure. A 64-byte chunk + overran the FIFO, the frame went out truncated and the host saw no valid ACK + at all. Byte-by-byte only worked because the per-byte EXI overhead happened + to pace the drain. 8 is half the FIFO depth, so it cannot overflow even if + the drain consumes nothing for a whole transaction. **Do not raise it + without deepening `tx_bytes` or giving the TX path real back-pressure.** ### ⚠️ OPEN: ring descriptor sometimes lands at page offset +1 diff --git a/gc_bba_test/source/bba.c b/gc_bba_test/source/bba.c index 8640913..bd21a70 100644 --- a/gc_bba_test/source/bba.c +++ b/gc_bba_test/source/bba.c @@ -125,21 +125,31 @@ int bba_tx_bytes(const u8 *buf, u32 len) { u32 off = 0; - /* Stream in 64-byte chunks, ONE EXI transaction each. + /* Stream in 8-byte chunks, ONE EXI transaction each. * - * This used to write byte by byte with bba_w8(), which means a full - * EXI_Lock / EXI_Select / header / EXI_Deselect / EXI_Unlock per BYTE -- - * about sixty transactions to send one 60-byte ACK. That latency is why - * roughly a third of netboot blocks were retried: the ACK was always - * transmitted (txto=0 on hardware, so TI always set) but did not get back - * to the host inside its timeout. The data phase of a register-space - * write runs until CS, so there is no reason to pay the per-byte cost. + * Writing byte by byte with bba_w8() costs a full EXI_Lock / EXI_Select / + * header / EXI_Deselect / EXI_Unlock PER BYTE -- about sixty transactions + * for one 60-byte ACK. That latency is why ~1/3 of netboot blocks were + * retried: the ACK always reached the W5100 (txto=0, TI always set) but + * missed the host's timeout. * - * 64 is bba_write()'s internal buffer limit, not a protocol limit. */ + * ⚠️ BUT THE CHUNK SIZE IS BOUNDED BY tx_bytes, WHICH IS 16 DEEP. + * BBARegisterFile silently drops bytes when tx_bytes.w_rdy is low, + * because SPI cannot back-pressure. Within one transaction the bytes + * arrive back-to-back at EXI speed -- far faster than TXFrameDrain can + * start consuming -- so a 64-byte chunk overran the FIFO, the frame went + * out truncated, and the host saw no valid ACK at all ("no ACK after 20 + * tries"). Byte-by-byte worked only because the per-byte EXI overhead + * happened to give the drain time. + * + * 8 is half the FIFO depth: it cannot overflow even if the drain consumes + * nothing for the whole transaction, while still cutting a 60-byte ACK + * from ~60 transactions to 8. Do not raise it without deepening + * tx_bytes or giving the TX path real back-pressure. */ while (off < len) { u32 n = len - off; int rc; - if (n > 64) n = 64; + if (n > 8) n = 8; rc = bba_write(REG_TXDATA, buf + off, n); if (rc < 0) return rc; off += n;