rfnoc: lib: introduce ctrl-port interface and BFM
Original-commit: 886cf97ad5d494b20c6fff0a53113f6096d458c7
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2018 Ettus Research, A National Instruments Company
|
||||
# Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
@@ -15,8 +15,9 @@ ctrlport.vh \
|
||||
))
|
||||
|
||||
RFNOC_CORE_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/rfnoc/core/, \
|
||||
rfnoc_chdr_utils_pkg.sv \
|
||||
ctrlport_pkg.sv \
|
||||
ctrlport_if.sv \
|
||||
rfnoc_chdr_utils_pkg.sv \
|
||||
axis_ctrl_endpoint.v \
|
||||
axis_ctrl_master.v \
|
||||
axis_ctrl_slave.v \
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// Copyright 2024 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Interface: ctrlport_if
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Defines a SystemVerilog interface for the control-port bus.
|
||||
//
|
||||
|
||||
interface ctrlport_if (
|
||||
input logic clk,
|
||||
input logic rst = 1'b0
|
||||
);
|
||||
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
// Consisting of a request and a response moving in different directions.
|
||||
ctrlport_request_t req;
|
||||
ctrlport_response_t resp;
|
||||
|
||||
// Master driving the request and consuming the response.
|
||||
modport master (
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
output req,
|
||||
input resp
|
||||
);
|
||||
|
||||
// Slave consuming the request and driving the response.
|
||||
modport slave (
|
||||
input clk,
|
||||
input rst,
|
||||
|
||||
input req,
|
||||
output resp
|
||||
);
|
||||
|
||||
endinterface : ctrlport_if
|
||||
@@ -14,4 +14,34 @@ package ctrlport_pkg;
|
||||
|
||||
`include "ctrlport.vh"
|
||||
|
||||
// typedef for status
|
||||
typedef enum logic [1:0] {
|
||||
STS_OKAY = CTRL_STS_OKAY,
|
||||
STS_CMDERR = CTRL_STS_CMDERR,
|
||||
STS_TSERR = CTRL_STS_TSERR,
|
||||
STS_WARNING = CTRL_STS_WARNING } ctrlport_status_t;
|
||||
|
||||
// Defining signals as defined in Table 18 of RFNoC Specification v.1.0.1.
|
||||
// request related signals
|
||||
typedef struct packed {
|
||||
logic wr;
|
||||
logic rd;
|
||||
logic [ CTRLPORT_ADDR_W-1:0] addr;
|
||||
logic [ CTRLPORT_PORTID_W-1:0] port_id;
|
||||
logic [CTRLPORT_REM_EPID_W-1:0] remote_epid;
|
||||
logic [ CTRLPORT_PORTID_W-1:0] remote_portid;
|
||||
logic [ CTRLPORT_DATA_W-1:0] data;
|
||||
logic [ CTRLPORT_BYTE_EN_W-1:0] byte_en;
|
||||
logic has_time;
|
||||
// signal time - renamed to timestamp to avoid conflict with SystemVerilog keyword time
|
||||
logic [ CTRLPORT_TIME_W-1:0] timestamp;
|
||||
} ctrlport_request_t;
|
||||
|
||||
// response related signals
|
||||
typedef struct packed {
|
||||
logic ack;
|
||||
ctrlport_status_t status;
|
||||
logic [CTRLPORT_DATA_W-1:0] data;
|
||||
} ctrlport_response_t;
|
||||
|
||||
endpackage : ctrlport_pkg
|
||||
|
||||
@@ -31,4 +31,10 @@ noc_shell_generic_ctrlport_pyld_chdr.v \
|
||||
timekeeper.v \
|
||||
ctrlport_terminator.v \
|
||||
chdr_strip_header.sv \
|
||||
ctrlport_if_clk_cross.sv \
|
||||
ctrlport_if_combiner.sv \
|
||||
ctrlport_if_decoder.sv \
|
||||
ctrlport_if_splitter.sv \
|
||||
ctrlport_if_terminator.sv \
|
||||
ctrlport_if_window.sv \
|
||||
))
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_clk_cross
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Crosses control-port interface between different clock domains.
|
||||
//
|
||||
|
||||
|
||||
module ctrlport_if_clk_cross
|
||||
(
|
||||
ctrlport_if.slave s_ctrlport,
|
||||
ctrlport_if.master m_ctrlport
|
||||
|
||||
);
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Slave to Master Clock Crossing (Request)
|
||||
//---------------------------------------------------------------------------
|
||||
ctrlport_request_t m_req_hs;
|
||||
logic m_req_hs_valid;
|
||||
|
||||
// Busy flag can be ignored as the response handshake takes at least the same
|
||||
// amount of cycles to transfer the response as this handshake instance needs
|
||||
// to release the busy flag as they are configured with the same amount of
|
||||
// synchronization stages. Furthermore the ctrlport protocol just allows for
|
||||
// one transaction to be active at the same time. A request can only be issued
|
||||
// once the response is provided.
|
||||
handshake #(
|
||||
.WIDTH($bits(ctrlport_request_t))
|
||||
) req_handshake_inst (
|
||||
.clk_a(s_ctrlport.clk),
|
||||
.rst_a(s_ctrlport.rst),
|
||||
.valid_a((s_ctrlport.req.rd | s_ctrlport.req.wr) & ~s_ctrlport.rst),
|
||||
.data_a(s_ctrlport.req),
|
||||
.busy_a(),
|
||||
.clk_b(m_ctrlport.clk),
|
||||
.valid_b(m_req_hs_valid),
|
||||
.data_b(m_req_hs)
|
||||
);
|
||||
|
||||
always_comb begin
|
||||
m_ctrlport.req = m_req_hs;
|
||||
// mask read and write flags
|
||||
m_ctrlport.req.wr = m_req_hs.wr & m_req_hs_valid & ~m_ctrlport.rst;
|
||||
m_ctrlport.req.rd = m_req_hs.rd & m_req_hs_valid & ~m_ctrlport.rst;
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Master to Slave Clock Crossing (Response)
|
||||
//---------------------------------------------------------------------------
|
||||
ctrlport_response_t s_resp_hs;
|
||||
logic s_resp_hs_valid;
|
||||
|
||||
// Busy flag can be ignored as the request handshake takes at least the same
|
||||
// amount of cycles to transfer the request as this handshake instance needs
|
||||
// to release the busy flag as they are configured with the same amount of
|
||||
// synchronization stages. Furthermore the ctrlport protocol just allows for
|
||||
// one transaction to be active at the same time. A response can only be
|
||||
// issued once the request is available.
|
||||
handshake #(
|
||||
.WIDTH($bits(ctrlport_response_t))
|
||||
) resp_handshake_inst (
|
||||
.clk_a(m_ctrlport.clk),
|
||||
.rst_a(m_ctrlport.rst),
|
||||
.valid_a(m_ctrlport.resp.ack & ~m_ctrlport.rst),
|
||||
.data_a(m_ctrlport.resp),
|
||||
.busy_a(),
|
||||
.clk_b(s_ctrlport.clk),
|
||||
.valid_b(s_resp_hs_valid),
|
||||
.data_b(s_resp_hs)
|
||||
);
|
||||
|
||||
always_comb begin
|
||||
s_ctrlport.resp = s_resp_hs;
|
||||
s_ctrlport.resp.ack = s_resp_hs.ack & s_resp_hs_valid & ~s_ctrlport.rst;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_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).
|
||||
// TIMEOUT : Number of cycles to wait for a response before aborting the wait for an ack.
|
||||
// TIMEOUT = 0 will not add a timeout.
|
||||
//
|
||||
|
||||
|
||||
module ctrlport_if_combiner #(
|
||||
int NUM_MASTERS = 2,
|
||||
bit PRIORITY = 0,
|
||||
int TIMEOUT = 0
|
||||
) (
|
||||
// Slave Interfaces
|
||||
ctrlport_if.slave s_ctrlport [NUM_MASTERS-1:0],
|
||||
// Master Interface
|
||||
ctrlport_if.master m_ctrlport
|
||||
);
|
||||
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
logic [$clog2(NUM_MASTERS)-1:0] slave_sel = '0; // Tracks which slave port is
|
||||
// currently being serviced.
|
||||
logic req_load_output = '0;
|
||||
logic timeout_occurred = '0;
|
||||
|
||||
// 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; i++) 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.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
ctrlport_request_t req_buffer [NUM_MASTERS-1:0];
|
||||
logic [NUM_MASTERS-1:0] req_valid;
|
||||
|
||||
for (genvar i = 0; i < NUM_MASTERS; i++) begin : gen_input_regs
|
||||
always_ff @(posedge m_ctrlport.clk) begin
|
||||
if (m_ctrlport.rst) begin
|
||||
req_valid[i] <= '0;
|
||||
end else begin
|
||||
if (s_ctrlport[i].req.wr | s_ctrlport[i].req.rd) begin
|
||||
// Mark this slave's request valid and save the request information
|
||||
req_valid[i] <= '1;
|
||||
end
|
||||
|
||||
// Clear the active request when it gets output
|
||||
if (req_load_output && (i == slave_sel)) begin
|
||||
req_valid[i] <= '0;
|
||||
end
|
||||
end
|
||||
|
||||
// Save buffer without reset
|
||||
if (s_ctrlport[i].req.wr | s_ctrlport[i].req.rd) begin
|
||||
req_buffer[i] <= s_ctrlport[i].req;
|
||||
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.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic req_active = '0; // Indicates if there's a request being serviced
|
||||
logic [NUM_MASTERS-1:0] next_slave_one_hot; // one hot for next active request
|
||||
// (used for PRIORITY = 1)
|
||||
|
||||
for (genvar i = 0; i < NUM_MASTERS; i++) 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
|
||||
|
||||
always_ff @(posedge m_ctrlport.clk) begin
|
||||
if (m_ctrlport.rst) begin
|
||||
slave_sel <= '0;
|
||||
req_active <= '0;
|
||||
req_load_output <= '0;
|
||||
end else begin
|
||||
req_load_output <= '0;
|
||||
|
||||
if (req_active) begin
|
||||
// Wait until we get the response before we allow another request
|
||||
if (m_ctrlport.resp.ack || timeout_occurred) begin
|
||||
req_active <= '0;
|
||||
|
||||
// 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;
|
||||
req_load_output <= '1;
|
||||
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_ff @(posedge m_ctrlport.clk) begin
|
||||
// Load the active request
|
||||
if (req_load_output) begin
|
||||
m_ctrlport.req <= req_buffer[slave_sel];
|
||||
end else begin
|
||||
m_ctrlport.req.wr <= '0;
|
||||
m_ctrlport.req.rd <= '0;
|
||||
end
|
||||
|
||||
if (m_ctrlport.rst) begin
|
||||
// only reset the flags of the master interface
|
||||
m_ctrlport.req.wr <= '0;
|
||||
m_ctrlport.req.rd <= '0;
|
||||
end
|
||||
end
|
||||
|
||||
// Output any response to the master that made the request
|
||||
for (genvar i = 0; i < NUM_MASTERS; i++) begin : gen_output_regs
|
||||
always_ff @(posedge m_ctrlport.clk) begin
|
||||
// Give the response data to all the slaves (no demux, to save logic)
|
||||
s_ctrlport[i].resp <= m_ctrlport.resp;
|
||||
|
||||
// Give the ack only to the master that made the request (use a demux)
|
||||
if (m_ctrlport.rst) begin
|
||||
s_ctrlport[i].resp.ack <= '0;
|
||||
end else if (i == slave_sel && m_ctrlport.resp.ack) begin
|
||||
s_ctrlport[i].resp.ack <= '1;
|
||||
end else begin
|
||||
s_ctrlport[i].resp.ack <= '0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// optional watchdog
|
||||
//---------------------------------------------------------------------------
|
||||
if (TIMEOUT == 0) begin: gen_no_timeout
|
||||
// never set a timeout but in always_ff block to avoid error with synchronous assignment
|
||||
// in gen_timeout block below
|
||||
always_ff @(posedge m_ctrlport.clk) begin
|
||||
timeout_occurred = '0;
|
||||
end
|
||||
|
||||
end else begin: gen_timeout
|
||||
// When the timeout occurred it takes 2 more clock cycles for the counter to reach zero
|
||||
// The path is: timeout_occurred -> req_active = 0 -> timeout_counter = 0
|
||||
// As the timeout counter has a minimum width of 2 bits the counter cannot reach TIMEOUT+1
|
||||
// again within these two clock cycles.
|
||||
// Therefore no other reset other than req_active is needed on this signal.
|
||||
logic [$clog2(TIMEOUT+2):0] timeout_counter = '0;
|
||||
|
||||
// Reset the timeout counter when there is no active request
|
||||
always_ff @(posedge m_ctrlport.clk) begin
|
||||
if (~req_active) begin
|
||||
timeout_counter <= '0;
|
||||
end else begin
|
||||
timeout_counter <= timeout_counter + 1;
|
||||
end
|
||||
|
||||
// Set the timeout flag for one cycle when the counter reaches the timeout value.
|
||||
// The delay of the additional register compensates the delay of the logic above to
|
||||
// load the active request into the output register.
|
||||
timeout_occurred <= timeout_counter == TIMEOUT;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,107 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_decoder
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// This block splits a single control port interface into multiple. It is
|
||||
// used when you have a single master that needs to access multiple slaves.
|
||||
//
|
||||
// This version also implements address decoding. The request is passed to a
|
||||
// slave only if the address falls within that slave's address space. Each
|
||||
// slave can have a unique base address and address space size.
|
||||
//
|
||||
// When passed to the slave, the base address is subtracted from the request
|
||||
// address.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// NUM_SLAVES : The number of slaves to connect to a master.
|
||||
// PORT_BASE : Base addresses to use for each slave.
|
||||
// PORT_SIZE : Size of the address space for each slave.
|
||||
//
|
||||
|
||||
module ctrlport_if_decoder #(
|
||||
int NUM_SLAVES = 2,
|
||||
int PORT_BASE [NUM_SLAVES] = '{'h0, 'h100},
|
||||
int PORT_SIZE [NUM_SLAVES] = '{'h100, 'h100}
|
||||
) (
|
||||
// Slave Interface
|
||||
ctrlport_if.slave s_ctrlport,
|
||||
// Master Interfaces
|
||||
ctrlport_if.master m_ctrlport [NUM_SLAVES]
|
||||
);
|
||||
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Check the address ranges
|
||||
//---------------------------------------------------------------------------
|
||||
for (genvar i = 0; i < NUM_SLAVES; i = i+1) begin : gen_overlap_1
|
||||
for (genvar j = 0; j < NUM_SLAVES; j = j+1) begin : gen_overlap_2
|
||||
if (i != j) begin
|
||||
if ((PORT_BASE[i] >= PORT_BASE[j]) &&
|
||||
(PORT_BASE[i] < PORT_BASE[j] + PORT_SIZE[j])) begin
|
||||
$error("Port %0d overlaps with port %0d.", i, j);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Split the requests among the slaves
|
||||
//---------------------------------------------------------------------------
|
||||
for (genvar i = 0; i < NUM_SLAVES; i = i+1) begin : gen_split
|
||||
always_ff @(posedge s_ctrlport.clk) begin
|
||||
// unconditionally pass the request by default
|
||||
m_ctrlport[i].req <= s_ctrlport.req;
|
||||
// pass only the respective address bits
|
||||
m_ctrlport[i].req.addr <= '0;
|
||||
m_ctrlport[i].req.addr[$clog2(PORT_SIZE[i])-1:0] <= s_ctrlport.req.addr - PORT_BASE[i];
|
||||
|
||||
// read and write trigger transactions and therefore need to react to reset
|
||||
if (s_ctrlport.rst) begin
|
||||
m_ctrlport[i].req.wr <= 1'b0;
|
||||
m_ctrlport[i].req.rd <= 1'b0;
|
||||
end else begin
|
||||
automatic logic address_in_range;
|
||||
address_in_range = (s_ctrlport.req.addr >= PORT_BASE[i]) &&
|
||||
(s_ctrlport.req.addr < PORT_BASE[i] + PORT_SIZE[i]);
|
||||
m_ctrlport[i].req.wr <= s_ctrlport.req.wr & address_in_range;
|
||||
m_ctrlport[i].req.rd <= s_ctrlport.req.rd & address_in_range;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Decode the responses
|
||||
//---------------------------------------------------------------------------
|
||||
// Take the responses and mask them with their respective ack
|
||||
ctrlport_response_t masked_resp [NUM_SLAVES-1:0];
|
||||
for (genvar i = 0; i < NUM_SLAVES; i++) begin : gen_mask
|
||||
assign masked_resp[i] = m_ctrlport[i].resp.ack ? m_ctrlport[i].resp : '0;
|
||||
end
|
||||
|
||||
// Combine the masked responses by OR'ing them together
|
||||
ctrlport_response_t combined_resp;
|
||||
always_comb begin : response_combine
|
||||
combined_resp = '0;
|
||||
for (int i = 0; i < NUM_SLAVES; i++) begin : gen_or
|
||||
combined_resp = combined_resp | masked_resp[i];
|
||||
end
|
||||
end
|
||||
|
||||
// Register the output to break combinatorial path
|
||||
always_ff @(posedge s_ctrlport.clk) begin : response_reg
|
||||
s_ctrlport.resp <= combined_resp;
|
||||
|
||||
if (s_ctrlport.rst) begin
|
||||
s_ctrlport.resp.ack <= '0;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_splitter
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// This block splits a single control-port interface into multiple. It is used
|
||||
// when you have a single master that needs to access multiple slaves. For
|
||||
// example, a NoC block where the registers are implemented in multiple
|
||||
// submodules that must be read/written by a single NoC shell.
|
||||
//
|
||||
// Note that this block does not do any address decoding, so the connected
|
||||
// slaves must use non-overlapping address spaces.
|
||||
//
|
||||
// This module takes the request received by its single slave interface and
|
||||
// outputs it on all its master interfaces. In the opposite direction, it takes
|
||||
// the responses received by its multiple master interfaces and combines them
|
||||
// into a single response on its slave interface. This is done by using the ack
|
||||
// bit of each response to mask the other bits of the response, then OR'ing all
|
||||
// of the masked responses together onto a single response bus. This is valid
|
||||
// because only one block is allowed to respond to a single request.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// NUM_SLAVES : The number of slaves you want to connect to a master.
|
||||
//
|
||||
|
||||
|
||||
module ctrlport_if_splitter #(
|
||||
int NUM_SLAVES = 2
|
||||
) (
|
||||
// Slave Interface
|
||||
ctrlport_if.slave s_ctrlport,
|
||||
// Master Interfaces
|
||||
ctrlport_if.master m_ctrlport [NUM_SLAVES-1:0]
|
||||
);
|
||||
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
generate
|
||||
if (NUM_SLAVES == 1) begin : gen_no_split
|
||||
assign m_ctrlport[0].req = s_ctrlport.req;
|
||||
assign s_ctrlport.resp = m_ctrlport[0].resp;
|
||||
end else begin : gen_splitter
|
||||
//---------------------------------------------------------------------------
|
||||
// Split the requests among the slaves
|
||||
//---------------------------------------------------------------------------
|
||||
for (genvar i = 0; i < NUM_SLAVES; i++) begin : gen_split
|
||||
// No special logic is required to split the requests from the master among
|
||||
// multiple slaves.
|
||||
assign m_ctrlport[i].req = s_ctrlport.req;
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Decode the responses
|
||||
//---------------------------------------------------------------------------
|
||||
// Take the responses and mask them with their respective ack
|
||||
ctrlport_response_t masked_resp [NUM_SLAVES-1:0];
|
||||
for (genvar i = 0; i < NUM_SLAVES; i++) begin : gen_mask
|
||||
assign masked_resp[i] = m_ctrlport[i].resp.ack ? m_ctrlport[i].resp : '0;
|
||||
end
|
||||
|
||||
// Combine the masked responses by OR'ing them together
|
||||
ctrlport_response_t combined_resp;
|
||||
always_comb begin : response_combine
|
||||
combined_resp = '0;
|
||||
for (int i = 0; i < NUM_SLAVES; i++) begin : gen_or
|
||||
combined_resp = combined_resp | masked_resp[i];
|
||||
end
|
||||
end
|
||||
|
||||
// Register the output to break combinatorial path
|
||||
always_ff @(posedge s_ctrlport.clk) begin : response_reg
|
||||
s_ctrlport.resp <= combined_resp;
|
||||
|
||||
if (s_ctrlport.rst) begin
|
||||
s_ctrlport.resp.ack <= '0;
|
||||
end
|
||||
end
|
||||
end
|
||||
endgenerate
|
||||
endmodule
|
||||
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_terminator
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Returns an error for all ctrlport requests in given address range.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// BASE_ADDRESS : First address to generate response for.
|
||||
// WINDOW_SIZE : Size of the address space.
|
||||
// Last address in range will be BASE_ADDRESS + WINDOW_SIZE - 1.
|
||||
//
|
||||
|
||||
module ctrlport_if_terminator #(
|
||||
int BASE_ADDRESS = 0,
|
||||
int WINDOW_SIZE = 32
|
||||
)(
|
||||
ctrlport_if.slave s_ctrlport
|
||||
);
|
||||
|
||||
import ctrlport_pkg::*;
|
||||
|
||||
// drive acknowledgement on requests but not on reset
|
||||
always_ff @(posedge s_ctrlport.clk) begin
|
||||
if (s_ctrlport.rst) begin
|
||||
s_ctrlport.resp.ack <= 1'b0;
|
||||
end else if ((s_ctrlport.req.addr >= BASE_ADDRESS) &&
|
||||
(s_ctrlport.req.addr < BASE_ADDRESS + WINDOW_SIZE)) begin
|
||||
s_ctrlport.resp.ack <= s_ctrlport.req.wr | s_ctrlport.req.rd;
|
||||
end else begin
|
||||
s_ctrlport.resp.ack <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// other outputs are fixed
|
||||
assign s_ctrlport.resp.status = STS_CMDERR;
|
||||
assign s_ctrlport.resp.data = '0;
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_if_window
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Copy requests from slave to master interface when s_ctrlport_req_addr is in
|
||||
// address range specified by the parameters.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// BASE_ADDRESS : Base address of the window.
|
||||
// This will be the first address in the range.
|
||||
// WINDOW_SIZE : Size of the address space for the window.
|
||||
// Last address in range will be BASE_ADDRESS + WINDOW_SIZE - 1.
|
||||
//
|
||||
|
||||
module ctrlport_if_window #(
|
||||
int BASE_ADDRESS = 0,
|
||||
int WINDOW_SIZE = 32
|
||||
) (
|
||||
ctrlport_if.slave s_ctrlport,
|
||||
ctrlport_if.master m_ctrlport
|
||||
);
|
||||
|
||||
always_comb begin
|
||||
// Mask write and read flag
|
||||
automatic logic address_in_range = (s_ctrlport.req.addr >= BASE_ADDRESS) &&
|
||||
(s_ctrlport.req.addr < BASE_ADDRESS + WINDOW_SIZE);
|
||||
|
||||
// initialize master request by slave request
|
||||
m_ctrlport.req = s_ctrlport.req;
|
||||
|
||||
// overwrite write and read flag with in range flag
|
||||
m_ctrlport.req.wr = s_ctrlport.req.wr && address_in_range;
|
||||
m_ctrlport.req.rd = s_ctrlport.req.rd && address_in_range;
|
||||
|
||||
// transfer reponse without change
|
||||
s_ctrlport.resp = m_ctrlport.resp;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user