From f961936a43c36342c587800d5f3ab06b6c1e7093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=B6hler?= Date: Mon, 10 Feb 2025 23:35:54 +0100 Subject: [PATCH] rfnoc: lib: introduce ctrl-port interface and BFM Original-commit: 886cf97ad5d494b20c6fff0a53113f6096d458c7 --- lib/rfnoc/core/Makefile.srcs | 5 +- lib/rfnoc/core/ctrlport_if.sv | 42 ++++ lib/rfnoc/core/ctrlport_pkg.sv | 30 +++ lib/rfnoc/utils/Makefile.srcs | 6 + lib/rfnoc/utils/ctrlport_if_clk_cross.sv | 84 ++++++++ lib/rfnoc/utils/ctrlport_if_combiner.sv | 242 ++++++++++++++++++++++ lib/rfnoc/utils/ctrlport_if_decoder.sv | 107 ++++++++++ lib/rfnoc/utils/ctrlport_if_splitter.sv | 85 ++++++++ lib/rfnoc/utils/ctrlport_if_terminator.sv | 44 ++++ lib/rfnoc/utils/ctrlport_if_window.sv | 45 ++++ sim/rfnoc/Makefile.srcs | 6 +- sim/rfnoc/ctrlport_bfm_pkg.sv | 231 +++++++++++++++++++++ 12 files changed, 923 insertions(+), 4 deletions(-) create mode 100644 lib/rfnoc/core/ctrlport_if.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_clk_cross.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_combiner.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_decoder.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_splitter.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_terminator.sv create mode 100644 lib/rfnoc/utils/ctrlport_if_window.sv create mode 100644 sim/rfnoc/ctrlport_bfm_pkg.sv diff --git a/lib/rfnoc/core/Makefile.srcs b/lib/rfnoc/core/Makefile.srcs index 0159fb9..9b1c356 100644 --- a/lib/rfnoc/core/Makefile.srcs +++ b/lib/rfnoc/core/Makefile.srcs @@ -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 \ diff --git a/lib/rfnoc/core/ctrlport_if.sv b/lib/rfnoc/core/ctrlport_if.sv new file mode 100644 index 0000000..9d1826a --- /dev/null +++ b/lib/rfnoc/core/ctrlport_if.sv @@ -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 diff --git a/lib/rfnoc/core/ctrlport_pkg.sv b/lib/rfnoc/core/ctrlport_pkg.sv index 35a0b0c..b0e68ba 100644 --- a/lib/rfnoc/core/ctrlport_pkg.sv +++ b/lib/rfnoc/core/ctrlport_pkg.sv @@ -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 diff --git a/lib/rfnoc/utils/Makefile.srcs b/lib/rfnoc/utils/Makefile.srcs index 6aba292..322cdb6 100644 --- a/lib/rfnoc/utils/Makefile.srcs +++ b/lib/rfnoc/utils/Makefile.srcs @@ -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 \ )) diff --git a/lib/rfnoc/utils/ctrlport_if_clk_cross.sv b/lib/rfnoc/utils/ctrlport_if_clk_cross.sv new file mode 100644 index 0000000..47eab23 --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_clk_cross.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 diff --git a/lib/rfnoc/utils/ctrlport_if_combiner.sv b/lib/rfnoc/utils/ctrlport_if_combiner.sv new file mode 100644 index 0000000..276a7eb --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_combiner.sv @@ -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 diff --git a/lib/rfnoc/utils/ctrlport_if_decoder.sv b/lib/rfnoc/utils/ctrlport_if_decoder.sv new file mode 100644 index 0000000..3dbaa07 --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_decoder.sv @@ -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 diff --git a/lib/rfnoc/utils/ctrlport_if_splitter.sv b/lib/rfnoc/utils/ctrlport_if_splitter.sv new file mode 100644 index 0000000..c0a5b21 --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_splitter.sv @@ -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 diff --git a/lib/rfnoc/utils/ctrlport_if_terminator.sv b/lib/rfnoc/utils/ctrlport_if_terminator.sv new file mode 100644 index 0000000..8c10375 --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_terminator.sv @@ -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 diff --git a/lib/rfnoc/utils/ctrlport_if_window.sv b/lib/rfnoc/utils/ctrlport_if_window.sv new file mode 100644 index 0000000..cc04ea2 --- /dev/null +++ b/lib/rfnoc/utils/ctrlport_if_window.sv @@ -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 diff --git a/sim/rfnoc/Makefile.srcs b/sim/rfnoc/Makefile.srcs index 19cbae5..fc517a1 100644 --- a/sim/rfnoc/Makefile.srcs +++ b/sim/rfnoc/Makefile.srcs @@ -5,17 +5,18 @@ # ################################################## -# Dependencies for AXI BFMS +# Dependencies for BFMs ################################################## SIM_RFNOC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/, \ axi4lite_sv/PkgAxiLite.sv \ axi4lite_sv/AxiLiteIf.sv \ axi4s_sv/AxiStreamIf.sv \ +rfnoc/core/ctrlport_pkg.sv \ )) ################################################## -# Simulation Libraries/Headers for AXI based interfaces +# Simulation Libraries/Headers for various interfaces ################################################## SIM_RFNOC_SRCS += $(abspath $(addprefix $(BASE_DIR)/../sim/rfnoc/, \ @@ -33,4 +34,5 @@ PkgRfnocItemUtils.sv \ PkgCtrlIfaceBfm.sv \ PkgChdrIfaceBfm.sv \ PkgRfnocBlockCtrlBfm.sv \ +ctrlport_bfm_pkg.sv \ )) diff --git a/sim/rfnoc/ctrlport_bfm_pkg.sv b/sim/rfnoc/ctrlport_bfm_pkg.sv new file mode 100644 index 0000000..36b2f8b --- /dev/null +++ b/sim/rfnoc/ctrlport_bfm_pkg.sv @@ -0,0 +1,231 @@ +// +// Copyright 2024 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Package: ctrlport_bfm_pkg +// +// Description: +// +// Package defining a Control Port bus functional model (BFM). +// See the RFNoC Specification for a protocol description. +// + +package ctrlport_bfm_pkg; + + import ctrlport_pkg::*; + + class ctrlport_bfm; + + // create combined request and response struct for mailbox + typedef struct { + ctrlport_request_t req; + ctrlport_response_t resp; + bit skip_data_check; + } ctrlport_transfer_t; + + local mailbox #(ctrlport_transfer_t) transactions; + + // link to interface + local virtual ctrlport_if iface; + + // data copy for response data + local logic [CTRLPORT_DATA_W-1:0] reponse_data; + + // ------------------------------------------------------------------ + // Constructor + // ------------------------------------------------------------------ + // Class constructor. This must be given an interface for the master + // connection and an interface for the slave connection. + function new(virtual ctrlport_if iface); + this.iface = iface; + transactions = new; + endfunction + + // ------------------------------------------------------------------ + // Asynchronous transaction handling + // ------------------------------------------------------------------ + // Queue an asynchronous transaction (read or write with full request) + task async_request( + ctrlport_request_t request, + ctrlport_response_t expected_response, + bit skip_data_check = '0 + ); + assert (request.rd || request.wr) else $error(1, "Transaction must be read or write"); + transactions.put('{request, expected_response, skip_data_check}); + endtask + + // Queue a simple write transaction + task async_write( + logic[CTRLPORT_ADDR_W-1:0] addr, + logic[CTRLPORT_DATA_W-1:0] data, + ctrlport_status_t expected_status = STS_OKAY + ); + ctrlport_request_t request; + ctrlport_response_t response; + + request = '0; + request.wr = '1; + request.addr = addr; + request.data = data; + + response = '0; + response.status = expected_status; + + async_request(request, response); + endtask; + + // Queue a simple read transaction with automated response checking + task async_read( + logic [CTRLPORT_ADDR_W-1:0] addr, + logic [CTRLPORT_DATA_W-1:0] expected_data, + ctrlport_status_t expected_status = STS_OKAY + ); + ctrlport_request_t request; + ctrlport_response_t response; + + request = '0; + request.rd = '1; + request.addr = addr; + + response = '0; + response.status = expected_status; + response.data = expected_data; + + async_request(request, response); + endtask; + + // wait for all transactions to be processed + task wait_complete(); + while(transactions.num() > 0) begin + @(posedge iface.clk); + end + endtask + + // ------------------------------------------------------------------ + // Synchronous transaction handling + // ------------------------------------------------------------------ + // Perform a request and get response + task request( + input ctrlport_request_t request, + input ctrlport_response_t expected_response, + output logic[CTRLPORT_DATA_W-1:0] data + ); + async_request(request, expected_response); + wait_complete(); + data = reponse_data; + endtask; + + // Perform a simple write transaction + task write( + logic[CTRLPORT_ADDR_W-1:0] addr, + logic[CTRLPORT_DATA_W-1:0] data, + ctrlport_status_t expected_status = STS_OKAY + ); + async_write(addr, data, expected_status); + wait_complete(); + endtask; + + // Perform a simple read transaction, no response data check, return data from interface + task read( + input logic[CTRLPORT_ADDR_W-1:0] addr, + output logic[CTRLPORT_DATA_W-1:0] data, + input ctrlport_status_t expected_status = STS_OKAY + ); + ctrlport_request_t request; + ctrlport_response_t response; + + request = '0; + request.rd = '1; + request.addr = addr; + + response = '0; + response.status = expected_status; + + async_request(request, response, '1); + wait_complete(); + data = reponse_data; + endtask; + + + // ------------------------------------------------------------------ + // Signal handling + // ------------------------------------------------------------------ + // Start the BFM. + // This will join a separate thread for the interface handling. + task run(); + fork + request_body(); + join_none + endtask + + // Control the master request interface + local task request_body(); + ctrlport_transfer_t transfer; + ctrlport_request_t request_idle; + + // initialize request interface + request_idle = 'x; + request_idle.wr = '0; + request_idle.rd = '0; + iface.req = request_idle; + + // ignore first clock cycle to let response settle + @(posedge iface.clk); + + forever begin + // handle any available transaction (but leave it in the mailbox for now) + if (transactions.try_peek(transfer)) begin + // wait for reset to be deasserted + while(iface.rst) begin + @(posedge iface.clk); + check_no_response(); + end + + // send request + iface.req = transfer.req; + // check minimum time for response on interface + @(negedge iface.clk); + check_no_response(); + + // reset request interface + @(posedge iface.clk); + iface.req = request_idle; + + // wait for response + while(!iface.resp.ack) begin + @(posedge iface.clk); + end + + // status is always expected to match + assert (iface.resp.status == transfer.resp.status) else + $error(1, "Unexpected status received on interface. Expected: %2b Received: %2b", transfer.resp.status, iface.resp.status); + + // check data if read and data check is not skipped + if (transfer.req.rd && !transfer.skip_data_check) begin + assert (iface.resp.data == transfer.resp.data) else + $error(1, "Unexpected data received on interface. Expected: %h Received: %h", transfer.resp.data, iface.resp.data); + end + + // save data for read operations + reponse_data = iface.resp.data; + + // request is done -> remove from queue + transactions.get(transfer); + + // wait at least one clock cycle for next check of mailbox + end else begin + @(posedge iface.clk); + check_no_response(); + end + end + endtask + + // Check for no response in this clock cycle + local task check_no_response(); + assert (iface.resp.ack == '0) else $error(1, "Unexpected response received on interface: %p", iface.resp); + endtask + + endclass + +endpackage