Add write skid buffer for same-register read-modify-write - #330
Conversation
Under token discipline, an operation that reads and writes the same register within one ctrl step (an accumulator, or #281's `NOT $0 -> $0, SOUTH` under backpressure) deadlocked: the write was rejected while the operand token was unconsumed, and the step could not complete until the write was delivered. Each bank now has a one-entry write skid buffer (one write port -> at most one blocked write). A write is accepted whenever the skid is free (outport_wr_rdy = ~skid_valid, pure registered state, so the producer's rdy never combinationally depends on any consumer's readiness and never collides with a commit on the single write port): - It lands directly if that cannot disturb an in-flight read (target token-free and not read by the current step), or at the boundary of the reading step (the write's token atomically replaces the consumed one; set wins over clear in the token update). - Otherwise it parks in the skid and commits once its target is no longer read by an open step: at that step's completion pulse, or immediately if the target is not being read and holds no token. The stability invariant — a register being read holds its value until the step completes — also fixes the partial-multicast corruption from issue #281 (a speculative register write landing while another fan-out leg is backpressured, followed by re-execution on the changed value), which the previous direct-write-if-token-free behavior still allowed. Tests: - test_reg_cluster_write_skid_buffer: three back-to-back writes with a stalled consumer flow through park/commit in order. - test_tile_same_register_accumulate: single-ctrl-word INC accumulator through one register with a stalled tile outport (the #281 shape); deadlocks without the skid buffer (verified), completes with it. - The cluster test harnesses now always emulate the tile's per-step done-tracking to drive inport_ctrl_proceed, since commit timing is tied to step completion.
2c43ee9 to
b672077
Compare
| # inport_ctrl_proceed and would otherwise close a loop through | ||
| # the FU's rdy chain), and it also guarantees a direct write can | ||
| # never collide with a skid commit on the single write port. | ||
| s.outport_wr_rdy @= ~s.skid_valid |
There was a problem hiding this comment.
How about letting outport_wr_rdy @= inport_ctrl_proceed?
Line 304 in 3d4d057
Lines 296 to 298 in 3d4d057
Lines 314 to 319 in 3d4d057
There is no combinational loop as three registers xxx_done are involved.
Semantically, inport_ctrl_proceed=1 at cycle N means the token will be consumed at cycle N. Then the outport_wr_rdy=1 at cycle N will allow the new token (either from FU computation result or routing buffer) being directly and safely written to register bank at the rising edge of cycle N+1. During cycles when inport_ctrl_proceed=0, the new token is either combinationally maintained on FU's outport or kept in the routing buffer, and outport_wr_rdy=0 also avoids the unsafe overwriting. Seems the skid buffer is no longer necessary here.
Taking NOT $0 -> $0, SOUTH as an example, no routing buffer space of the south tile at cycle N results in inport_ctrl_proceed=0, then the computation result of NOT (new token) will not overwrite $0 at the rising edge of cycle N+1 because outport_wr_rdy=0. If south tile suddenly has buffer space at cycle 2N, we will have inport_ctrl_proceed=1 and outport_wr_rdy=1 at cycle 2N, then the new token that is combinationally kept on FU's outport during cycle N~2N can safely overwrite $0 at the rising edge of cycle 2N+1.
Follow-up to #322 (based on its branch; retarget to
masterafter #322 merges). Removes #322's known limitation and addresses the register half of #281/#286.Problem
Under #322's token discipline, an operation that reads and writes the same register within one ctrl step deadlocked: the write was rejected while the operand's token was unconsumed, and the step could not complete until the write was delivered. This pattern is real — the mapper emits it (
NOT $0 -> $0, SOUTHin the histogram kernel from #281), and it is the natural encoding of an accumulator.Design
Each bank gains a one-entry write skid buffer (one write port ⇒ at most one blocked write, so one entry per bank suffices; the cluster/tile interfaces are unchanged).
outport_wr_rdy = ~skid_valid— a write is accepted whenever the skid is free. Pure registered state: the producer'srdynever combinationally depends on any consumer's readiness (deliberately not onskid_commit, which derives fromctrl_proceedand would close a loop through the FU's rdy chain), and a direct write can never collide with a commit on the single write port.ctrl_proceedpulse — the new token atomically replaces the consumed one; set wins over clear). Otherwise it parks in the skid and commits once its target is no longer read by an open step.With the write-through path, a same-register accumulator runs at full rate (one iteration per step, no bubble).
Tests
test_reg_cluster_write_skid_buffer: three back-to-back writes to one register with a stalled consumer flow through park→commit→park; all three delivered in order.test_tile_same_register_accumulate: single-ctrl-wordINCaccumulator through one register that also fans out to a stalled tile outport — the [P0] Simulator and rtl comparison (latency gap) debug #281 shape end-to-end at tile level. Deadlocks at max-cycles against the Prevent overwriting unconsumed tokens in register banks #322 base (verified as a negative control); completes with the skid. The unarmed first read seeds the accumulator with the register default, so the outport observes 1, 2, 3.ctrl_proceed), since commit timing is tied to step completion; historical expectations are preserved, with stalled-sink tests observing one leading unarmed read.CgraRTL_test×3, FIR terminate, streaming ×2, vector global reduce, migration ×3): 10/10 pass locally.Relation to #286
The xbar half of #281 (
send_rdy_vectorignoringsend_accepted) is separate and remains open in #286 — this PR handles the register-file half (stability + RMW liveness).