backend: Accept a zero-length transfer as a no-op when RejectZeroTransfers is 0 - #195
Open
DanielKellerM wants to merge 2 commits into
Open
backend: Accept a zero-length transfer as a no-op when RejectZeroTransfers is 0#195DanielKellerM wants to merge 2 commits into
DanielKellerM wants to merge 2 commits into
Conversation
The rejection was a combinational function of the request handshake, so it lasted one cycle whether or not the consumer was ready and it could overtake the response of a transfer still in flight. A dropped rejection leaves the backend owing a response forever; a reordered one is credited to the wrong transfer. Latch it into a proper stream and hold back a zero-length request while an older rejection is unaccepted or any transfer still owes a response. Fixes pulp-platform#189
…sfers is 0 A zero-length transfer moves no bytes, but with RejectZeroTransfers = 0 the request was handed to the legalizer, which has no encoding for zero bytes. AxLEN = beats - 1 underflowed to a full 256-beat burst at an aligned address and the write mask aliased tailer = 0 to full width, so the backend read 1024 bytes of unrelated source data, overwrote 1024 bytes at the destination and reported success. With a misaligned source the read produced no valid bytes against a 256-beat write and the backend deadlocked without ever answering. The mode had no test, no CI coverage and no in-repo user, and is_length_zero was left undriven at X. Both settings must suppress the request; the parameter can only choose the payload of the response the request is still owed. Hoist the suppression, the outstanding-transfer counter and the hold-back out of the generate so they apply unconditionally, and let RejectZeroTransfers pick only error and err_type. Take the response last flag from the request instead of hardcoding it, capturing it when the request is accepted since the response is emitted later. This also changes the rejecting path; the ND midend completes an ND request on the first burst response carrying last, so a hardcoded last retired a multi-burst ND request one burst early. Cover the mode: run zero_transfer and zero_transfer_busy at both settings, and add zero-then-real, misaligned-source and misaligned-destination job files.
Collaborator
Author
|
Deferred until after the 0.7.0 release, per Daniel. Not to be merged into the release branch. |
This was referenced Aug 13, 2026
Merged
DanielKellerM
force-pushed
the
devel
branch
3 times, most recently
from
August 19, 2026 11:33
a4b8a4e to
2e0b0fe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #194. Stacked on #193 (both restructure the same generate block); merge after it.
RejectZeroTransfers=0is not merely unmodelled, it silently corrupts memory. The zero-length request is passed straight to the legaliser, which emits one read and one write burst descriptor withnum_bytes = 0- and zero has no representation downstream:AxLEN = (num_bytes + offset - 1) >> OffsetWidthunderflows at offset 0, giving a 256-beat, 1024-byte bursttailer = (num_bytes + offset)[OffsetWidth-1:0] = 0aliases to "all bytes valid", so WSTRB is1111on every beatMeasured on
tb_idma_backend_rw_axi, onelength=0job at0x1000 -> 0x2000: AR len=255, AW len=255, 256 write beats with full strobes, B OKAY, and an iDMA response with error=0. A zero-length transfer reads 1024 bytes of unrelated source memory, overwrites 1024 bytes at the destination, and reports success. The testbench passes, becausecompare_memwith length 0 checks nothing.Other shapes are worse: on OBI it writes 4 bytes of undefined data; with a misaligned source it hard deadlocks (watchdog at 1020 ns, no response ever); and on AXI Stream the read and write beat counts diverge, so 1020 bytes of over-read leak into the next job, which is the only reason the bug was visible at all.
is_length_zerois also left undriven in the non-rejecting branch and reads X for the whole simulation.The fix
The invariant is the response contract, not the byte count: one accepted request owes exactly one response, and the ND/RT midends and the descriptor frontends count on that. So both settings must suppress the request at the input and synthesise the response;
RejectZeroTransferslegitimately selects only the payload:=1=0It can never legitimately hand the request to the legaliser, because no supported protocol can express a zero-beat transaction and AXI has no side-effect-free zero-byte read. The change is one generate block hoisted out of the
if (RejectZeroTransfers); no legaliser, transport-layer, database or testbench-model change.Verified
idma_rsp_oat the handshake.Mismatch!on devel at=0now passes; I re-ran that one myself, before and after.Coverage note
Every instantiation in the repo hardwires
RejectZeroTransfers = 1'b1- jobs.json, all testbenches,idma_inst64_top,idma_nd_midend_synthand both template defaults - so=0had no in-repo user and no CI coverage. That is why this survived.