587 lines
18 KiB
Systemverilog
587 lines
18 KiB
Systemverilog
//
|
|
// Copyright 2020 Ettus Research, A National Instruments Brand
|
|
//
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
//
|
|
// Module: PkgAxiStreamBfm
|
|
//
|
|
// Description: Package for a bi-directional AXI Stream bus functional model
|
|
// (BFM). This consists of the AxiStreamPacket and AxiStreamBfm classes.
|
|
// It's based on the AxiStreamIf in lib/axi4s_svPkgAXI4S.sv
|
|
//
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// AXI-Stream BFM Package
|
|
//-----------------------------------------------------------------------------
|
|
|
|
package PkgAxiStreamBfm;
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Raw packets - packet of just bytes
|
|
//---------------------------------------------------------------------------
|
|
|
|
// Ethernet in particular defies normal word boundaries, so underneath treating
|
|
// it as the most fundamental quantity - Bytes.
|
|
typedef byte raw_pkt_t[$]; // Byte Queue
|
|
|
|
// Push a packet with random data onto to the AXI Stream bus
|
|
// Args:
|
|
// - num_bytes: number of random bytes to add to the raw packet.
|
|
// - pkt: packet with rand data
|
|
task automatic get_rand_raw_pkt (
|
|
input int num_bytes,
|
|
output raw_pkt_t pkt);
|
|
begin
|
|
repeat(num_bytes) begin
|
|
pkt.push_back($urandom);
|
|
end
|
|
end
|
|
endtask
|
|
|
|
// Push a packet with a ramp on to the AXI Stream bus
|
|
// Args:
|
|
// - num_samps: Packet size in bytes *8
|
|
// - ramp_start: Start value for the ramp
|
|
// - ramp_inc: Increment per clock cycle
|
|
task automatic get_ramp_raw_pkt (
|
|
input int num_samps,
|
|
input logic [63:0] ramp_start,
|
|
input logic [63:0] ramp_inc,
|
|
input int SWIDTH=64,
|
|
output raw_pkt_t pkt);
|
|
begin
|
|
logic[63:0] word;
|
|
automatic integer counter = 0;
|
|
repeat(num_samps) begin
|
|
word = ramp_start+(counter*ramp_inc);
|
|
for (int i=0; i < SWIDTH ; i+=8) begin
|
|
pkt.push_back(word[i +: 8]);
|
|
end
|
|
counter = counter + 1;
|
|
end
|
|
end
|
|
endtask
|
|
|
|
// Comparison Functions
|
|
function automatic bit raw_pkt_compare(input raw_pkt_t a, input raw_pkt_t b);
|
|
|
|
bit queue_match;
|
|
queue_match = 1;
|
|
// check each element of the queue and clear queue_match if they don't match.
|
|
// workaround for Vivado bug - could be a==b
|
|
foreach(a[i]) queue_match = queue_match && a[i] == b[i];
|
|
return ((a.size() == b.size()) && queue_match);
|
|
|
|
endfunction
|
|
|
|
//---------------------------------------------------------------------------
|
|
// AXI Stream Packet Class
|
|
//---------------------------------------------------------------------------
|
|
|
|
class AxiStreamPacket #(DATA_WIDTH = 64, USER_WIDTH = 1);
|
|
|
|
//------------------
|
|
// Type Definitions
|
|
//------------------
|
|
|
|
// Round up width of TKEEP when TDATA is not a multiple of 8
|
|
localparam int KEEP_WIDTH = (DATA_WIDTH+7)/8;
|
|
|
|
typedef logic [DATA_WIDTH-1:0] data_t; // Single bus TDATA word
|
|
typedef logic [KEEP_WIDTH-1:0] keep_t; // Single TKEEP word
|
|
typedef logic [USER_WIDTH-1:0] user_t; // Single TUSER word
|
|
|
|
typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket_t;
|
|
|
|
bit verbose=0;
|
|
//------------
|
|
// Properties
|
|
//------------
|
|
|
|
data_t data[$];
|
|
user_t user[$];
|
|
keep_t keep[$];
|
|
|
|
|
|
//---------
|
|
// Methods
|
|
//---------
|
|
|
|
// Return a handle to a copy of this transaction
|
|
function AxisPacket_t copy();
|
|
AxisPacket_t temp;
|
|
temp = new();
|
|
temp.data = this.data;
|
|
temp.user = this.user;
|
|
temp.keep = this.keep;
|
|
return temp;
|
|
endfunction
|
|
|
|
|
|
// Delete the contents of the current packet
|
|
function void empty();
|
|
data = {};
|
|
user = {};
|
|
keep = {};
|
|
endfunction;
|
|
|
|
// Delete a word from the current packet
|
|
function void delete(int i);
|
|
data.delete(i);
|
|
user.delete(i);
|
|
keep.delete(i);
|
|
endfunction;
|
|
|
|
// Return true if this packet equals that of the argument
|
|
virtual function bit equal(AxisPacket_t packet);
|
|
// These variables are needed to workaround Vivado queue support issues
|
|
data_t data_a, data_b;
|
|
user_t user_a, user_b;
|
|
keep_t keep_a, keep_b;
|
|
|
|
if (data.size() != packet.data.size()) return 0;
|
|
foreach (data[i]) begin
|
|
data_a = data[i];
|
|
data_b = packet.data[i];
|
|
if (data_a !== data_b) begin
|
|
if (verbose) $display("AxisPacket data mismatch a[%2d]=%X b[%2d]=%X",i,data_a,i,data_b);
|
|
return 0;
|
|
end
|
|
end
|
|
|
|
if (user.size() != packet.user.size()) return 0;
|
|
foreach (data[i]) begin
|
|
user_a = user[i];
|
|
user_b = packet.user[i];
|
|
if (user_a !== user_b) begin
|
|
if (verbose) $display("AxisPacket user mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b);
|
|
return 0;
|
|
end
|
|
end
|
|
|
|
if (keep.size() != packet.keep.size()) return 0;
|
|
foreach (keep[i]) begin
|
|
keep_a = keep[i];
|
|
keep_b = packet.keep[i];
|
|
if (keep_a !== keep_b) begin
|
|
if (verbose) $display("AxisPacket keep mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b);
|
|
return 0;
|
|
end
|
|
end
|
|
|
|
return 1;
|
|
endfunction : equal
|
|
|
|
|
|
// Format the contents of the packet into a string
|
|
function string sprint();
|
|
string str = "";
|
|
string data_str = "";
|
|
if (data.size() == user.size() && data.size() == keep.size()) begin
|
|
str = { str, "data, user, keep:\n" };
|
|
foreach (data[i]) begin
|
|
data_str = "";
|
|
if (DATA_WIDTH > 64) begin
|
|
for (int b=0; b < DATA_WIDTH; b +=64) begin
|
|
data_str = { data_str, $sformatf("%3d: %X",b,data[i][b+:64])};
|
|
if (b+64 < DATA_WIDTH) begin
|
|
data_str = { data_str, $sformatf("\n ")};
|
|
end
|
|
end
|
|
end else begin
|
|
data_str = { data_str, $sformatf("%X",data[i]) };
|
|
end
|
|
str = { str, $sformatf("%5d> %s %X %X \n", i, data_str, user[i], keep[i]) };
|
|
end
|
|
end else begin
|
|
str = { str, "data:\n" };
|
|
foreach (data[i]) begin
|
|
str = { str, $sformatf("%5d> %X\n", i, data[i]) };
|
|
end
|
|
str = { str, "user:\n" };
|
|
foreach (user[i]) begin
|
|
str = { str, $sformatf("%5d> %X\n", i, user[i]) };
|
|
end
|
|
str = { str, "keep:\n" };
|
|
foreach (keep[i]) begin
|
|
str = { str, $sformatf("%5d> %X\n", i, keep[i]) };
|
|
end
|
|
end
|
|
return str;
|
|
endfunction : sprint
|
|
|
|
|
|
// Print the contents of the packet
|
|
function void print();
|
|
$display(sprint());
|
|
endfunction : print
|
|
|
|
// Add an array of bytes (little endian)
|
|
function void push_bytes(raw_pkt_t raw, input user_t user = '0);
|
|
data_t word;
|
|
keep_t my_keep;
|
|
while (raw.size() > 0) begin
|
|
// fill tkeep
|
|
// SIZE = TKEEP / 0 = 0000, 1 = 0001, 2 = 0011, etc
|
|
my_keep = '1;
|
|
if (raw.size <= DATA_WIDTH/8) begin
|
|
foreach (my_keep[i]) my_keep[i] = i < (raw.size);
|
|
end
|
|
// fill the word with raw data from bottom up
|
|
word = '0;
|
|
for (int i = 0; i < DATA_WIDTH/8 ; i++) begin
|
|
if (my_keep[i]) word[i*8 +: 8] = raw.pop_front();
|
|
end
|
|
this.data.push_back(word);
|
|
this.keep.push_back(my_keep);
|
|
this.user.push_back(user);
|
|
end
|
|
endfunction
|
|
|
|
// Dump data contents as an array of bytes. (little endian)
|
|
function raw_pkt_t dump_bytes();
|
|
data_t word;
|
|
keep_t my_keep;
|
|
raw_pkt_t raw;
|
|
assert (data.size == keep.size) else
|
|
$fatal(1, "data and keep have different sizes!");
|
|
foreach (data[i]) begin
|
|
my_keep = this.keep[i];
|
|
word = this.data[i];
|
|
for (int j = 0; j < DATA_WIDTH/8 ; j++) begin
|
|
if (my_keep[j] !== 1'b0) raw.push_back(word[j*8 +: 8]);
|
|
end;
|
|
end
|
|
return raw;
|
|
endfunction
|
|
|
|
endclass : AxiStreamPacket;
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// AXI Stream BFM Class
|
|
//---------------------------------------------------------------------------
|
|
|
|
class AxiStreamBfm #(
|
|
int DATA_WIDTH = 64,
|
|
int USER_WIDTH = 1,
|
|
int MAX_PACKET_BYTES = 0,
|
|
bit TDATA = 1,
|
|
bit TUSER = 1,
|
|
bit TKEEP = 1,
|
|
bit TLAST = 1
|
|
);
|
|
|
|
//------------------
|
|
// Type Definitions
|
|
//------------------
|
|
|
|
typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket_t;
|
|
typedef AxisPacket_t::data_t data_t;
|
|
typedef AxisPacket_t::user_t user_t;
|
|
typedef AxisPacket_t::keep_t keep_t;
|
|
|
|
|
|
//------------
|
|
// Properties
|
|
//------------
|
|
|
|
localparam int KEEP_WIDTH = AxisPacket_t::KEEP_WIDTH;
|
|
|
|
// Default stall probability, as a percentage (0-100).
|
|
local const int DEF_STALL_PROB = 38;
|
|
|
|
// Default values to use for idle bus cycles
|
|
local const AxisPacket_t::data_t IDLE_DATA = {DATA_WIDTH{1'bX}};
|
|
local const AxisPacket_t::user_t IDLE_USER = {(USER_WIDTH > 1 ? USER_WIDTH : 1){1'bX}};
|
|
local const AxisPacket_t::keep_t IDLE_KEEP = {KEEP_WIDTH{1'bX}};
|
|
|
|
// Virtual interfaces for master and slave connections to DUT
|
|
local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES,
|
|
TDATA,TUSER,TKEEP,TLAST).master master;
|
|
local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES,
|
|
TDATA,TUSER,TKEEP,TLAST).slave slave;
|
|
// NOTE: We should not need these flags if Vivado would be OK with null check
|
|
// without throwing unnecessary null-ptr deref exceptions.
|
|
local bit master_en;
|
|
local bit slave_en;
|
|
|
|
bit slave_tready_init = 0;
|
|
// Queues to store the bus transactions
|
|
mailbox #(AxisPacket_t) tx_packets;
|
|
mailbox #(AxisPacket_t) rx_packets;
|
|
|
|
// Properties for the stall behavior of the BFM
|
|
protected int master_stall_prob = DEF_STALL_PROB;
|
|
protected int slave_stall_prob = DEF_STALL_PROB;
|
|
|
|
// Number of clocks between packets
|
|
int inter_packet_gap = 0;
|
|
|
|
//---------
|
|
// Methods
|
|
//---------
|
|
|
|
// Returns 1 if the packets have the same contents, otherwise returns 0.
|
|
function bit packets_equal(AxisPacket_t a, AxisPacket_t b);
|
|
return a.equal(b);
|
|
endfunction : packets_equal
|
|
|
|
|
|
// Class constructor. This must be given an interface for the master
|
|
// connection and an interface for the slave connection.
|
|
function new(
|
|
virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES,
|
|
TDATA,TUSER,TKEEP,TLAST).master master,
|
|
virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES,
|
|
TDATA,TUSER,TKEEP,TLAST).slave slave
|
|
);
|
|
this.master_en = (master != null);
|
|
this.slave_en = (slave != null);
|
|
this.master = master;
|
|
this.slave = slave;
|
|
tx_packets = new;
|
|
rx_packets = new;
|
|
endfunction : new
|
|
|
|
|
|
// Queue the provided packet for transmission
|
|
task put(AxisPacket_t packet);
|
|
assert (master_en) else $fatal(1, "Cannot use TX operations for a null master");
|
|
tx_packets.put(packet);
|
|
endtask : put
|
|
|
|
|
|
// Attempt to queue the provided packet for transmission. Return 1 if
|
|
// successful, return 0 if the queue is full.
|
|
function bit try_put(AxisPacket_t packet);
|
|
assert (master_en) else $fatal(1, "Cannot use TX operations for a null master");
|
|
return tx_packets.try_put(packet);
|
|
endfunction : try_put
|
|
|
|
|
|
// Get the next packet when it becomes available (waits if necessary)
|
|
task get(output AxisPacket_t packet);
|
|
assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave");
|
|
rx_packets.get(packet);
|
|
endtask : get
|
|
|
|
|
|
// Get the next packet if there's one available and return 1. Return 0 if
|
|
// there's no packet available.
|
|
function bit try_get(output AxisPacket_t packet);
|
|
assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave");
|
|
return rx_packets.try_get(packet);
|
|
endfunction : try_get
|
|
|
|
|
|
// Get the next packet when it becomes available (wait if necessary), but
|
|
// don't remove it from the receive queue.
|
|
task peek(output AxisPacket_t packet);
|
|
assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave");
|
|
rx_packets.peek(packet);
|
|
endtask : peek
|
|
|
|
|
|
// Get the next packet if there's one available and return 1, but don't
|
|
// remove it from the receive queue. Return 0 if there's no packet
|
|
// available.
|
|
function bit try_peek(output AxisPacket_t packet);
|
|
assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave");
|
|
return rx_packets.try_peek(packet);
|
|
endfunction : try_peek
|
|
|
|
|
|
// Return the number of packets available in the receive queue
|
|
function int num_received();
|
|
assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave");
|
|
return rx_packets.num();
|
|
endfunction
|
|
|
|
|
|
// Wait until num packets have started transmission (i.e., until num
|
|
// packets have been dequeued). Set num = -1 to wait until all currently
|
|
// queued packets have started transmission.
|
|
task wait_send(int num = -1);
|
|
int end_num;
|
|
assert (master_en) else $fatal(1, "Cannot use TX operations for a null master");
|
|
|
|
if (num == -1) end_num = 0;
|
|
else begin
|
|
end_num = tx_packets.num() - num;
|
|
assert(end_num >= 0) else begin
|
|
$fatal(1, "Not enough packets queued to wait for %0d packets", num);
|
|
end
|
|
end
|
|
while(tx_packets.num() > end_num) @(posedge master.clk);
|
|
endtask : wait_send
|
|
|
|
|
|
// Wait until num packets have completed transmission. Set num = -1 to wait
|
|
// for all currently queued packets to complete transmission.
|
|
task wait_complete(int num = -1);
|
|
int end_num;
|
|
assert (master_en) else $fatal(1, "Cannot use TX operations for a null master");
|
|
|
|
if (num == -1) num = tx_packets.num();
|
|
else begin
|
|
assert(num <= tx_packets.num()) else begin
|
|
$fatal(1, "Not enough packets queued to wait for %0d packets", num);
|
|
end
|
|
end
|
|
|
|
repeat (num) begin
|
|
@(posedge master.tlast); // Wait for last word
|
|
do begin // Wait until the last word is accepted
|
|
@(posedge master.clk);
|
|
end while(master.tready != 1);
|
|
end
|
|
endtask : wait_complete
|
|
|
|
|
|
// Set the probability (as a percentage, 0 to 100) of the master interface
|
|
// stalling due to lack of data to send.
|
|
function void set_master_stall_prob(int stall_probability = DEF_STALL_PROB);
|
|
assert(stall_probability >= 0 && stall_probability <= 100) else begin
|
|
$fatal(1, "Invalid master stall_probability value");
|
|
end
|
|
master_stall_prob = stall_probability;
|
|
endfunction
|
|
|
|
|
|
// Set the probability (as a percentage, 0 to 100) of the slave interface
|
|
// stalling due to lack of buffer space.
|
|
function void set_slave_stall_prob(int stall_probability = DEF_STALL_PROB);
|
|
assert(stall_probability >= 0 && stall_probability <= 100) else begin
|
|
$fatal(1, "Invalid slave stall_probability value");
|
|
end
|
|
slave_stall_prob = stall_probability;
|
|
endfunction
|
|
|
|
|
|
// Get the probability (as a percentage, 0 to 100) of the master interface
|
|
// stalling due to lack of data to send.
|
|
function int get_master_stall_prob();
|
|
return master_stall_prob;
|
|
endfunction
|
|
|
|
|
|
// Get the probability (as a percentage, 0 to 100) of the slave interface
|
|
// stalling due to lack of buffer space.
|
|
function int get_slave_stall_prob();
|
|
return slave_stall_prob;
|
|
endfunction
|
|
|
|
|
|
// Determine if the slave interface is doing a transfer this clock
|
|
function logic slave_idle();
|
|
return !slave.tvalid;
|
|
endfunction
|
|
|
|
|
|
// Create separate processes for driving the master and slave interfaces
|
|
task run();
|
|
fork
|
|
if (master_en) master_body();
|
|
if (slave_en) slave_body();
|
|
join_none
|
|
endtask
|
|
|
|
|
|
//----------------
|
|
// Master Process
|
|
//----------------
|
|
|
|
local task master_body();
|
|
AxisPacket_t packet;
|
|
|
|
master.tvalid <= 0;
|
|
master.tdata <= IDLE_DATA;
|
|
master.tuser <= IDLE_USER;
|
|
master.tkeep <= IDLE_KEEP;
|
|
master.tlast <= 0;
|
|
|
|
forever begin
|
|
repeat(inter_packet_gap) begin
|
|
@(posedge master.clk);
|
|
if (master.rst) continue;
|
|
end
|
|
|
|
if (tx_packets.try_get(packet)) begin
|
|
foreach (packet.data[i]) begin
|
|
// Randomly deassert tvalid for next word and stall
|
|
if ($urandom_range(99) < master_stall_prob) begin
|
|
master.tvalid <= 0;
|
|
master.tdata <= IDLE_DATA;
|
|
master.tuser <= IDLE_USER;
|
|
master.tkeep <= IDLE_KEEP;
|
|
master.tlast <= 0;
|
|
do begin
|
|
@(posedge master.clk);
|
|
if (master.rst) break;
|
|
end while ($urandom_range(99) < master_stall_prob);
|
|
if (master.rst) break;
|
|
end
|
|
|
|
// Send the next word
|
|
master.tvalid <= 1;
|
|
master.tdata <= packet.data[i];
|
|
master.tuser <= packet.user[i];
|
|
master.tkeep <= packet.keep[i];
|
|
if (i == packet.data.size()-1) master.tlast <= 1;
|
|
|
|
do begin
|
|
@(posedge master.clk);
|
|
if (master.rst) break;
|
|
end while (!master.tready);
|
|
end
|
|
master.tvalid <= 0;
|
|
master.tdata <= IDLE_DATA;
|
|
master.tuser <= IDLE_USER;
|
|
master.tkeep <= IDLE_KEEP;
|
|
master.tlast <= 0;
|
|
end else begin
|
|
@(posedge master.clk);
|
|
if (master.rst) continue;
|
|
end
|
|
end
|
|
endtask : master_body
|
|
|
|
|
|
//---------------
|
|
// Slave Process
|
|
//---------------
|
|
|
|
local task slave_body();
|
|
AxisPacket_t packet = new();
|
|
|
|
slave.tready <= slave_tready_init;
|
|
|
|
forever begin
|
|
@(posedge slave.clk);
|
|
if (slave.rst) continue;
|
|
|
|
if (slave.tvalid) begin
|
|
if (slave.tready) begin
|
|
packet.data.push_back(slave.tdata);
|
|
packet.user.push_back(slave.tuser);
|
|
packet.keep.push_back(slave.tkeep);
|
|
if (slave.tlast) begin
|
|
rx_packets.put(packet.copy());
|
|
packet.data = {};
|
|
packet.user = {};
|
|
packet.keep = {};
|
|
end
|
|
end
|
|
slave.tready <= $urandom_range(99) < slave_stall_prob ? 0 : 1;
|
|
end
|
|
end
|
|
endtask : slave_body
|
|
|
|
endclass : AxiStreamBfm
|
|
|
|
|
|
endpackage : PkgAxiStreamBfm
|