Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • RTL Foundations36
  • Combinational Design25
  • Sequential Design27
  • FSM & Control27
  • Timing & Clocking39
  • Reset & CDC34
  • Memory & Buses57
    • SRAM and ROM
    • Register Files
    • Valid/Ready Handshake
    • AXI-Lite
    • AXI-Stream
    • Bus Integration
      • Address Decode
      • Mux Trees and Read Paths
      • Error Paths and Timeouts
      • Arbitration
      • Integration Checklist
      • An End-to-End Bus Example
    • Synchronous FIFO
    • Register-Map and CSR RTL
    • Memory Reliability
    • Peripheral RTL Integration
    • Full AXI4: RTL Roadmap
  • Advanced RTL28
  • Senior & Interview76
  • Glossary and Notation
Loading lesson

‹Bus Integration
  1. Topics
  2. RTL Design
  3. Memory & Buses
  4. Bus Integration
  5. Arbitration

Part 7 · Memory & Buses · Intermediate

Arbitration

Fixed-priority versus round-robin arbitration with registered grants and a bounded-fairness testbench.

On this page

    Why this matters

    Fixed priority lets a chatty high-priority master lock out a lower one forever. A registered grant that moves during a stall changes the payload in the middle of a transaction.

    After this lesson you can: choose between fixed priority and round-robin, hold a registered grant until acceptance, and state the fairness bound.

    Fixed priority

    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 and registered grants

    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.

    Try it: watch the grants

    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.

    Arbiter comparison

    Loading arbiter comparison…

    Selector only, like the waveform above: one grant per cycle, each grant completes at once and there is no idle hand-off cycle. Round robin starts with its pointer at 0 and moves it one past each owner; a cycle with no grant leaves it where it is.
    • 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.

    Common mistakes

    Common pitfall

    Registering grants does not by itself retain ownership during backpressure. Hold the registered owner until its acceptance/completion event; advancing to another master during a stalled transaction can change the payload or route a delayed reply to the wrong master.

    Check yourself Four masters request forever under fixed priority. How many grants does master 3 get?

    Zero. Master 0 wins every cycle, so master 3 starves. That is the documented behavior, and the reason fair fabrics use round-robin.

    Check yourself Master 2 owns the grant and all four request. Who owns the next cycle if acceptance is low, and if it is high?

    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.

    Testbench

    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.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    // 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
    

    Remember

    • Fixed priority is a priority encoder and cheap, but it can starve a master, so document that.

    • Round-robin searches after the last owner. With persistent requests, each master is served within N accepted ownership intervals.

    • A grant stays until accept_i is high, then clears for one arbitration cycle before the next owner is chosen.

    How this was checked

    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.

    Keep going

    • Practice: Memory arbiter for two masters

    • Practice: Round-robin arbiter for AXI masters

    • Code lab: Round-robin arbiter

    • Interview: run a mock interview on this topic

    Related topics

    • Mux Trees and Read Paths

    • Integration Checklist

    Previous

    213 / 350

    Next: Integration Checklist

    Up next

    Up nextIntegration Checklist

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Address Decode
    • Mux Trees and Read Paths
    • An End-to-End Bus Example
    clkreqfgrant_rr012481grant_fx01
    All masters requesting: round-robin rotation against fixed-priority starvation
    clkreq00001111accept_igrant00000001000000100000010000001000ptr0123
    The transaction-owning arb_4 in round-robin mode with all four masters requesting: master 0 keeps the grant through a stall, then each completion clears the grant for one idle cycle before the next owner