Skip to content

frontend: Fix reg next_id launch deadlock (non-blocking) - #154

Merged
DanielKellerM merged 6 commits into
develfrom
frontend/reg-rdswacc-fix
Jul 10, 2026
Merged

frontend: Fix reg next_id launch deadlock (non-blocking)#154
DanielKellerM merged 6 commits into
develfrom
frontend/reg-rdswacc-fix

Conversation

@DanielKellerM

Copy link
Copy Markdown
Collaborator

What

Fixes the register-frontend next_id launch deadlock (introduced by the SystemRDL/PeakRDL migration in #73, inherited by #148) by making the launch non-blocking. Supersedes #153.

Why non-blocking, not just deadlock-free

Reading next_id launches a transfer. With next_id modelled as a PeakRDL external register, that read stalls the config bus until the backend arbiter grants — which has two problems:

  1. Deadlock — an ungranted read latches external_pending, masks the CPUIF request, and the read-ack can then never fire, so the whole reg block freezes.
  2. Head-of-line blocking — even with the deadlock fixed, a blocking config read couples the config bus to backend readiness. On any SoC where the config port shares an interconnect with other traffic, a stalled next_id read back-pressures unrelated memory transactions — a real hang on integrated systems, not a corner case.

#153 fixed (1) but kept the blocking contract, so it still hits (2). A non-blocking read avoids both.

The fix

next_id becomes an internal register with an rd_swacc read-strobe, so the config read completes in the same cycle (non-blocking — no HOL-block). A launch-pending latch then holds the arbiter request asserted until the backend grants, so a launch is never dropped when the grant lands a cycle after the (now-immediate) read. status/done_id are likewise internal, which also removes a latent freeze on reads of unused per-stream channel registers.

Template-only (idma_reg.rdl + idma_reg.sv.tpl); CPUIF-agnostic (apb/obi/axi4-lite).

Software contract

The read no longer blocks — it returns immediately with the launch id. To serialise launches, software polls next_id until it advances (the launch was accepted) before programming the next transfer, instead of relying on the read to block.

Test

New test/tb_idma_reg_frontend.sv (non-blocking contract) — the first directed reg-frontend testbench. It asserts the next_id read completes within a bounded latency even under backpressure (the non-blocking property), and that each returned id yields exactly one backend launch with no drop (the latch guarantee). Verified in Questa (NumStreams 1 and 2). Non-vacuity: the same testbench run against a blocking template fails the bounded-latency check (the read never completes under backpressure), so it genuinely distinguishes the contract.

Supersedes / subsumes

Make next_id/status/done_id internal (non-external) PeakRDL registers and
launch a transfer on the next_id rd_swacc read strobe. The config read now
completes in the same cycle it is issued (non-blocking): it no longer waits
on the arbiter grant, so it can never head-of-line-block the config bus.

Previously next_id was an external register whose read-ack was gated on the
arbiter accepting the launch. When the backend stalled (req_ready_i low), the
read-ack was withheld, the reg block raised external_pending, and that stall
masked every further config-bus request -- deadlocking the block whenever a
next_id read's launch was not granted in its single active cycle.

The rd_swacc strobe pulses for one cycle, so capture it into a per-port
launch_pending latch that holds arb_valid and the request payload stable
until the arbiter accepts the launch (arb_ready implies req_ready_i),
honoring rr_arb_tree LockIn so a launch is never dropped when the grant is a
cycle late. The change lives in the CPUIF-agnostic launch/hwif layer, below
the per-CPUIF packing, so it holds for apb4-flat, obi-flat and axi4-lite-flat.
…heck

Self-checking testbench for the idma_reg32_3d register frontend that gates the
non-blocking next_id launch contract (NumStreams 1 and 2, apb4-flat):

- Test 2 asserts the next_id read completes within a bounded latency (2 config
  cycles) even while req_ready_i is held low. On a blocking readback (where the
  read-ack waits on the arbiter grant) this read hangs, so the per-read watchdog
  trips -- the test distinguishes the non-blocking read from a blocking one.
- Test 2b (launch integrity) holds req_ready_i low for several cycles after a
  single next_id read, then releases it, and scoreboards that the launch fires
  exactly once. This is the no-dropped-launch guarantee of the pending latch.
- Test 3 (NumStreams=2) checks stream_idx_o stays pinned to the launching
  stream until the grant lands.
- A per-read watchdog and a global timeout catch any read or run that hangs.

Because the non-blocking read no longer stalls, the driver waits for the launch
to be accepted (id-advance) before re-launching, modeling the SW poll. Wired
into Bender (idma_test) and idma.mk (idma_sim_tb_idma_reg_frontend).
Copilot AI review requested due to automatic review settings July 9, 2026 12:27

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.

stream_idx_o was driven by a last-wins scan over launch_pending, decoupled
from the rr_arb_tree grant. With NumRegs>1 and multiple config ports launching
different streams concurrently, the arbiter grants one port's dma_req_o while
stream_idx_o points at another port's stream, so the transfer is tracked and
completed under the wrong stream.

Drive stream_idx_o from the arbiter's winning index (rr_arb_tree idx_o), gated
by req_valid_o, so it always matches dma_req_o. NumRegs=1 was unaffected, which
is why the existing tests missed it.

Add a NumRegs=2 directed test: two ports launch on distinct streams, checking
stream_idx_o matches the arbitrated port's stream. Fails pre-fix, passes after.

Flagged by da-gazzi (#150).
@DanielKellerM

Copy link
Copy Markdown
Collaborator Author

Folded in the stream_idx_o arbitration bug @da-gazzi flagged (#150): stream_idx_o now rides the arbiter's winning index (rr_arb_tree.idx_o) instead of a last-wins scan, so with NumRegs>1 it always matches the arbitrated dma_req_o. Confirmed with a directed NumRegs=2 test (fails pre-fix, passes after). #150's .ready struct-vs-field fix is obsoleted here — this rewrite drops proc_dma_backpressure, so pready comes straight from the reg_top. The one remaining piece from #150 is the C-header / target/sw generation, which is orthogonal and can land separately. cc @da-gazzi — this now carries your fix; a review would be welcome.

Comment thread src/frontend/reg/tpl/idma_reg.sv.tpl Outdated
Comment thread src/frontend/reg/tpl/idma_reg.sv.tpl Outdated
Comment thread test/frontend/tb_idma_reg_frontend.sv Outdated
@da-gazzi

da-gazzi commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Mostly LGTM, you could avoid some duplicate TB logic by using test infrastructure from the APB repo

Comment thread src/frontend/reg/tpl/idma_reg.sv.tpl Outdated
Comment thread src/frontend/reg/tpl/idma_reg.sv.tpl Outdated
Comment thread src/frontend/reg/tpl/idma_reg.sv.tpl Outdated
Comment thread test/frontend/tb_idma_reg_frontend.sv
Comment thread Bender.yml Outdated
Comment thread idma.mk Outdated
Comment thread idma.mk
Rename the registered launch-latch signals (launch_pending, held_stream,
arb_dma_req) to the _q convention and collapse the verbose block comments
to a single line per the review.
Group tb_idma_reg_frontend.sv with the other frontend testbenches in
test/frontend/ and its Bender entry accordingly, and remove the verbose
sim-target comment per the review.
@DanielKellerM

DanielKellerM commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the review in two new commits (b3fa0f64, ad6b854d) — appended on top, no force-push, the existing three untouched.

Fixed

  • _q naming (@da-gazzi): launch_pending/held_stream/arb_dma_req*_q; combinational nets left as-is.
  • Verbose comments (tpl:109/181/286, idma.mk:392): tightened to a single line or removed.
  • Test location: git mvtest/frontend/, grouped with the desc64 frontend TBs in Bender.yml.
  • tpl:110 (@da-gazzi) — agreed it checks out; arb_valid is latched the same way, nothing to change.

Deferred (follow-ups)

Replace the hand-rolled strict-APB4 tasks with the standard
apb_test::apb_driver from the apb dep. Bridge one APB_DV interface per
config port to the packed dma_ctrl_req_i/rsp_o via the apb assign macros
and give each port its own driver so the multi-port arbitration test can
drive two ports concurrently. Recover the bounded read latency by timing
the blocking driver.read and subtracting its fixed framing; the per-read
watchdog still fatals a read that never returns.
@DanielKellerM

DanielKellerM commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator Author

@da-gazzi — took your apb_test.sv suggestion after all (feb5a51): the TB now drives via apb_test::apb_driver

@DanielKellerM
DanielKellerM merged commit 71efa34 into devel Jul 10, 2026
13 checks passed
@DanielKellerM
DanielKellerM deleted the frontend/reg-rdswacc-fix branch July 10, 2026 12:07
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.

3 participants