aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/utils/ctrlport_combiner.v
blob: b5895147efce1872796885506488d94ffe184443 (plain)
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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: ctrlport_combiner
//
// Description:
//
// This block is an arbiter that merges control-port interfaces. This block is
// used when you have multiple control-port masters that need to access a
// single slave. For example, a NoC block with multiple submodules that each
// need to read and/or write registers outside of themselves.
//
// This module combines the control-port requests from multiple masters into a
// single request for one slave. Simultaneous requests are handled in the order
// specified by PRIORITY. The responding ACK is routed back to the requester.
//
// The module has been designed so that the latency through it is always the
// same when PRIORITY=1 and there is no contention, so that it can be used in
// applications where deterministic behavior is desired.
//
// Parameters:
//
//   NUM_MASTERS : The number of control-port masters to connect to a single
//                 control-port slave.
//   PRIORITY    : Use PRIORITY = 0 for round robin arbitration, PRIORITY = 1
//                 for priority arbitration (lowest number port serviced first).
//


module ctrlport_combiner #(
  parameter NUM_MASTERS = 2,
  parameter PRIORITY    = 0
) (
  input wire ctrlport_clk,
  input wire ctrlport_rst,

  // Requests from multiple masters
  input  wire [   NUM_MASTERS-1:0] s_ctrlport_req_wr,
  input  wire [   NUM_MASTERS-1:0] s_ctrlport_req_rd,
  input  wire [20*NUM_MASTERS-1:0] s_ctrlport_req_addr,
  input  wire [10*NUM_MASTERS-1:0] s_ctrlport_req_portid,
  input  wire [16*NUM_MASTERS-1:0] s_ctrlport_req_rem_epid,
  input  wire [10*NUM_MASTERS-1:0] s_ctrlport_req_rem_portid,
  input  wire [32*NUM_MASTERS-1:0] s_ctrlport_req_data,
  input  wire [ 4*NUM_MASTERS-1:0] s_ctrlport_req_byte_en,
  input  wire [   NUM_MASTERS-1:0] s_ctrlport_req_has_time,
  input  wire [64*NUM_MASTERS-1:0] s_ctrlport_req_time,
  // Responses to multiple masters
  output reg  [   NUM_MASTERS-1:0] s_ctrlport_resp_ack,
  output reg  [ 2*NUM_MASTERS-1:0] s_ctrlport_resp_status,
  output reg  [32*NUM_MASTERS-1:0] s_ctrlport_resp_data,

  // Request to a single slave
  output reg         m_ctrlport_req_wr,
  output reg         m_ctrlport_req_rd,
  output reg  [19:0] m_ctrlport_req_addr,
  output reg  [ 9:0] m_ctrlport_req_portid,
  output reg  [15:0] m_ctrlport_req_rem_epid,
  output reg  [ 9:0] m_ctrlport_req_rem_portid,
  output reg  [31:0] m_ctrlport_req_data,
  output reg  [ 3:0] m_ctrlport_req_byte_en,
  output reg         m_ctrlport_req_has_time,
  output reg  [63:0] m_ctrlport_req_time,
  // Response from a single slave
  input  wire        m_ctrlport_resp_ack,
  input  wire [ 1:0] m_ctrlport_resp_status,
  input  wire [31:0] m_ctrlport_resp_data
);

  reg [$clog2(NUM_MASTERS)-1:0] slave_sel = 0;  // Tracks which slave port is
                                                // currently being serviced.
  reg req_load_output = 1'b0;

  // Helper function to convert one hot vector to binary index
  // (LSB = index 0)
  function integer one_hot_to_binary(input [NUM_MASTERS-1:0] one_hot_vec);
    integer i, total;
  begin
    total = 0;
    for (i = 0; i <= NUM_MASTERS-1; i = i + 1) begin
      if (one_hot_vec[i]) begin
        total = total + i;
      end
    end
    one_hot_to_binary = total;
  end
  endfunction

  //---------------------------------------------------------------------------
  // Input Registers
  //---------------------------------------------------------------------------
  //
  // Latch each request until it can be serviced. Only one request per slave
  // can be in progress at a time.
  //
  //---------------------------------------------------------------------------

  reg [   NUM_MASTERS-1:0] req_valid = 0;
  reg [   NUM_MASTERS-1:0] req_wr;
  reg [   NUM_MASTERS-1:0] req_rd;
  reg [20*NUM_MASTERS-1:0] req_addr;
  reg [10*NUM_MASTERS-1:0] req_portid;
  reg [16*NUM_MASTERS-1:0] req_rem_epid;
  reg [10*NUM_MASTERS-1:0] req_rem_portid;
  reg [32*NUM_MASTERS-1:0] req_data;
  reg [ 4*NUM_MASTERS-1:0] req_byte_en;
  reg [   NUM_MASTERS-1:0] req_has_time;
  reg [64*NUM_MASTERS-1:0] req_time;

  always @(posedge ctrlport_clk) begin
    if (ctrlport_rst) begin
      req_valid <= 0;
    end else begin : input_reg_gen
      integer i;
      for (i = 0; i < NUM_MASTERS; i = i + 1) begin
        if (s_ctrlport_req_wr[i] | s_ctrlport_req_rd[i]) begin
          // Mark this slave's request valid and save the request information
          req_valid[i]             <= 1'b1;
          req_wr[i]                <= s_ctrlport_req_wr[i];
          req_rd[i]                <= s_ctrlport_req_rd[i];
          req_addr[20*i+:20]       <= s_ctrlport_req_addr[20*i+:20];
          req_portid[10*i+:10]     <= s_ctrlport_req_portid[10*i+:10];
          req_rem_epid[16*i+:16]   <= s_ctrlport_req_rem_epid[16*i+:16];
          req_rem_portid[10*i+:10] <= s_ctrlport_req_rem_portid[10*i+:10];
          req_data[32*i+:32]       <= s_ctrlport_req_data[32*i+:32];
          req_byte_en[4*i+:4]      <= s_ctrlport_req_byte_en[4*i+:4];
          req_has_time[i]          <= s_ctrlport_req_has_time[i];
          req_time[64*i+:64]       <= s_ctrlport_req_time[64*i+:64];
        end
      end

      // Clear the active request when it gets output
      if (req_load_output) begin
        req_valid[slave_sel] <= 1'b0;
      end
    end
  end


  //---------------------------------------------------------------------------
  // Arbitration State Machine
  //---------------------------------------------------------------------------
  //
  // This state machine tracks which slave port is being serviced and which to
  // service next. This is done using a counter that simply checks each port in
  // sequential order and then stops when it finds one that has a valid request.
  //
  //---------------------------------------------------------------------------

  reg  req_active = 0;  // Indicates if there's a request being serviced
  wire [NUM_MASTERS-1:0] next_slave_one_hot; // one hot for next active request
                                             // (used for PRIORITY = 1)

  generate
    genvar i;
    for (i = 0; i < NUM_MASTERS; i = i+1) begin : gen_next_slave_one_hot
      if (i == 0) begin
        assign next_slave_one_hot[i] = req_valid[i];
      end else begin
        assign next_slave_one_hot[i] = req_valid[i] & ~next_slave_one_hot[i-1];
      end
    end
  endgenerate

  always @(posedge ctrlport_clk) begin
    if (ctrlport_rst) begin
      slave_sel       <= 0;
      req_active      <= 1'b0;
      req_load_output <= 1'b0;
    end else begin
      req_load_output <= 1'b0;

      if (req_active) begin
        // Wait until we get the response before we allow another request
        if (m_ctrlport_resp_ack) begin
          req_active <= 1'b0;

          // Go to next slave immediately
          if(PRIORITY == 1)
            slave_sel <= one_hot_to_binary(next_slave_one_hot);
          // Round robin - Go to the next slave so we don't service the same
          // slave again
          else if(slave_sel == NUM_MASTERS-1)
            slave_sel <= 0;
          else
            slave_sel <= slave_sel + 1;
        end
      end else begin
        // No active request in progress, so check if there's a new request on
        // the selected slave.
        if (req_valid[slave_sel]) begin
          req_active      <= 1'b1;
          req_load_output <= 1'b1;
        end else begin
          // Go to next slave immediately
          if(PRIORITY == 1)
            slave_sel <= one_hot_to_binary(next_slave_one_hot);
          // Round robin - Nothing from this slave, so move to the next slave.
          else if (slave_sel == NUM_MASTERS-1)
            slave_sel <= 0;
          else
            slave_sel <= slave_sel + 1;
        end
      end
    end
  end


  //---------------------------------------------------------------------------
  // Output Register
  //---------------------------------------------------------------------------
  //
  // Here we load the active request for a single clock cycle and demultiplex
  // the response back to the requesting master.
  //
  //---------------------------------------------------------------------------

  always @(posedge ctrlport_clk) begin
    if (ctrlport_rst) begin
      m_ctrlport_req_wr <= 1'b0;
      m_ctrlport_req_rd <= 1'b0;
    end else begin : output_reg_gen
      integer i;

      // Load the active request
      if (req_load_output) begin
        m_ctrlport_req_wr         <= req_wr        [slave_sel];
        m_ctrlport_req_rd         <= req_rd        [slave_sel];
        m_ctrlport_req_addr       <= req_addr      [20*slave_sel +: 20];
        m_ctrlport_req_portid     <= req_portid    [10*slave_sel +: 10];
        m_ctrlport_req_rem_epid   <= req_rem_epid  [16*slave_sel +: 16];
        m_ctrlport_req_rem_portid <= req_rem_portid[10*slave_sel +: 10];
        m_ctrlport_req_data       <= req_data      [32*slave_sel +: 32];
        m_ctrlport_req_byte_en    <= req_byte_en   [ 4*slave_sel +: 4];
        m_ctrlport_req_has_time   <= req_has_time  [slave_sel];
        m_ctrlport_req_time       <= req_time      [64*slave_sel +: 64];
      end else begin
        m_ctrlport_req_wr <= 1'b0;
        m_ctrlport_req_rd <= 1'b0;
      end

      // Output any response to the master that made the request
      for (i = 0; i < NUM_MASTERS; i = i + 1) begin
        // Give the response data to all the slaves (no demux, to save logic)
        s_ctrlport_resp_status[2*i +: 2] <= m_ctrlport_resp_status;
        s_ctrlport_resp_data[32*i +: 32] <= m_ctrlport_resp_data;

        // Give the ack only to the master that made the request (use a demux)
        if (i == slave_sel && m_ctrlport_resp_ack) begin
          s_ctrlport_resp_ack[i] <= 1'b1;
        end else begin
          s_ctrlport_resp_ack[i] <= 1'b0;
        end
      end
    end
  end

endmodule