Watch 4 requestors fight over one resource. Toggle requests, see the grant pointer rotate, compare fairness vs starvation — the core of every SoC bus fabric.
Toggle requestors ON/OFF. The grant pointer always advances after each grant — nobody starves. Fair
Priority order: REQ0 > REQ1 > REQ2 > REQ3. Enable REQ0 to see how REQ1–REQ3 get starved.
| Property | Round-Robin | Fixed Priority |
|---|---|---|
| Fairness | ✓ Equal share (Jain ≈ 1.0) | ✗ Biased towards high-priority |
| Starvation | ✓ Impossible — pointer always advances | ✗ Low-priority can wait indefinitely |
| Latency for high priority | ⚠ Up to N–1 extra cycles | ✓ Always 1 cycle |
| Implementation complexity | ⚠ Slightly higher (rotating mask) | ✓ Simple priority encoder |
| Best use case | CPU cores, DMA channels, equal-priority masters | Interrupt controllers, real-time display controllers |
| Weighted variant | Weighted RR: REQ0 gets 3 slots, REQ1 gets 1 slot | Strict priority with aging (prevents starvation) |
| SoC examples | AXI interconnect, NoC routers, PCIe TLP arbitration | ARM GIC interrupt controller, USB SOF arbiter |
Classic rotating-mask implementation. Works for any N. Synthesizes to a priority encoder + register.
// Parameterized Round-Robin Arbiter
// After each grant to bit i, next arbitration starts from bit i+1
module rr_arbiter #(
parameter N = 4 // number of requestors
)(
input wire clk,
input wire rst_n,
input wire [N-1:0] req, // request vector (1 = requesting)
output reg [N-1:0] grant // one-hot grant output
);
reg [N-1:0] ptr; // one-hot pointer: next highest priority
// Rotate mask: mask off all bits at and below last grant
// Priority within the masked window, then fall back to full req
wire [N-1:0] mask_req = req & ~((ptr - 1) | ptr); // req masked by pointer
wire [N-1:0] raw_grant, mask_grant;
// Priority encoders (lowest index wins within active window)
assign mask_grant = mask_req & (~mask_req + 1'b1); // isolate LSB
assign raw_grant = req & (~req + 1'b1);
wire [N-1:0] next_grant = |mask_req ? mask_grant : raw_grant;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
grant <= '0;
ptr <= {{N-1{1'b0}}, 1'b1}; // start at position 0
end else begin
if (|req) begin
grant <= next_grant;
// Advance pointer to one past the granted position
ptr <= {next_grant[N-2:0], next_grant[N-1]}; // rotate left by 1
end else begin
grant <= '0;
end
end
end
endmodule
// Fixed Priority Arbiter (REQ[0] = highest priority)
// Simple: grant the lowest-index active request
module fp_arbiter #(
parameter N = 4
)(
input wire clk,
input wire rst_n,
input wire [N-1:0] req,
output reg [N-1:0] grant
);
// Isolate lowest-set-bit: req & (-req) in two's complement
wire [N-1:0] next_grant = req & (~req + 1'b1);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) grant <= '0;
else grant <= |req ? next_grant : '0;
end
endmodule
// Note: if req[0] is always asserted, req[1..N-1] never receive a grant.
// To prevent starvation with fixed priority, add an aging counter:
// promote a requestor to higher priority after waiting K cycles.
module tb_rr_arbiter;
parameter N = 4;
reg clk = 0, rst_n = 0;
reg [N-1:0] req;
wire [N-1:0] grant;
always #5 clk = ~clk; // 100 MHz
rr_arbiter #(.N(N)) dut (.clk(clk), .rst_n(rst_n), .req(req), .grant(grant));
integer i; reg [N-1:0] grant_cnt [0:N-1];
initial begin
for (i=0; i
A round-robin arbiter grants access to a shared resource among multiple requestors in a rotating, circular order. After granting requestor N, the next grant starts searching from requestor N+1. This ensures every requestor eventually receives a grant — preventing starvation — and distributes bandwidth fairly when all requestors have equal priority.
Starvation occurs in a fixed-priority arbiter when a high-priority requestor continuously asserts its request, preventing lower-priority requestors from ever being granted. Round-robin prevents starvation by advancing the grant pointer after every grant — the requestor that was just served has the lowest priority in the next cycle, guaranteeing every other active requestor gets served before it wins again.
Jain's fairness index J = (Σxᵢ)² / (n × Σxᵢ²) measures how equitably grants are distributed across n requestors. J = 1.0 means perfectly fair. J = 1/n means one requestor received everything. Round-robin achieves J ≈ 1.0 under uniform load. Fixed priority with one dominant requestor can drive J close to 1/N.
The standard implementation uses a rotating mask. A pointer register tracks the last-granted position. A mask is applied to zero out all requests at or below the pointer, giving higher priority to the remaining requests. A priority encoder selects the lowest active request in the masked window. If no masked request is active, it falls back to the full unmasked request vector. This produces correct round-robin behaviour in a single always block.
Use fixed priority when requestors have genuinely different latency requirements — for example, an interrupt controller where some interrupts are safety-critical, or a display controller that must fetch pixels before every frame deadline. Use round-robin when multiple masters (CPU cores, DMA channels) share a bus with similar importance and fairness matters more than minimizing peak latency for one agent.
Weighted round-robin gives each requestor a configurable number of consecutive grant slots. For example, REQ0 might receive 4 grants per round while REQ1 receives 1. This provides proportional bandwidth sharing while still guaranteeing every requestor receives some grants — preventing starvation. It is widely used in network switches and SoC NoC routers to implement Quality-of-Service (QoS) policies.