rfnoc: lib: introduce ctrl-port interface and BFM

Original-commit: 886cf97ad5d494b20c6fff0a53113f6096d458c7
This commit is contained in:
Max Köhler
2025-02-14 08:04:26 -06:00
committed by Wade Fife
parent c48f8bd5f1
commit f961936a43
12 changed files with 923 additions and 4 deletions
+3 -2
View File
@@ -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 \
+42
View File
@@ -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
+30
View File
@@ -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
+6
View File
@@ -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 \
))
+84
View File
@@ -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
+242
View File
@@ -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
+107
View File
@@ -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
+85
View File
@@ -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
+44
View File
@@ -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
+45
View File
@@ -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
+4 -2
View File
@@ -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 \
))
+231
View File
@@ -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