Ring Arbiter

A ring arbiter is a many-to-one flow control block that accepts transactions from 2 or more input ports and provides that data to an output interface. If more than 1 input port is ready to send data, the ring arbiter will arbitrate between all active input ports by accepting 1 transaction from each input port, in port order, so that no input port is allowed to transmit two transactions in a row as long as there is at least one other input port that is waiting to send a transaction.

This implementation is pipelined from input to output so that back-to-back transfers are achieved, thus allowing for sustained maximum data throughput to the output interface. As shown in the figure below, the core of the control path design of the ring arbiter consists of two Rotate blocks, a One-hot Priority Encoder, and a rotate controller. The pre-Rotate + One-hot Priority Encoder + post-Rotate combination determines which input port will be serviced next while the rotate controller calculates the rotation amounts such that the last accepted input port is the lowest priority input port until a new transaction is accepted from a different input port, thus changing the last accepted input port. Therefore, the prev_accepted register is only written when the Skid Buffer indicates that it has accepted a new transaction from an input port.

The datapath for the ring arbiter consists of a One-hot Mux and a Skid Buffer. The One-hot Mux selects the data from the input port that has been decided by the control path so that the Skid Buffer sees the correct input port’s data at its input. On the output, the Skid Buffer’s output is connected directly to the output interface. The Skid Buffer is used here because it provides the back-to-back transfers needed on a pipelined interface in a fairly lightweight manner.

Note that the i_clock, i_aresetn, and i_clear signals are intentionally omitted from the block diagram below to make it easier to read and understand the ring arbiter design.

Ring Arbiter Block Diagram

Parameters

  • PORTS : number of input ports each of DATA_WIDTH bits

  • DATA_WIDTH : data width per input port in bits

Ports

  • i_clock : input clock

  • i_aresetn : asynchronous active-low reset

  • i_clear : synchronous clear

  • i_data : input data of all ports concatenated into a single vector (size is PORTS*DATA_WIDTH bits)

  • i_input_valid : valid signals from all input ports. Used with o_input_ready for input handshaking

  • i_output_ready : ready signal from output interface. Used with o_output_valid for output handshaking

  • o_data : output data (size is DATA_WIDTH bits)

  • o_output_valid : valid signal from output interface. Used with i_output_ready for output handshaking

  • o_input_ready : ready signals to all input ports. Used with i_input_valid for input handshaking

  • o_accept : accept status signal that indicates whenever input data is accepted by the ring arbiter

  • o_transmit : transmit status signal that indicates whenever output data is transmitted by the ring arbiter

Source Code

ring_arbiter.sv
  1`ifndef LIBSV_ARBITERS_RING_ARBITER
  2`define LIBSV_ARBITERS_RING_ARBITER
  3
  4`include "libsv/bit_ops/rotate.sv"
  5`include "libsv/coders/onehot_priority_encoder.sv"
  6`include "libsv/muxes/onehot_mux.sv"
  7`include "libsv/fifos/skid_buffer.sv"
  8
  9module ring_arbiter #(
 10    parameter int PORTS  /* verilator public_flat_rd */      = 4,
 11    parameter int DATA_WIDTH  /* verilator public_flat_rd */ = 8
 12) (
 13    input  logic                        i_clock,
 14    input  logic                        i_aresetn,
 15    input  logic                        i_clear,
 16    // This is a bummer. Would much rather do:
 17    //     input logic [PORTS-1:0][DATA_WIDTH-1:0] i_data
 18    // and have a 2D packed array but verilator doesn't
 19    // support 2D packed arrays using VPI which is what
 20    // cocotb uses. See https://github.com/verilator/verilator/issues/2812.
 21    input  logic [PORTS*DATA_WIDTH-1:0] i_data,
 22    input  logic [           PORTS-1:0] i_input_valid,
 23    input  logic                        i_output_ready,
 24    output logic [      DATA_WIDTH-1:0] o_data,
 25    output logic                        o_output_valid,
 26    output logic [           PORTS-1:0] o_input_ready,
 27    output logic                        o_accept,
 28    output logic                        o_transmit
 29);
 30
 31    logic [$clog2(PORTS)-1:0] pre_rotate_amt;
 32    logic [        PORTS-1:0] pre_rotate_out;
 33    logic [        PORTS-1:0] ohpe_out;
 34    logic                     ohpe_valid;
 35    logic [$clog2(PORTS)-1:0] post_rotate_amt;
 36    logic [        PORTS-1:0] post_rotate_out;
 37    logic [        PORTS-1:0] prev_accepted;
 38    logic accept, transmit;
 39    logic                  is_any_input_valid;
 40    logic                  input_ready;
 41    logic [DATA_WIDTH-1:0] ohm_out;
 42
 43    // CONTROL PATH -------------------------------
 44
 45    // pre-rotate
 46    rotate #(PORTS) pre_rotate (
 47        .i_data(i_input_valid),
 48        .i_amt (pre_rotate_amt),
 49        .o_data(pre_rotate_out)
 50    );
 51
 52    // onehot priority encoder selects the current highest priority input port
 53    onehot_priority_encoder #(PORTS) ohpe (
 54        .i_data(pre_rotate_out),
 55        .o_data(ohpe_out)
 56    );
 57
 58    // post-rotate (undo the pre-rotate)
 59    rotate #(PORTS) post_rotate (
 60        .i_data(ohpe_out),
 61        .i_amt (post_rotate_amt),
 62        .o_data(post_rotate_out)
 63    );
 64
 65    always_ff @(posedge i_clock, negedge i_aresetn) begin : prev_accepted_logic
 66        if (!i_aresetn || i_clear) begin
 67            prev_accepted <= '0;
 68        end else begin
 69            if (accept) prev_accepted <= post_rotate_out;
 70            else prev_accepted <= prev_accepted;
 71        end
 72    end : prev_accepted_logic
 73
 74    always_comb begin : rotate_controller
 75        pre_rotate_amt  = '0;
 76        post_rotate_amt = '0;
 77        for (int i = 0; i < $bits(prev_accepted); ++i) begin
 78            if (prev_accepted[i]) begin
 79                pre_rotate_amt  = $bits(pre_rotate_amt)'(PORTS - i - 1);
 80                post_rotate_amt = $bits(post_rotate_amt)'(i + 1);
 81            end
 82        end
 83    end : rotate_controller
 84
 85    assign is_any_input_valid = |i_input_valid;
 86    assign o_input_ready      = {PORTS{input_ready}} & post_rotate_out;
 87    assign o_accept           = accept;
 88    assign o_transmit         = transmit;
 89
 90    // END OF CONTROL PATH ------------------------
 91
 92    // DATA PATH ----------------------------------
 93
 94    onehot_mux #(
 95        .PORTS     (PORTS),
 96        .DATA_WIDTH(DATA_WIDTH)
 97    ) ohm (
 98        .i_data  (i_data),
 99        .i_select(post_rotate_out),
100        .o_data  (ohm_out)
101    );
102
103    skid_buffer #(DATA_WIDTH) sb (
104        .i_clock       (i_clock),
105        .i_aresetn     (i_aresetn),
106        .i_clear       (i_clear),
107        .i_data        (ohm_out),
108        .i_input_valid (is_any_input_valid),
109        .i_output_ready(i_output_ready),
110        .o_data        (o_data),
111        .o_output_valid(o_output_valid),
112        .o_input_ready (input_ready),
113        .o_accept      (accept),
114        .o_transmit    (transmit)
115    );
116
117    // END OF DATA PATH ---------------------------
118
119endmodule : ring_arbiter
120
121`endif  /* LIBSV_ARBITERS_RING_ARBITER */