Skip to content

test: Watch the real INIT handshake and skip zero-length stream jobs - #188

Merged
DanielKellerM merged 1 commit into
pulp-platform:develfrom
DanielKellerM:test/random-tb-fixes
Aug 13, 2026
Merged

test: Watch the real INIT handshake and skip zero-length stream jobs#188
DanielKellerM merged 1 commit into
pulp-platform:develfrom
DanielKellerM:test/random-tb-fixes

Conversation

@DanielKellerM

Copy link
Copy Markdown
Collaborator

Two independent testbench defects behind the red vsim-sim-random legs. Neither is an RTL problem; the RTL bug that accounts for the remaining leg is filed separately.

1. The INIT write watchdog watched a dead wire

The write-side stream_watchdog generator has no init case, so INIT fell through to the generic AXI-shaped binding init_axi_req.w_valid / init_axi_rsp.w_ready. The testbench ties both init_axi_read_req and init_axi_write_req to '0 before joining them, so that valid is a compile-time constant 0. The real INIT write handshake is init_write_req.req_valid / init_write_rsp.req_ready.

Effect: on any INIT-destination transfer the watchdog expired after WatchDogNumCycles (100) while the DMA was working perfectly. Measured on the minimal case (one 4096 B transfer, OBI to INIT): it trips at exactly 1020 ns, while i_idma_init_write.write_happening toggles 88 times and the OBI read memory 82 times, and the read watchdog counter stays healthy.

This is not a weakening: the watchdog still fires after 100 genuinely idle cycles, it just observes the handshake it was always meant to.

2. The AXI-Stream write model mis-attributed jobs after a zero-length transfer

The model pops every job whose destination is AXI Stream, including zero-length ones, then blocks on wait(axis_write_req.tvalid). A rejected zero-length transfer emits no beats, so the model consumed the next job's packet and wrote it at the zero-length job's destination, desynchronising by one job for the rest of the run. The read model was never affected because its while (address < src_addr + length) loop is a natural no-op at length 0; that asymmetry is the bug.

Worth noting why it hid for so long: compare_mem skips any byte reading 0xff, so a mis-attributed job usually compares vacuously against untouched memory and passes. It only becomes a hard Mismatch! once a later, longer job has already overwritten the region.

Verified

Regenerated and recompiled clean, then re-ran the previously failing legs with the CI gate (! grep Error: and ! grep Fatal:):

  • snitch_write seed 0: PASS (was tripping the write watchdog at 1020 ns)
  • rw_axi_rw_axis seeds 0 and 2: PASS
  • rw_axi_rw_axis seed 1: still fails, and correctly so. It no longer produces the bogus Mismatch!; it now hangs honestly on the RTL defect filed separately, so this PR deliberately does not turn it green.

10 of the 11 red legs are fixed here. No seed was pinned or skipped, no watchdog relaxed, no gate weakened, and the random job generator is untouched.

Copilot AI lite review requested due to automatic review settings August 13, 2026 10:20

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 DanielKellerM left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

check and verify

// Handle writes
while(axis_jobs.size() > 0) begin
current_job = axis_jobs.pop_front();
// a zero-length transfer emits no beats; waiting for one would steal the next job's

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

there is a parameter for what behaviour we want to regarding zero length transfers, is this aligned?

@DanielKellerM

Copy link
Copy Markdown
Collaborator Author

Checked, and you are right that it is not aligned. It is correct only for RejectZeroTransfers=1.

I built the directed case your question implies - a zero-length AXI-Stream transfer followed by a real 64 B one - and ran it both ways, with and without this PR:

RejectZeroTransfers=1 RejectZeroTransfers=0
baseline, without this PR PASS FAIL, Mismatch!
with this PR PASS FAIL, Mismatch!

So RejectZeroTransfers=0 with a zero-length stream transfer is already broken on devel; this PR neither causes nor worsens it. But the continue does silently assume the rejecting configuration, which is what your comment is pointing at.

Two things I deliberately did not do without your call:

  1. Gate the skip on the parameter. RejectZeroTransfers is visible in the generated testbench (tb_idma_backend_rw_axi_rw_axis.sv:43, passed to the backend at :389), so the model can read it. That makes the assumption explicit rather than implicit.

  2. Work out what =0 should actually do. I have not established whether the DUT emits stream beats for a zero-length transfer in that mode, so I do not yet know whether the model should wait for beats, skip, or whether the combination is supported at all. The Mismatch! may be a testbench modelling gap or a real datapath question, and guessing would just move the bug.

Note the CI matrix only ever runs RejectZeroTransfers=1, which is why this has stayed invisible.

Happy to add the parameter gate to this PR so the assumption is at least explicit and =0 fails loudly rather than silently, and open a separate issue for what =0 ought to do. Or keep this PR as-is if you would rather handle both together.

The write-side watchdog generator had no init case, so INIT fell through to an
AXI-shaped binding on a wire the testbench ties to zero; the watchdog expired
after 100 cycles on any INIT-destination transfer while the DMA was making
progress.

The AXI-Stream write model also popped zero-length jobs and then blocked
waiting for beats that a rejected transfer never emits, so it consumed the
next job's packet and mis-attributed every later one.
@DanielKellerM

Copy link
Copy Markdown
Collaborator Author

Agreed, and dropped. The guard did not fix anything; it only made a wrong configuration fail more legibly, and that does not belong in a PR that is otherwise fixing real failures. Issue #194 now tracks what RejectZeroTransfers=0 should actually do, and a fix for it is in progress on its own branch; whatever that lands will replace the need for a guard rather than sit next to one.

What is left here is two measured fixes, 6 lines:

INIT write watchdog watched a dead wire. The generator has no init case, so INIT fell through to the AXI-shaped binding init_axi_req.w_valid, which the testbench ties to '0 before joining. The real handshake is init_write_req.req_valid/init_write_rsp.req_ready. The watchdog therefore expired after 100 cycles on any INIT-destination transfer while the DMA was making progress: it trips at 1020 ns while i_idma_init_write.write_happening toggles 88 times.

AXI-Stream model consumed the wrong job after a zero-length transfer. It popped zero-length jobs and blocked on wait(axis_write_req.tvalid), so it took the next job's packet and wrote it at the wrong address, mis-attributing every job after it.

Measured effect on the red vsim-sim-random legs:

leg before after
snitch_write s0 watchdog at 1020 ns PASS
snitch_read, snitch_read_write (3 seeds each) watchdog PASS
r_obi_w_axi watchdog PASS
rw_axi_rw_axis s0, s2 PASS PASS
rw_axi_rw_axis s1 Mismatch! fails honestly on the RTL bug, fixed in #193

That is 10 of the 11 red legs, and with #193 the eleventh. Also rebased onto devel, since the previous base predated #185 and the diff would otherwise have reverted it.

@DanielKellerM
DanielKellerM merged commit dfe7a56 into pulp-platform:devel Aug 13, 2026
13 checks passed
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