Fixed priority always serves the lowest-index active requester. It is cheap: a priority encoder on the request vector.
The cost is starvation. A chatty high-priority master can lock out a lower one forever, so the starvation behavior must be documented, not discovered.
Round-robin remembers the last completed owner and searches after that owner for the next grant. The registered grant stays fixed until accept_i is sampled high, then clears for an arbitration cycle before selecting another pending request. This conservative handoff avoids reselecting a completed request before its master can withdraw it. With persistent requests, each master is served within N accepted ownership intervals; a clock-cycle bound additionally requires bounds on arbitration and downstream stalls.
accept_i completes the currently granted ownership interval, not the grant being selected for the next cycle. For a shared bus carrying a request and its reply, assert it only when that transaction completes so replies still route to the owner. A request-only arbiter may use the request handshake if a separate queue tracks response ownership. The first idle-to-active edge selects an owner without consuming its request.
A requester holds its pending request and payload until completion, then withdraws or offers its next request during the idle arbitration cycle. A grant reserves ownership; it is not a new transfer on every held cycle. The bus transaction engine must issue the request once and retain response context until accept_i.
Register grant signals. A registered grant breaks the request-to-grant path and avoids combinational multi-drivers on shared buses.
N may be 1 (a single master, granted whenever it requests): the pointer width is guarded as localparam int AW = (N > 1) ? $clog2(N) : 1; because $clog2(1) is 0.
This selector-only waveform illustrates winner order with every request high, without transaction ownership or handoff cycles. The transaction-owning RTL below retains that winner order but holds the owner until completion and inserts an idle arbitration cycle after each completion. Its test checks that timing separately.
Slot 0: reset holds both grants at 0.
Slot 1: both arbiters serve master 0 first.
Slot 2: round-robin moves to master 1 while fixed priority stays on master 0.
Slot 3: round-robin moves to master 2 while fixed priority stays on master 0.
Slot 4: round-robin moves to master 3 while fixed priority stays on master 0.
Slot 5: round-robin wraps to master 0. Master 3 was never served under fixed priority.
The tool starts with requesters 0, 1 and 3 asking every cycle (requests 1101) and shows eight cycles of both schemes, fixed priority described first.
Loading arbiter comparison…
Compare the two timelines: fixed priority grants requester 0 in all 8 cycles, so requesters 1 and 3 starve; round robin grants 0, 1, 3, 0, 1, 3 and nobody waits more than 2 cycles.
Turn requester 0 off and press Step four times: fixed priority now serves requester 1 every cycle, and requester 3 still starves.
Turn every requester on (1111) and press Step eight times: round robin serves 0, 1, 2, 3 in turn, as in the waveform, and the longest wait is 3 cycles, one less than the number of requesters.
This is the real arb_4 from the listing. Each slot is one clock cycle; grant is registered, so a decision made in one slot shows in the next. ptr is the round-robin cursor.
Slots 2 to 4: master 0 owns the bus. accept_i is low in slots 2 and 3 (its transaction is stalled), so the grant and ptr hold.
Slot 4: accept_i is high, so the grant clears at the end of the slot and ptr moves past the completed owner to 1.
Slots 5, 7 and 9: idle arbitration cycles with no grant. Each next owner (0010, 0100, 1000) appears one slot later.
Rate: with persistent requests and immediate completion, one ownership interval ends every two cycles, which is why the bench counts 40 services in 80 cycles.
Zero. Master 0 wins every cycle, so master 3 starves. That is the documented behavior, and the reason fair fabrics use round-robin.
With acceptance low, master 2 retains ownership. With acceptance high, the grant clears for an arbitration cycle, then master 3 is selected. Persistent requests wait at most three other accepted services, not necessarily three clock cycles.
A parameterized arbiter in both modes plus a bounded checker. In addition to totals, each persistent request counts other accepted services while waiting, with a bound of active_request_count - 1. All 15 nonempty four-master masks are exercised; stalled clocks do not increment service age.
// arb_4: 1-to-4-master arbiter, fixed priority or round-robin, with registered grants.
module arb_4 #(
parameter int N = 4, // number of masters; N must be at least 1
parameter int RR = 1 // 1: round-robin, 0: fixed priority (lowest index wins)
)(
input logic clk, rst_n,
input logic [N-1:0] req,
input logic accept_i, // currently granted ownership interval completed
output logic [N-1:0] grant // registered owner; retained until accepted
);
logic [N-1:0] grant_q, grant_next;
// N = 1 guard ($clog2(1) is 0, so [$clog2(N)-1:0] would be [-1:0]).
localparam int AW = (N > 1) ? $clog2(N) : 1;
logic [AW-1:0] ptr;
int idx;
always_comb begin
grant_next = {N{1'b0}};
idx = 0;
if (|req) begin
if (RR == 0) begin
for (int i = 0; i < N; i++)
if (req[i] && grant_next == {N{1'b0}}) grant_next[i] = 1'b1;
end else begin
for (int k = 0; k < N; k++) begin
idx = ptr + k;
if (idx >= N) idx = idx - N;
if (req[idx] && grant_next == {N{1'b0}}) grant_next[idx] = 1'b1;
end
end
end
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
grant_q <= {N{1'b0}};
ptr <= '0;
end else if (!(|grant_q)) begin
grant_q <= grant_next;
end else if (accept_i) begin
grant_q <= '0;
// The cursor advances from the completed owner, never from a stalled candidate.
for (int i = 0; i < N; i++)
if (grant_q[i]) begin
if (i == N-1) ptr <= '0;
else ptr <= AW'(i + 1);
end
end
end
assign grant = grant_q;
endmodule
Source checker, run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It runs a parameterised arbiter in both modes, counts for each persistent request the other accepted services while it waits (bound: active_request_count - 1), and exercises all 15 nonempty four-master masks. Stalled clocks do not increment service age.