rfnoc: lib: introduce ctrl-port interface and BFM
Original-commit: 886cf97ad5d494b20c6fff0a53113f6096d458c7
This commit is contained in:
@@ -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 \
|
||||
))
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user