232 lines
6.8 KiB
Systemverilog
232 lines
6.8 KiB
Systemverilog
//
|
|
// 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
|