Skip to content

backend: Accept a zero-length transfer as a no-op when RejectZeroTransfers is 0 - #195

Open
DanielKellerM wants to merge 2 commits into
pulp-platform:develfrom
DanielKellerM:backend/zero-len-accept
Open

backend: Accept a zero-length transfer as a no-op when RejectZeroTransfers is 0#195
DanielKellerM wants to merge 2 commits into
pulp-platform:develfrom
DanielKellerM:backend/zero-len-accept

Conversation

@DanielKellerM

Copy link
Copy Markdown
Collaborator

Fixes #194. Stacked on #193 (both restructure the same generate block); merge after it.

RejectZeroTransfers=0 is 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 with num_bytes = 0 - and zero has no representation downstream:

  • AxLEN = (num_bytes + offset - 1) >> OffsetWidth underflows at offset 0, giving a 256-beat, 1024-byte burst
  • tailer = (num_bytes + offset)[OffsetWidth-1:0] = 0 aliases to "all bytes valid", so WSTRB is 1111 on every beat

Measured on tb_idma_backend_rw_axi, one length=0 job at 0x1000 -> 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, because compare_mem with 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_zero is 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; RejectZeroTransfers legitimately selects only the payload:

response
=1 error=1, err_type=BACKEND
=0 error=0, a no-op that succeeds

It 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

  • The directed zero-length job passes at both settings, and 16 zero-length runs (8 job and testbench combinations x 2 settings) pass.
  • Exactly one response per zero-length request at both settings, with the payloads above, probed on idma_rsp_o at the handshake.
  • No AR, AW or W handshake at either setting; the probe was self-checked against a real transfer so the absence is real.
  • The two-job AXI-Stream case that reported Mismatch! on devel at =0 now passes; I re-ran that one myself, before and after.
  • No regression: the shipped directed suites still pass, and the change elaborates under verilator and slang.

Coverage note

Every instantiation in the repo hardwires RejectZeroTransfers = 1'b1 - jobs.json, all testbenches, idma_inst64_top, idma_nd_midend_synth and both template defaults - so =0 had no in-repo user and no CI coverage. That is why this survived.

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.
Copilot AI lite review requested due to automatic review settings August 13, 2026 14:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@DanielKellerM

Copy link
Copy Markdown
Collaborator Author

Deferred until after the 0.7.0 release, per Daniel. Not to be merged into the release branch.

@DanielKellerM
DanielKellerM deleted the branch pulp-platform:devel August 19, 2026 08:27
@DanielKellerM DanielKellerM reopened this Aug 19, 2026
@DanielKellerM
DanielKellerM force-pushed the devel branch 3 times, most recently from a4b8a4e to 2e0b0fe Compare August 19, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants