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.
Parameters
PORTS: number of input ports each ofDATA_WIDTHbitsDATA_WIDTH: data width per input port in bits
Ports
i_clock: input clocki_aresetn: asynchronous active-low reseti_clear: synchronous cleari_data: input data of all ports concatenated into a single vector (size isPORTS*DATA_WIDTHbits)i_input_valid: valid signals from all input ports. Used with o_input_ready for input handshakingi_output_ready: ready signal from output interface. Used with o_output_valid for output handshakingo_data: output data (size isDATA_WIDTHbits)o_output_valid: valid signal from output interface. Used with i_output_ready for output handshakingo_input_ready: ready signals to all input ports. Used with i_input_valid for input handshakingo_accept: accept status signal that indicates whenever input data is accepted by the ring arbitero_transmit: transmit status signal that indicates whenever output data is transmitted by the ring arbiter
Source Code
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 */