A round-robin arbiter shares a link between a DMA engine and a processor. The arbiter is perfectly fair: it alternates between them. Yet the processor's single-word reads still wait more than 60 cycles at times. The arbiter alternates between bursts, and the DMA engine's bursts are 64 beats long. Fairness in grants is not fairness in cycles.
How often should an arbiter be allowed to switch, and what does each choice cost?
The obvious arbiter grants a whole transaction: once a requester wins, it keeps the resource until its burst is done. That keeps a burst's beats together, so the target and the protocol stay simple. But every other requester now waits for the longest burst anyone might send, however fair the arbiter is. The unit of arbitration sets the minimum wait.
Granularity | The arbiter may switch | Wait for others | Cost |
|---|---|---|---|
Per locked sequence | Only when a lock is released | Whole sequence of transactions | Worst latency; risk of priority inversion (lesson 8.4) |
Per burst (per transaction) | Between transactions | Up to the longest burst | Simple: beats of one burst stay together |
Per beat (per flit) | Every beat | About one beat per competitor | Every beat must carry its owner (an ID), receivers must reassemble (lesson 6.7) |
Finer granularity only helps if the protocol allows it: switching mid-burst means interleaving, which the receiver must support. That is why read data can often be arbitrated per beat (receivers track IDs) while write data in AXI4 cannot (lesson 6.7).
Requester A sends 8-beat bursts back to back; requester B asks once, for a single beat. Two arbiters run side by side: one switches only between A's bursts, the other may switch every beat.
// Arbitration granularity. Requester A sends 8-beat bursts back to back; requester
// B needs single beats now and then. Two arbiters run side by side:
// per burst: once A starts a burst it keeps the link until the burst ends
// per beat: the arbiter may switch every beat (round-robin between A and B)
// The checker records how long B waits for its first beat under each policy.
module tb;
logic clk = 0;
always #5 clk = ~clk;
initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end
int cyc = 0;
always @(posedge clk) cyc <= cyc + 1;
wire a_req = (cyc >= 1); // from cycle 1, A always has a burst ready
wire b_req = (cyc == 3); // B asks in cycle 3 for one beat
// Per-burst arbiter: A's burst runs 8 beats; between bursts B wins if it is waiting
logic b_pend_x = 0, gnt_b_x;
logic [2:0] beat_x = 0;
logic in_burst_x = 0;
assign gnt_b_x = !in_burst_x && b_pend_x;
wire gnt_a_x = a_req && !gnt_b_x;
always @(posedge clk) begin
if (b_req) b_pend_x <= 1; else if (gnt_b_x) b_pend_x <= 0;
if (gnt_a_x) begin beat_x <= beat_x + 1; in_burst_x <= (beat_x != 7); end
end
// Per-beat arbiter: alternates whenever both want the link
logic b_pend_y = 0, last_b_y = 0;
wire gnt_b_y = b_pend_y && !last_b_y;
wire gnt_a_y = a_req && !gnt_b_y;
always @(posedge clk) begin
if (b_req) b_pend_y <= 1; else if (gnt_b_y) b_pend_y <= 0;
last_b_y <= gnt_b_y;
end
int asked = 3, got_x = -1, got_y = -1;
always @(posedge clk) begin
if (gnt_b_x && got_x < 0) got_x = cyc;
if (gnt_b_y && got_y < 0) got_y = cyc;
end
initial begin
#200;
if (got_y == 4 && got_x == 9) $display("PASS: B waits %0d cycle with per-beat arbitration, %0d cycles with per-burst", got_y - asked, got_x - asked);
else $display("FAIL: got_y %0d got_x %0d", got_y, got_x);
$finish;
end
endmoduleThe per-burst arbiter's only extra state is in_burst_x: while A is inside a burst, B cannot win.
How to read it, slot by slot:
Slots 0 to 7, per burst: A holds the link for its 8-beat burst (gnt_a_x).
Slot 2: B asks (b_req).
Slot 3, per beat: the per-beat arbiter grants B at once (gnt_b_y), then returns to A: B waited one cycle.
Slot 8, per burst: only when A's burst has ended does the per-burst arbiter grant B (gnt_b_x): B waited six cycles, and would wait up to a full burst in the worst case.
Illustrative: a processor's single-beat read competes with a DMA engine's bursts on a link that moves one beat per cycle.
DMA burst length: 16 beats 64 beats
Per-burst arbitration, worst wait: 16 cycles 64 cycles
Per-beat arbitration, worst wait: 1 cycle 1 cycle
DMA throughput loss per processor read: 1 beat either wayThe throughput cost to the DMA engine is the same (one beat per processor read); only the processor's wait changes. That is why a common compromise is per-burst arbitration with a cap on burst length for shared links: it keeps bursts together and bounds the wait.
Protocol | Granularity |
|---|---|
AXI | Per transaction on address channels; read data may be interleaved per beat between IDs; write data per transaction (no interleaving in AXI4). |
PCI Express | Per packet: a packet is never split, so the maximum payload size bounds how long one packet holds a link. |
Network-on-chip routers | Per packet with wormhole switching (a packet holds a channel until its tail passes), or per flit across virtual channels (lesson 8.8). |
The opening system (illustrative): the DMA engine's 64-beat bursts made the processor's worst-case read wait exceed its budget, although the arbiter was round-robin. The fix was to cap DMA bursts at 16 beats on that link, which cut the worst wait by four and cost the DMA engine nothing measurable.
Two other requesters' bursts: up to 64 cycles.
It may switch on any beat, so as soon as B asks, the next beat can be B's.
Arbitration granularity (per beat, per burst, per lock) sets the minimum wait for competitors.
Finer granularity needs interleaving support; coarser granularity needs bounded burst or packet sizes.
Worst wait = grants to others × longest hold; cap holds on shared links.