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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
    • Topologies and Bridges
    • AXI Integration Details
    • Ordering and Deadlock at the Top
    • QoS and Arbitration
      • Round-Robin and Its Measured Cost
      • Starvation, Priority and Shaping
      • QoS in Practice on NanoSoC
    • NoC Basics
    • Coherent Interconnect View
    • Firewalls and Protection
    • Bandwidth and Latency Verification
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←QoS and Arbitration
  1. Topics
  2. SoC Design
  3. Interconnect and Bus Fabrics
  4. QoS and Arbitration
  5. Round-Robin and Its Measured Cost
On this page

    Part 4 of 14, lesson 11 of 26Part 4 of 14/Interconnect and Bus Fabrics/Lesson 11 of 26All levels

    Round-Robin and Its Measured Cost

    Fair by rotation, taxed on every burst: what NanoSoC round-robin costs a shared slave, beat by beat.

    Why it matters

    The peak table says 384.0 MB/s per link, but the NanoSoC link table quietly quotes 361.4 sustained for 16-beat bursts. The missing 22.6 MB/s is not a slower slave or a narrower bus: it is one idle cycle per burst. NanoSoC's model charges that bubble once per burst whether or not the grant changes hands: a lone master pays it as address-to-data turnaround, alternating masters pay it as a switch. Any budget that multiplies peak by time without the bubble is wrong before simulation starts.

    The core idea

    Round-robin grants masters in rotation, which guarantees no master waits longer than one rotation: starvation-free by construction. A burst of L beats costs L plus 1 cycles, so efficiency is L over L plus 1: four-beat cache-line fills run at 80 percent, sixteen-beat streams at 94.1, and single-beat register traffic at 50, all before any slave latency. Two details make the circuit correct. The last-granted bit must be updated on every grant, not only when both masters ask, or a master that won alone several times wins again the moment the other joins. And the grant must be held until the last beat of the granted burst (WLAST or RLAST), because AXI data cannot interleave bursts and the 17-cycle burst model forbids it. The module and testbench below do both, and the testbench fails if either rule is broken.

    nano_rr_arb2.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
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    // NanoSoC teaching values: 2-master round-robin with a burst lock.
    // The grant is held until the last beat of the granted burst, so bursts never
    // interleave. The idle cycle of the link model is added by the fabric, not here.
    module nano_rr_arb2 (
      input  logic       clk,
      input  logic       resetn,
      input  logic [1:0] req,
      input  logic       burst_done, // last beat (WLAST or RLAST) of the granted burst accepted
      output logic [1:0] grant
    );
      logic       locked; // a burst is in progress: hold the grant
      logic [1:0] owner;  // who holds it
      logic       last;   // 0: M0 was granted last, 1: M1 was granted last
    
      always_comb begin
        grant = 2'b00;
        if (locked) grant = owner;
        else case (req)
          2'b01: grant = 2'b01;
          2'b10: grant = 2'b10;
          2'b11: grant = last ? 2'b01 : 2'b10;
          default: grant = 2'b00;
        endcase
      end
    
      always_ff @(posedge clk or negedge resetn) begin
        if (!resetn) begin
          locked <= 1'b0;
          owner <= 2'b00;
          last <= 1'b0;
        end else if (!locked) begin
          if (grant != 2'b00) begin
            locked <= 1'b1;
            owner <= grant;
            last <= grant[1]; // updated on EVERY grant, not only when both ask
          end
        end else if (burst_done) begin
          locked <= 1'b0;
        end
      end
    endmodule
    
    // ---- self-checking testbench: strict alternation, no interleaving ----
    module tb;
      logic clk = 0, resetn = 0;
      always #5 clk = ~clk;
      logic [1:0] req = 2'b00;
      wire [1:0] grant;
      logic [1:0] beat = 2'd0; // beat counter of the granted burst (4-beat bursts)
      wire burst_done = (grant != 2'b00) && (beat == 2'd3);
      nano_rr_arb2 dut (.clk(clk), .resetn(resetn), .req(req), .burst_done(burst_done), .grant(grant));
    
      always @(posedge clk) begin
        if (!resetn) beat <= 2'd0;
        else if (grant != 2'b00) beat <= beat + 2'd1;
      end
    
      int errors = 0, n_m0 = 0, n_m1 = 0;
      logic [1:0] owner = 2'b00, prev = 2'b00;
      bit both_phase = 0, first = 1;
      always @(posedge clk) if (resetn && grant != 2'b00) begin
        if (beat == 2'd0) begin // a new burst starts
          owner = grant;
          if (both_phase) begin
            if (first && grant != 2'b01) begin errors++; $display("FAIL: M0 must win after M1 went last"); end
            if (!first && grant == prev) begin errors++; $display("FAIL: master %b granted twice in a row", grant); end
            first = 0;
            if (grant == 2'b01) n_m0++; else n_m1++;
          end
          prev = grant;
        end else if (grant != owner) begin errors++; $display("FAIL: grant changed inside a burst"); end
      end
    
      initial begin
        repeat (3) @(negedge clk); resetn = 1;
        req = 2'b10; repeat (20) @(negedge clk);     // M1 alone for several bursts
        both_phase = 1; req = 2'b11; repeat (80) @(negedge clk); // then both ask continuously
        req = 2'b00; repeat (6) @(negedge clk);
        if (n_m0 < 8 || n_m1 < 8 || n_m0 > n_m1 + 1 || n_m1 > n_m0 + 1) begin errors++; $display("FAIL: unfair share m0=%0d m1=%0d", n_m0, n_m1); end
        if (errors == 0) $display("PASS: strict alternation while both ask, no interleaving inside a burst (m0=%0d m1=%0d)", n_m0, n_m1);
        $finish;
      end
      initial #50000 begin $display("FAIL timeout"); $finish; end
    endmodule
    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # NanoSoC teaching values: the burst bubble and the alternating CPU and SBR pair.
    F = 96e6 # clk_sys
    eff = {1: 1 / 2, 4: 4 / 5, 8: 8 / 9, 16: 16 / 17}
    want = {1: 192.0, 4: 307.2, 8: 341.3, 16: 361.4}
    for L, e in eff.items():
        assert abs(384.0 * e - want[L]) < 0.1
    # CPU 4-beat fills alternate with SBR 16-beat bursts on one bank: each burst pays one bubble
    period_cycles = (4 + 1) + (16 + 1)
    period_s = period_cycles / F
    cpu_mbs = 16 / period_s / 1e6
    sbr_mbs = 64 / period_s / 1e6
    total_mbs = (16 + 64) / period_s / 1e6
    assert abs(cpu_mbs - 69.8) < 0.1
    assert abs(sbr_mbs - 279.3) < 0.1
    assert abs(total_mbs - 349.1) < 0.1 # below the 361.4 of a pure 16-beat stream
    # the worst wait for the CPU is one SBR burst plus its bubble
    worst_wait_ns = (16 + 1) / F * 1e9
    assert abs(worst_wait_ns - 177.1) < 0.1
    print("PASS: burst-16 361.4, alternating pair 69.8 + 279.3 = 349.1 MB/s, CPU waits at most 177.1 ns")

    What the review looks for

    • Every shared slave quotes sustained, not peak, with the burst length and bubble the number assumes.

    • The arbitration scheme per slave is named, with its worst-case wait bound written down (NanoSoC: one burst of the other master).

    • Single-beat traffic has its own budget row at 50 percent efficiency, never the burst number.

    Verify and debug

    Alternate the two masters on one slave in simulation and measure each side against the 69.8 and 279.3 figures: a higher number means the bubble is missing from the model, a lower one means extra stalls to find. Then run each master alone and confirm both recover to their burst-efficiency rates; if they do not, the limit is in the slave, not the arbiter. For the arbiter itself, drive one master alone for several bursts and then both together: the first grant after the second master joins must go to it, and no burst may change owner mid-way.

    Budgeting with peak instead of sustained bandwidth: the spreadsheet multiplies 384 MB/s by time while the fabric delivers 361.4 at best and 349.1 under alternation. Caught by traffic-model simulation against the budget table; fixed by budgeting sustained rates per traffic pattern from the start.

    If asked what round-robin costs, answer efficiency first: L over L plus 1, then the NanoSoC pair, 69.8 plus 279.3 on a shared slave. Then volunteer the bound that justifies it: no master waits more than one burst of the other.

    Check yourself CPU line fills are 4 beats. Why does the link table still quote 361.4 MB/s sustained for SRAM?

    Because 361.4 assumes 16-beat bursts, which is the SBR streaming pattern the SRAM budget is built for. CPU fills run at 307.2 on the wire and 128 end to end; each pattern gets its own row.

    Related topics

    • Protocol First Principles: Arbitration Policies

    • Next: Starvation, Priority and Shaping

    • RTL: Bus Integration Arbitration

    Previous

    75 / 289

    Next: Starvation, Priority and Shaping

    Up next

    Up nextStarvation, Priority and Shaping

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • QoS in Practice on NanoSoC