Skip to content

AMBA AXI · Module 12

Arbiter & Arbitration

How an AXI interconnect resolves contention at a shared subordinate — round-robin, fixed-priority, and QoS-weighted arbitration schemes, the fairness-vs-priority trade-off, the arbiter state machine, and starvation avoidance.

When multiple managers target the same subordinate (the per-subordinate contention of Chapter 12.2), the interconnect must decide who goes first — that's the arbiter's job. The arbitration scheme determines the system's fairness (does any manager starve?), throughput (is the port kept busy?), and priority (do latency-critical masters get precedence?). This chapter covers the main schemes — round-robin, fixed-priority, and QoS-weighted — the fairness-vs-priority trade-off, the arbiter as a state machine, and how to avoid starvation.

1. The Arbiter's Job

At each shared subordinate port, an arbiter sees the set of managers currently requesting access and grants one per cycle (or per transaction). The granted manager's transaction proceeds; the others wait and are reconsidered next round. Arbitration happens on the address channels (AW and AR contention are arbitrated, each independently — read and write are separate), and similarly on response paths where they contend.

A good arbiter has three properties: no starvation (every requester eventually wins), high throughput (the port isn't idle while requests are pending — grant someone every cycle there's a request), and policy (it can favor higher-priority/QoS masters when desired). The scheme chosen trades these off.

Three managers request one subordinate; an arbiter grants one per round and the others wait.M0 requestswaitingM1 requestsgrantedM2 requestswaitingArbitergrant one per roundSubordinateserves granted manager12
Figure 1 — the arbiter at a shared subordinate port. Multiple managers request the same subordinate; the arbiter grants one per round, the others wait and are reconsidered. Arbitration is per-channel (AW and AR independently). A good arbiter is starvation-free, keeps the port busy, and can honor priority/QoS.

2. The Arbitration Schemes

The common schemes:

  • Round-robin (RR): rotate priority among requesters so each gets a fair turn — after granting a manager, it goes to the back of the line. No starvation, fair bandwidth distribution. The common default for general traffic.
  • Fixed-priority: managers have static priorities; the highest-priority requesting manager always wins. Simple and low-latency for the top priority, but lower-priority masters can starve if a high-priority one is always requesting.
  • QoS-weighted: use AxQOS (Chapter 6.5) to weight arbitration — higher-QoS transactions get more grants/precedence. Programmable priority that lets software/system tune which masters get bandwidth under contention.
  • Weighted round-robin / least-recently-granted: hybrids that give some masters proportionally more turns while still bounding everyone's wait — balancing fairness and priority.

The choice depends on the traffic: RR for fairness among equals, fixed-priority where one master is genuinely most critical (and starvation of others is acceptable or impossible), QoS-weighted for tunable, mixed-criticality systems.

Round-robin fair no starvation; fixed-priority simple but starves; QoS-weighted programmable; weighted-RR balances.Round-robinfair, no starvation (default)Fixed-prioritytop wins, can starve othersQoS-weightedAxQOS-tuned precedenceWeighted RR / LRGproportional + bounded wait12
Figure 2 — arbitration schemes. Round-robin: rotate turns, fair, no starvation (default). Fixed-priority: highest-priority requester always wins — simple, low-latency for top priority, but can starve the rest. QoS-weighted: AxQOS weights grants — programmable, tunable bandwidth. Weighted-RR/LRG hybrids balance proportional priority with bounded waits.

3. The Arbiter State Machine

Conceptually, an arbiter cycles through request → grant → advance:

Idle to Grant on a request, Grant to Rotate on acceptance, Rotate to Grant if more requests or to Idle if none.Idle (norequests)GrantwinnerRotatepointerrequest(s) present: grant (RR order)request(s) present: grant (RR order)request(s)present: grant (R…transfer accepted: advance past winnertransfer accepted:advance past winnermore requests pendingmore requests pendingno requestsno requests
Figure 3 — a round-robin arbiter state machine. From Idle (no requests), when request(s) arrive it grants the highest-priority requester in the current rotation (Grant); when the transfer is accepted it advances the rotation pointer past the winner (Rotate); then it grants the next pending requester, or returns to Idle if none. The rotating pointer is what makes it fair (no starvation).

The rotating pointer is what gives round-robin its fairness: after a manager wins, the pointer moves past it, so it won't win again until everyone ahead of it in the rotation has had a turn — bounding each manager's worst-case wait. Fixed-priority lacks this (the pointer never moves off the top priority), which is why it can starve. The arbiter also typically grants per address transaction (once granted, the manager's burst proceeds on the granted path), then re-arbitrates for the next.

4. Fairness vs Priority — The Core Trade

Arbitration is fundamentally a fairness-vs-priority trade-off:

  • Fairness (round-robin) treats requesters equally — no one starves, bandwidth is shared evenly. Best when masters are peers and predictable latency for all matters.
  • Priority (fixed-priority) favors the most critical master — lowest latency for it — at the cost of potentially starving others. Best when one master genuinely must come first (e.g., a real-time display controller that must never underflow) and starving others is acceptable.
  • QoS-weighted is the tunable middle: it lets the system grant proportionally more bandwidth to higher-QoS masters without fully starving lower ones — combining priority's responsiveness with fairness's safety, programmable per workload.

Real interconnects often use QoS-weighted or weighted round-robin precisely because pure fairness ignores criticality and pure priority risks starvation. The arbitration policy is a key performance knob: it determines, under contention, which master gets the bandwidth and which waits — directly shaping system latency and throughput.

Round-robin for fairness, fixed-priority for criticality with starvation risk, QoS-weighted as the tunable balance.peersone criticalmixedEqual peers,or mixedcriticality?Equal →round-robin(fair, nostarvation)One critical →fixed-priority(starvationrisk)Mixed →QoS-weighted(tunablebalance)
Figure 4 — the fairness-vs-priority trade. Round-robin maximizes fairness (no starvation, equal shares) but ignores criticality. Fixed-priority maximizes responsiveness for the top master but can starve others. QoS-weighted (and weighted-RR) is the tunable middle — proportional priority with bounded waits. The policy is a performance knob set to the traffic's criticality mix.

4b. The Arbiter, Written Out

Figure 3 is drawn as a three-state machine because that is how the policy reads. The implementation is not a state machine at all: the grant is combinational from the current requests and a rotation pointer, and the only sequential element is the pointer itself. Writing it out makes the fairness argument concrete, and it makes the starvation bug in the Debug Lab below visible.

rr_arbiter.sv — round-robin by rotating priority mask
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module rr_arbiter #(
  parameter int N = 4
) (
  input  logic         clk,
  input  logic         rst_n,
  input  logic [N-1:0] req,
  // The granted requester must still be accepted downstream. In an AXI
  // interconnect this is the AW/AR channel handshake: the arbiter has issued
  // a grant, but nothing has happened until READY is also high.
  input  logic         ready,
  output logic [N-1:0] grant
);
  // `mask` holds the requesters that come AFTER the last winner in rotation
  // order. Priority within a mask is simply lowest-index-first.
  logic [N-1:0] mask;
 
  // Isolate the lowest set bit: v & (-v). This is the whole priority encoder.
  function automatic logic [N-1:0] lowest_set (input logic [N-1:0] v);
    return v & (~v + 1'b1);
  endfunction
 
  wire [N-1:0] masked_req = req & mask;
 
  // Try the requesters after the last winner first; if none of them are
  // asking, wrap around and serve the lowest-index requester overall. That
  // wrap is the "round" in round-robin.
  assign grant = (|masked_req) ? lowest_set(masked_req)
                               : lowest_set(req);
 
  wire granted_now = (|grant) && ready;
 
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      mask <= '1;                       // first round: everyone eligible
    end else if (granted_now) begin
      // Set every bit strictly above the winner. `grant | (grant-1)` fills
      // bits 0..winner, so the inverse is bits winner+1..N-1.
      mask <= ~(grant | (grant - 1'b1));
      // When the winner was the top index this evaluates to all-zero, so the
      // next cycle finds masked_req empty and wraps to lowest_set(req).
      // The wrap is not a special case in the code because it does not need
      // to be one.
    end
    // NOTE the absence of an else. The pointer does NOT move when a grant was
    // issued but not accepted. That omission is the entire subject of the
    // Debug Lab below.
  end
endmodule

For contrast, fixed priority is the same module with the pointer deleted:

fp_arbiter.sv — fixed priority, for comparison
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module fp_arbiter #(parameter int N = 4) (
  input  logic [N-1:0] req,
  output logic [N-1:0] grant
);
  // No state, no rotation: requester 0 wins whenever it asks.
  assign grant = req & (~req + 1'b1);
endmodule

Putting them side by side makes the trade-off from Section 4 mechanical rather than rhetorical. Round-robin costs N flops and one extra mux layer, and in exchange the pointer guarantees that a requester cannot be passed over twice in a row while others are served. Fixed priority costs nothing and offers no such guarantee — requester 0's behaviour alone determines whether requester 3 ever runs.

Proving the fairness claim

"No starvation" is the property worth checking, and it is the one a directed test cannot establish. These bind onto the arbiter:

rr_arbiter_sva.sv — the properties that make the claim checkable
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module rr_arbiter_sva #(parameter int N = 4, parameter int MAX_WAIT = 64) (
  input logic clk, rst_n,
  input logic [N-1:0] req, grant,
  input logic ready
);
  // Safety: at most one grant, and never to a requester that is not asking.
  a_onehot: assert property (@(posedge clk) disable iff (!rst_n)
    $onehot0(grant));
  a_grant_implies_req: assert property (@(posedge clk) disable iff (!rst_n)
    (grant & ~req) == '0);
 
  // Progress: if anyone is asking and the downstream is ready, somebody must
  // be granted. An arbiter that stalls with requests pending is broken even
  // if it never grants incorrectly.
  a_no_idle_stall: assert property (@(posedge clk) disable iff (!rst_n)
    (|req && ready) |-> |grant);
 
  // Liveness, made checkable by bounding it. True liveness ("eventually
  // granted") is unfalsifiable in a finite simulation, so we assert the
  // engineering requirement instead: a continuously-asserted request is
  // served within MAX_WAIT cycles. Size MAX_WAIT from the worst legal case -
  // N-1 other requesters times the longest transfer each can occupy.
  for (genvar i = 0; i < N; i++) begin : g_starve
    int unsigned waiting;
    always_ff @(posedge clk or negedge rst_n)
      if (!rst_n)                        waiting <= 0;
      else if (grant[i] && ready)        waiting <= 0;
      else if (req[i])                   waiting <= waiting + 1;
      else                               waiting <= 0;
 
    a_bounded_wait: assert property (@(posedge clk) disable iff (!rst_n)
      waiting < MAX_WAIT)
      else $error("requester %0d waited %0d cycles - starvation", i, waiting);
  end
 
  // Coverage that makes a pass mean something: every requester actually won
  // at least once, and the all-requesting case was actually exercised.
  for (genvar i = 0; i < N; i++) begin : g_cov
    c_won: cover property (@(posedge clk) disable iff (!rst_n) grant[i] && ready);
  end
  c_full_contention: cover property (@(posedge clk) disable iff (!rst_n) &req);
endmodule

The counter-based bounded-wait property is the one that earns its place. The $onehot0 and grant-implies-request checks catch a broken encoder, but a perfectly well-formed arbiter can still starve a requester forever, and only the counter notices. Note also c_full_contention: without it, a clean run cannot distinguish "fair under load" from "never had two simultaneous requesters".

1

A round-robin arbiter starved a master, and the fairness logic was the cause

ROTATE-ON-GRANT-NOT-ACCEPT
Symptom

A four-master AXI interconnect showed one master achieving roughly a third of the bandwidth the other three got, despite a round-robin arbiter that had been reviewed and was believed fair. The disparity only appeared when the shared slave applied back-pressure — under light load all four masters were served evenly, and the performance model that had signed off the design assumed exactly that light-load behaviour. On a benchmark that saturated the slave, the affected master's worst-case latency was unbounded in the sense that mattered: it kept growing with run length rather than settling.

Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The pointer advanced whenever a grant was ISSUED, regardless of whether
// the downstream accepted it.
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)      mask <= '1;
  else if (|grant) mask <= ~(grant | (grant - 1'b1));   // <-- no `ready`
end
Diagnostic Evidence

Two measurements localised it. A histogram of accepted grants per master showed the expected 25% each under no back-pressure and a clear skew as soon as ready began deasserting — so the bug was conditional on the handshake, not on the request pattern. Then a waveform of a stalled window showed the decisive detail: master 2 was granted for three consecutive cycles while ready was low, and on the fourth cycle, still without any transfer having occurred, the grant moved to master 3.

That is the whole bug in one screenful. A grant that is never accepted is not a turn. The arbiter had counted it as one.

Root Cause

The rotation pointer advanced on |grant rather than on |grant && ready. In AXI terms, the arbiter treated issuing AWVALID as completing the transaction, when the transaction only occurs on the cycle where AWVALID and AWREADY are both high.

The consequence is not merely a lost turn, it is a systematically unfair one. When the slave stalls, the arbiter rotates through all four masters while nothing is transferring. The master that happens to hold the grant on the cycle ready finally rises is the one that transfers — and which master that is depends on the phase relationship between the rotation and the stall pattern. A master whose grant window repeatedly coincided with the low part of a periodic ready was passed over every round. The fairness mechanism, running open-loop against a handshake it was ignoring, became the mechanism of unfairness.

This is why the property in the previous section counts cycles a request has been waiting rather than checking grant distribution. A distribution check on issued grants looks perfectly fair here — all four masters get grants at exactly the same rate. Only accepted grants tell the truth.

Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Advance the pointer only when a transfer actually occurred.
wire granted_now = (|grant) && ready;
 
always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)          mask <= '1;
  else if (granted_now) mask <= ~(grant | (grant - 1'b1));
end

Verifying the fix requires back-pressure in the stimulus, which is the part the original verification plan was missing. The test that fails on the old RTL and passes on the new one drives all four masters continuously and toggles the slave's ready with a duty cycle deliberately chosen to beat against the rotation period — a pattern random ready stalling will find eventually, and a fixed "stall every other cycle" pattern may never find at all.

The bounded-wait assertion above is the durable form of the check, because it holds under any ready pattern rather than the one the test happened to pick. Bind it and the failure is caught by any test that applies sustained contention, not only by the test written to look for it.

The generalisation is worth carrying to other arbiters: any arbiter feeding a handshake must advance its state on the handshake, not on its own output. The same defect appears in credit-return logic, in skid-buffer pointer updates, and in scheduler round-robins — anywhere the thing being counted is an intention rather than an event. See VALID/READY handshake for the rule this rests on and handshake deadlock rules for the related failure where the dependency runs the other way.

5. Common Misconceptions

6. Debugging Insight

7. Verification Insight

8. Interview Questions

8b. Where This Is Specified

  • Arm AMBA AXI Protocol Specification (ARM IHI 0022). The VALID/READY handshake rule that a transfer occurs only when both are asserted, which is the rule the arbiter's pointer update depends on, and the independence of the AW, AR, W, R and B channels that makes per-channel arbitration necessary.
  • Arm AMBA AXI, AxQOS signalling. The four-bit quality-of-service identifier an interconnect may use to weight arbitration. The specification defines the signal and its transport; the arbitration policy that consumes it is implementation-defined.
  • IEEE 1800-2023 §16 — Assertions. The concurrent assertion and cover property constructs used for the safety and bounded-wait checks, and $onehot0.
  • IEEE 1800-2023 §27 — Generate constructs. The genvar loop that replicates the per-requester starvation counters.

Round-robin masking, weighted round-robin and least-recently-granted are standard design practice rather than standardised behaviour — AMBA constrains the handshake an arbiter must honour, not the policy it implements.

9. Summary

The arbiter resolves contention at a shared subordinate port (per-subordinate, per-channel), granting one of the competing managers per round. The main schemes: round-robin (rotate turns — fair, no starvation, the default for peers), fixed-priority (top requester always wins — simple and low-latency for it, but can starve others), QoS-weighted (use AxQOS to weight grants — programmable, proportional priority without full starvation), and weighted-RR/LRG hybrids. The rotating pointer is what makes round-robin starvation-free; fixed-priority's static pointer is why it can starve. A good arbiter is starvation-free, work-conserving (no idle cycles with pending requests), and policy-honoring.

The core trade-off is fairness vs priority: round-robin maximizes fairness, fixed-priority maximizes responsiveness for one master (risking starvation), and QoS-weighting is the tunable middle for mixed-criticality systems — which is why real interconnects favor QoS-weighted/weighted-RR. Arbitration is a key performance knob, and its bugs (starvation, unfairness, ignored QoS, priority inversion, non-work-conserving) are performance/fairness failures that functional tests miss — so verify the fairness/weighting behavior (bounded waits, grant distribution, utilization) under sustained contention, not just transaction correctness. Next: mux/demux and ID-based routing — how responses find their way back through the interconnect.

10. What Comes Next

You've got contention resolution; next, routing responses back:

Previous: 12.3 — Decoder & Address Map. Related: 6.5 — AxPROT, AxQOS & AxREGION for the QoS signal, and 12.2 — Crossbar Architecture for the per-subordinate contention this resolves. For the broader protocol catalog, see the AMBA family overview doc.