// // 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