Merge FPGA repository back into UHD repository

The FPGA codebase was removed from the UHD repository in 2014 to reduce
the size of the repository. However, over the last half-decade, the
split between the repositories has proven more burdensome than it has
been helpful. By merging the FPGA code back, it will be possible to
create atomic commits that touch both FPGA and UHD codebases. Continuous
integration testing is also simplified by merging the repositories,
because it was previously difficult to automatically derive the correct
UHD branch when testing a feature branch on the FPGA repository.

This commit also updates the license files and paths therein.

We are therefore merging the repositories again. Future development for
FPGA code will happen in the same repository as the UHD host code and
MPM code.

== Original Codebase and Rebasing ==

The original FPGA repository will be hosted for the foreseeable future
at its original local location: https://github.com/EttusResearch/fpga/

It can be used for bisecting, reference, and a more detailed history.

The final commit from said repository to be merged here is
05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as
v4.0.0.0-pre-uhd-merge.

If you have changes in the FPGA repository that you want to rebase onto
the UHD repository, simply run the following commands:

- Create a directory to store patches (this should be an empty
  directory):

    mkdir ~/patches

- Now make sure that your FPGA codebase is based on the same state as
  the code that was merged:

    cd src/fpga # Or wherever your FPGA code is stored
    git rebase v4.0.0.0-pre-uhd-merge

  Note: The rebase command may look slightly different depending on what
  exactly you're trying to rebase.

- Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge:

    git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches

  Note: Make sure that only patches are stored in your output directory.
  It should otherwise be empty. Make sure that you picked the correct
  range of commits, and only commits you wanted to rebase were exported
  as patch files.

- Go to the UHD repository and apply the patches:

    cd src/uhd # Or wherever your UHD repository is stored
    git am --directory fpga ~/patches/*
    rm -rf ~/patches # This is for cleanup

== Contributors ==

The following people have contributed mainly to these files (this list
is not complete):

Co-authored-by: Alex Williams <alex.williams@ni.com>
Co-authored-by: Andrej Rode <andrej.rode@ettus.com>
Co-authored-by: Ashish Chaudhari <ashish@ettus.com>
Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com>
Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Derek Kozel <derek.kozel@ettus.com>
Co-authored-by: EJ Kreinar <ej@he360.com>
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Ian Buckley <ian.buckley@gmail.com>
Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Jon Kiser <jon.kiser@ni.com>
Co-authored-by: Josh Blum <josh@joshknows.com>
Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
Co-authored-by: Matt Ettus <matt@ettus.com>
Co-authored-by: Michael West <michael.west@ettus.com>
Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com>
Co-authored-by: Nick Foster <nick@ettus.com>
Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Paul David <paul.david@ettus.com>
Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com>
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-authored-by: Sylvain Munaut <tnt@246tNt.com>
Co-authored-by: Trung Tran <trung.tran@ettus.com>
Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>


Original-commit: bafa9d95453387814ef25e6b6256ba8db2df612f
This commit is contained in:
Martin Braun
2020-01-28 09:35:36 -08:00
co-authored by Alex Williams Andrej Rode Ashish Chaudhari Ben Hilburn Ciro Nishiguchi Daniel Jepson Derek Kozel EJ Kreinar Humberto Jimenez Ian Buckley Jörg Hofrichter Jon Kiser Josh Blum Jonathon Pendlum Matt Ettus Michael West Moritz Fischer Nick Foster Nicolas Cuervo Paul Butler Paul David Ryan Marlow Sugandha Gupta Sylvain Munaut Trung Tran Vidush Vishwanath Wade Fife
parent 74893643ca
commit 6b67702ad7
2157 changed files with 1282567 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
#
# Copyright 2019 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
##################################################
# Simulation Libraries/Headers for AXI based interfaces
##################################################
SIM_RFNOC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../sim/rfnoc/, \
PkgTestExec.sv \
test_exec.svh \
sim_clock_gen.sv \
PkgAxiStreamBfm.sv \
PkgChdrUtils.sv \
PkgChdrBfm.sv \
PkgAxisCtrlBfm.sv \
PkgRfnocItemUtils.sv \
PkgRfnocBlockCtrlBfm.sv \
))
##################################################
# Proto-RFNoC simlib still used in some places
##################################################
SIM_PROTORFNOC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../sim/rfnoc/, \
sim_rfnoc_lib.svh \
))
+474
View File
@@ -0,0 +1,474 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgAxiStream
//
// Description: Package for a bi-directional AXI Stream bus functional model
// (BFM). This consists of the AxiStreamIf interface, and the
// AxiStreamPacket and AxiStreamBfm classes.
//
//-----------------------------------------------------------------------------
// Unidirectional AXI4-Stream interface
//-----------------------------------------------------------------------------
interface AxiStreamIf #(
parameter int DATA_WIDTH = 64,
parameter int USER_WIDTH = 1
) (
input logic clk,
input logic rst = 1'b0
);
// Signals that make up a unidirectional AXI-Stream interface
logic tready;
logic tvalid;
logic [DATA_WIDTH-1:0] tdata;
logic [USER_WIDTH-1:0] tuser;
logic [DATA_WIDTH/8-1:0] tkeep;
logic tlast;
// View from the master side
modport master (
input clk, rst,
output tvalid, tdata, tuser, tkeep, tlast,
input tready
);
// View from the slave side
modport slave (
input clk, rst,
input tvalid, tdata, tuser, tkeep, tlast,
output tready
);
endinterface : AxiStreamIf
//-----------------------------------------------------------------------------
// AXI-Stream BFM Package
//-----------------------------------------------------------------------------
package PkgAxiStreamBfm;
//---------------------------------------------------------------------------
// AXI Stream Packet Class
//---------------------------------------------------------------------------
class AxiStreamPacket #(DATA_WIDTH = 64, USER_WIDTH = 1);
//------------------
// Type Definitions
//------------------
typedef logic [DATA_WIDTH-1:0] data_t; // Single bus TDATA word
typedef logic [DATA_WIDTH/8-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;
//------------
// Properties
//------------
data_t data[$];
user_t user[$];
keep_t keep[$];
//---------
// Methods
//---------
// Return a handle to a copy of this transaction
function AxisPacket copy();
AxisPacket 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;
// Return true if this packet equals that of the argument
virtual function bit equal(AxisPacket 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) return 0;
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) return 0;
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) return 0;
end
return 1;
endfunction : equal
// Format the contents of the packet into a string
function string sprint();
string str = "";
if (data.size() == user.size() && data.size() == keep.size()) begin
str = { str, "data, user, keep:\n" };
foreach (data[i]) begin
str = { str, $sformatf("%5d> %X %X %b\n", i, data[i], 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
endclass : AxiStreamPacket;
//---------------------------------------------------------------------------
// AXI Stream BFM Class
//---------------------------------------------------------------------------
class AxiStreamBfm #(
parameter int DATA_WIDTH = 64,
parameter int USER_WIDTH = 1
);
//------------------
// Type Definitions
//------------------
typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket;
typedef AxisPacket::data_t data_t;
typedef AxisPacket::user_t user_t;
typedef AxisPacket::keep_t keep_t;
//------------
// Properties
//------------
// 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::data_t IDLE_DATA = {DATA_WIDTH{1'bX}};
local const AxisPacket::user_t IDLE_USER = {(USER_WIDTH > 1 ? USER_WIDTH : 1){1'bX}};
local const AxisPacket::keep_t IDLE_KEEP = {(DATA_WIDTH/8){1'bX}};
// Virtual interfaces for master and slave connections to DUT
local virtual AxiStreamIf #(DATA_WIDTH, USER_WIDTH).master master;
local virtual AxiStreamIf #(DATA_WIDTH, USER_WIDTH).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;
// Queues to store the bus transactions
mailbox #(AxisPacket) tx_packets;
mailbox #(AxisPacket) 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;
//---------
// Methods
//---------
// Returns 1 if the packets have the same contents, otherwise returns 0.
function bit packets_equal(AxisPacket a, AxisPacket 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).master master,
virtual AxiStreamIf #(DATA_WIDTH, USER_WIDTH).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 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 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 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 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 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 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(int stall_probability = DEF_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(int stall_probability = DEF_STALL_PROB);
return slave_stall_prob;
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 packet;
master.tvalid <= 0;
master.tdata <= IDLE_DATA;
master.tuser <= IDLE_USER;
master.tkeep <= IDLE_KEEP;
master.tlast <= 0;
forever begin
@(posedge master.clk);
if (master.rst) continue;
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
end
endtask : master_body
//---------------
// Slave Process
//---------------
local task slave_body();
AxisPacket packet = new();
slave.tready <= 0;
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
+232
View File
@@ -0,0 +1,232 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgAxisCtrlBfm
//
// Description: Package for an AXIS-Ctrl bus functional model (BFM), which
// consists primarily of the AxisCtrlPacket and AxisCtrlBfm classes.
//
package PkgAxisCtrlBfm;
import PkgChdrUtils::*;
import PkgAxiStreamBfm::*;
export PkgAxiStreamBfm::*;
//---------------------------------------------------------------------------
// AXIS-Ctrl Packet Class
//---------------------------------------------------------------------------
class AxisCtrlPacket;
//------------
// Properties
//------------
axis_ctrl_header_t header;
chdr_word_t timestamp;
ctrl_op_word_t op_word;
ctrl_word_t data[$];
//---------
// Methods
//---------
// Return a handle to a copy of this transaction
function AxisCtrlPacket copy();
AxisCtrlPacket temp;
temp = new();
temp.header = this.header;
temp.timestamp = this.timestamp;
temp.op_word = this.op_word;
temp.data = this.data;
return temp;
endfunction
// Return true if this packet equals that of the argument
function bit equal(AxisCtrlPacket packet);
if (header != packet.header) return 0;
if (op_word != packet.op_word) return 0;
if (data.size() != packet.data.size()) return 0;
if (header.has_time && timestamp != packet.timestamp) return 0;
foreach (data[i]) begin
if (data[i] != packet.data[i]) return 0;
end
return 1;
endfunction : equal
// Format the contents of the packet into a string
function string sprint();
string str;
str = {str, $sformatf("AxisCtrlPacket:\n")};
str = {str, $sformatf("- header: %p\n", header) };
if (header.has_time)
str = {str, $sformatf("- timestamp: %0d\n", timestamp) };
str = {str, $sformatf("- op_word: '{status:%s,op_code:%s,byte_enable:0b%4b,address:0x%05x}\n",
op_word.status.name, op_word.op_code.name, op_word.byte_enable, op_word.address)};
str = {str, $sformatf("- data:\n") };
foreach (data[i]) begin
str = {str, $sformatf("%5d> 0x%08x\n", i, data[i]) };
end
return str;
endfunction : sprint
// Print the contents of the packet
function void print();
$display(sprint());
endfunction : print
function void write_ctrl(
ref axis_ctrl_header_t header,
ref ctrl_op_word_t op_word,
ref ctrl_word_t data[$],
input chdr_word_t timestamp = 0
);
this.header = header;
this.data = data;
this.timestamp = timestamp;
this.op_word = op_word;
endfunction : write_ctrl
endclass : AxisCtrlPacket;
//---------------------------------------------------------------------------
// AXIS-Ctrl Bus Functional Model Class
//---------------------------------------------------------------------------
class AxisCtrlBfm extends AxiStreamBfm #(32);
extern function new (
virtual AxiStreamIf #(32).master master,
virtual AxiStreamIf #(32).slave slave
);
extern task put_ctrl(AxisCtrlPacket ctrl_packet);
extern task try_put_ctrl(AxisCtrlPacket ctrl_packet);
extern task get_ctrl(output AxisCtrlPacket ctrl_packet);
extern function bit try_get_ctrl(output AxisCtrlPacket ctrl_packet);
extern function AxisPacket axis_ctrl_to_axis(AxisCtrlPacket ctrl_packet);
extern function AxisCtrlPacket axis_to_axis_ctrl(AxisPacket axis_packet);
endclass : AxisCtrlBfm
//---------------------------------------------------------------------------
// AXIS-Ctrl BFM Methods
//---------------------------------------------------------------------------
// Class constructor. This must be given an interface for the master
// connection and an interface for the slave connection.
function AxisCtrlBfm::new (
virtual AxiStreamIf #(32).master master,
virtual AxiStreamIf #(32).slave slave
);
super.new(master, slave);
endfunction : new
// Queue the provided packet for transmission
task AxisCtrlBfm::put_ctrl(AxisCtrlPacket ctrl_packet);
AxisPacket axis_packet;
axis_packet = axis_ctrl_to_axis(ctrl_packet);
super.put(axis_packet);
endtask : put_ctrl
// Attempt to queue the provided packet for transmission. Return 1 if
// successful, return 0 if the queue is full.
task AxisCtrlBfm::try_put_ctrl(AxisCtrlPacket ctrl_packet);
AxisPacket axis_packet;
axis_packet = axis_ctrl_to_axis(ctrl_packet);
super.put(axis_packet);
endtask : try_put_ctrl
// Get the next packet when it becomes available (waits if necessary)
task AxisCtrlBfm::get_ctrl(output AxisCtrlPacket ctrl_packet);
AxisPacket axis_packet;
super.get(axis_packet);
ctrl_packet = axis_to_axis_ctrl(axis_packet);
endtask : get_ctrl
// Get the next packet if there's one available and return 1. Return 0 if
// there's no packet available.
function bit AxisCtrlBfm::try_get_ctrl(output AxisCtrlPacket ctrl_packet);
AxisPacket axis_packet;
if(!super.try_get(axis_packet)) return 0;
ctrl_packet = axis_to_axis_ctrl(axis_packet);
return 1;
endfunction : try_get_ctrl
// Convert an AXIS-Ctrl packet data structure to an AXI-Stream packet data
// structure.
function AxisCtrlBfm::AxisPacket AxisCtrlBfm::axis_ctrl_to_axis(AxisCtrlPacket ctrl_packet);
AxisPacket axis_packet = new();
int index;
// Insert words 0 and 1 (header)
axis_packet.data.push_back(ctrl_packet.header[31: 0]);
axis_packet.data.push_back(ctrl_packet.header[63:32]);
// Insert timestamp if has_time is set (words 2 and 3)
if (ctrl_packet.header.has_time) begin
axis_packet.data.push_back(ctrl_packet.timestamp[31: 0]);
axis_packet.data.push_back(ctrl_packet.timestamp[63:32]);
end
// Insert word 4 (operation word)
axis_packet.data.push_back(ctrl_packet.op_word[31: 0]);
// Insert data
foreach (ctrl_packet.data[i]) begin
axis_packet.data.push_back(ctrl_packet.data[i]);
end
return axis_packet;
endfunction : axis_ctrl_to_axis
// Convert an AXI-Stream packet data structure to an AXIS-Ctrl packet data
// structure.
function AxisCtrlPacket AxisCtrlBfm::axis_to_axis_ctrl(AxisPacket axis_packet);
AxisCtrlPacket ctrl_packet = new();
int i; // Use an index instead of pop_front() to workaround a ModelSim bug
// Grab words 0 and 1 (header)
ctrl_packet.header[31: 0] = axis_packet.data[0];
ctrl_packet.header[63:32] = axis_packet.data[1];
// If has_time is set, grab timestamp (words 2 and 3)
i = 2;
if (ctrl_packet.header.has_time) begin
ctrl_packet.timestamp[31: 0] = axis_packet.data[2];
ctrl_packet.timestamp[63:32] = axis_packet.data[3];
i += 2;
end
// Grab word 4 (operation word)
ctrl_packet.op_word[31: 0] = axis_packet.data[i];
// Grab data
ctrl_packet.data = axis_packet.data[(i+1):$];
return ctrl_packet;
endfunction : axis_to_axis_ctrl
endpackage : PkgAxisCtrlBfm
+780
View File
@@ -0,0 +1,780 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgChdrBfm
//
// Description: Package for a bi-directional CHDR bus functional model (BFM),
// which consists primarily of the ChdrPacket and ChdrBfm classes.
//
package PkgChdrBfm;
import PkgChdrUtils::*;
import PkgAxiStreamBfm::*;
//---------------------------------------------------------------------------
// CHDR Packet Class
//---------------------------------------------------------------------------
class ChdrPacket #(int BUS_WIDTH = 64);
typedef ChdrPacket #(BUS_WIDTH) ChdrPacket;
chdr_header_t header;
chdr_word_t timestamp;
chdr_word_t metadata[$];
chdr_word_t data[$];
extern function ChdrPacket copy();
extern function bit equal(ChdrPacket packet);
extern function string sprint(bit pretty = 1);
extern function void print(bit pretty = 1);
// Accessors
extern function void write_raw (ref chdr_header_t header,
ref chdr_word_t data[$],
input chdr_word_t metadata[$] = {},
input chdr_word_t timestamp = 0,
input int data_byte_length = -1);
extern function void read_raw (output chdr_header_t header,
output chdr_word_t data[$],
output chdr_word_t metadata[$],
output chdr_word_t timestamp,
output int data_byte_length);
extern function void write_stream_status(ref chdr_header_t header,
ref chdr_str_status_t status);
extern function void read_stream_status (output chdr_header_t header,
output chdr_str_status_t status);
extern function void write_stream_cmd (ref chdr_header_t header,
ref chdr_str_command_t command);
extern function void read_stream_cmd (output chdr_header_t header,
output chdr_str_command_t command);
extern function void write_mgmt (ref chdr_header_t header,
ref chdr_mgmt_t mgmt);
extern function void read_mgmt (output chdr_header_t header,
output chdr_mgmt_t mgmt);
extern function void write_ctrl (ref chdr_header_t header,
ref chdr_ctrl_header_t ctrl_header,
ref ctrl_op_word_t ctrl_op_word,
ref ctrl_word_t ctrl_data[$],
input chdr_word_t ctrl_timestamp = 0);
extern function void read_ctrl (output chdr_header_t header,
output chdr_ctrl_header_t ctrl_header,
output ctrl_op_word_t ctrl_op_word,
output ctrl_word_t ctrl_data[$],
output chdr_word_t ctrl_timestamp);
// Helper methods
extern function int header_bytes();
extern function int mdata_bytes();
extern function int data_bytes();
extern function void update_lengths();
extern function string sprint_raw();
extern function string sprint_pretty();
endclass : ChdrPacket;
//---------------------------------------------------------------------------
// CHDR BFM Class
//---------------------------------------------------------------------------
class ChdrBfm #(
parameter int BUS_WIDTH = 64,
parameter int USER_WIDTH = 1
) extends AxiStreamBfm #(BUS_WIDTH, USER_WIDTH);
typedef ChdrPacket #(BUS_WIDTH) ChdrPacket;
// Number of 64-bit CHDR words per AXI word
const int CHDR_PER_BUS = BUS_WIDTH / $bits(chdr_word_t);
// Default fields used by high-level transaction methods
chdr_epid_t dst_epid;
chdr_seq_num_t seq_num;
extern function new (
virtual AxiStreamIf #(BUS_WIDTH, USER_WIDTH).master master,
virtual AxiStreamIf #(BUS_WIDTH, USER_WIDTH).slave slave
);
// Send Transactions
extern task put_chdr(ChdrPacket chdr_packet);
extern function bit try_put_chdr(ChdrPacket chdr_packet);
// Receive Transactions
extern task get_chdr(output ChdrPacket chdr_packet);
extern function bit try_get_chdr(output ChdrPacket chdr_packet);
extern task peek_chdr(output ChdrPacket chdr_packet);
extern function bit try_peek_chdr(output ChdrPacket chdr_packet);
// AXI-Stream/CHDR Conversion Functions
extern function ChdrPacket axis_to_chdr (AxisPacket axis_packet);
extern function AxisPacket chdr_to_axis (ChdrPacket chdr_packet);
endclass : ChdrBfm
//---------------------------------------------------------------------------
// CHDR Packet Class Methods
//---------------------------------------------------------------------------
// Create a copy of this packet and return a handle to the copy
function ChdrPacket::ChdrPacket ChdrPacket::copy();
ChdrPacket temp;
temp = new();
temp.header = this.header;
temp.timestamp = this.timestamp;
temp.metadata = this.metadata;
temp.data = this.data;
return temp;
endfunction
// Return true if this packet equals that of the argument
function bit ChdrPacket::equal(ChdrPacket packet);
if (header != packet.header) return 0;
if (!chdr_word_queues_equal(data, packet.data)) return 0;
if (!chdr_word_queues_equal(metadata, packet.metadata)) return 0;
if (header.pkt_type == CHDR_DATA_WITH_TS && timestamp !== packet.timestamp) return 0;
return 1;
endfunction : equal
// Format the contents of the packet into a string (don't dissect contents)
function string ChdrPacket::sprint_raw();
string str;
str = {str, $sformatf("ChdrPacket:\n")};
str = {str, $sformatf("- header: %p\n", header) };
str = {str, $sformatf("- timestamp: %X\n", timestamp) };
str = {str, $sformatf("- metadata:\n") };
foreach (metadata[i]) begin
str = {str, $sformatf("%5d> %X\n", i, metadata[i]) };
end
str = {str, $sformatf("- data:\n") };
foreach (data[i]) begin
str = {str, $sformatf("%5d> %X\n", i, data[i]) };
end
return str;
endfunction : sprint_raw
// Format the contents of the packet into a string (dissect contents)
function string ChdrPacket::sprint_pretty();
string str;
str = {str, $sformatf("ChdrPacket:\n")};
str = {str, $sformatf("- header: %p\n", header) };
if (header.pkt_type == CHDR_DATA_WITH_TS) begin
str = {str, $sformatf("- timestamp: %0d\n", timestamp) };
end
if (header.num_mdata != '0) begin
str = {str, $sformatf("- metadata:\n") };
foreach (metadata[i]) begin
str = {str, $sformatf("%5d> %X\n", i, metadata[i]) };
end
end
str = {str, $sformatf("- data (%s):\n", header.pkt_type.name) };
if (header.pkt_type == CHDR_MANAGEMENT) begin
chdr_header_t tmp_hdr;
chdr_mgmt_t tmp_mgmt;
read_mgmt(tmp_hdr, tmp_mgmt);
str = {str, $sformatf(" > chdr_mgmt_header_t : %p\n", tmp_mgmt.header)};
foreach (tmp_mgmt.ops[i]) begin
str = {str, $sformatf(" > %3d: chdr_mgmt_op_t: '{op_payload:0x%12x,op_code:%s,ops_pending:%0d}\n",
i, tmp_mgmt.ops[i].op_payload, tmp_mgmt.ops[i].op_code.name, tmp_mgmt.ops[i].ops_pending)};
end
end else if (header.pkt_type == CHDR_STRM_STATUS) begin
chdr_header_t tmp_hdr;
chdr_str_status_t tmp_sts;
read_stream_status(tmp_hdr, tmp_sts);
str = {str, $sformatf(" > chdr_str_status_t0 : '{status_info:0x%012x,buff_info:0x%04x,xfer_count_bytes:%0d,xfer_count_pkts:%0d...\n",
tmp_sts.status_info,tmp_sts.buff_info,tmp_sts.xfer_count_bytes,tmp_sts.xfer_count_pkts)};
str = {str, $sformatf(" > chdr_str_status_t1 : capacity_pkts:%0d,capacity_bytes:%0d,status:%s,src_epid:%0d}\n",
tmp_sts.capacity_pkts,tmp_sts.capacity_bytes,tmp_sts.status.name,tmp_sts.src_epid)};
end else if (header.pkt_type == CHDR_STRM_CMD) begin
chdr_header_t tmp_hdr;
chdr_str_command_t tmp_cmd;
read_stream_cmd(tmp_hdr, tmp_cmd);
str = {str, $sformatf(" > chdr_str_command_t : %p\n", tmp_cmd)};
end else if (header.pkt_type == CHDR_CONTROL) begin
chdr_header_t tmp_hdr;
chdr_word_t tmp_ts;
chdr_ctrl_header_t tmp_ctrl_hdr;
ctrl_op_word_t tmp_op_word;
ctrl_word_t tmp_ctrl_data[$];
read_ctrl(tmp_hdr, tmp_ctrl_hdr, tmp_op_word, tmp_ctrl_data, tmp_ts);
str = {str, $sformatf(" > chdr_ctrl_header_t : %p\n", tmp_ctrl_hdr)};
if (tmp_ctrl_hdr.has_time)
str = {str, $sformatf(" > timestamp : %0d\n", tmp_ts)};
str = {str, $sformatf(" > ctrl_op_word_t : '{status:%s,op_code:%s,byte_enable:0b%4b,address:0x%05x}\n",
tmp_op_word.status.name, tmp_op_word.op_code.name, tmp_op_word.byte_enable, tmp_op_word.address)};
foreach (tmp_ctrl_data[i]) begin
str = {str, $sformatf(" > data %2d : 0x%08x\n", i, tmp_ctrl_data[i])};
end
end else begin
foreach (data[i]) begin
str = {str, $sformatf("%5d> %X\n", i, data[i]) };
end
end
return str;
endfunction : sprint_pretty
function string ChdrPacket::sprint(bit pretty = 1);
if (pretty)
return sprint_pretty();
else
return sprint_raw();
endfunction: sprint
// Print the contents of the packet
function void ChdrPacket::print(bit pretty = 1);
$display(sprint(pretty));
endfunction : print
// Populate the packet with the provided info. The packet Length and NumMData
// fields are calculated and set in this method. Omitting the
// data_byte_length argument, or providing a negative value, causes this
// method to calculate the payload length based on the size of the data
// array.
function void ChdrPacket::write_raw (
ref chdr_header_t header,
ref chdr_word_t data[$],
input chdr_word_t metadata[$] = {},
input chdr_word_t timestamp = 0,
input int data_byte_length = -1
);
this.header = header;
this.timestamp = timestamp;
this.data = data;
this.metadata = metadata;
update_lengths();
// Adjust length field according to data_byte_length
if (data_byte_length >= 0) begin
int array_num_bytes;
// Make sure number of words for data_byte_length matches data length
assert((data_byte_length+7) / 8 == data.size()) else begin
$error("ChdrPacket::write_raw: data_byte_length doesn't correspond to number of words in data");
end
array_num_bytes = data.size() * $bits(chdr_word_t)/8;
this.header.length -= (array_num_bytes - data_byte_length);
end
endfunction : write_raw
// Read the contents of this packet
function void ChdrPacket::read_raw (
output chdr_header_t header,
output chdr_word_t data[$],
output chdr_word_t metadata[$],
output chdr_word_t timestamp,
output int data_byte_length
);
header = this.header;
data = this.data;
metadata = this.metadata;
timestamp = this.timestamp;
data_byte_length = data_bytes();
endfunction : read_raw
// Populate this packet as a status packet
function void ChdrPacket::write_stream_status (
ref chdr_header_t header,
ref chdr_str_status_t status
);
header.pkt_type = CHDR_STRM_STATUS; // Update packet type in header
this.header = header;
data = {};
for (int i = 0; i < $bits(status); i += $bits(chdr_word_t)) begin
data.push_back( status[i +: $bits(chdr_word_t)] );
end
update_lengths();
endfunction : write_stream_status
// Read this packet as a status packet
function void ChdrPacket::read_stream_status (
output chdr_header_t header,
output chdr_str_status_t status
);
// Make sure it's a stream status packet
assert(this.header.pkt_type == CHDR_STRM_STATUS) else begin
$error("ChdrPacket::read_status: Packet type is not CHDR_STRM_STATUS");
end
// Make sure we have enough payload
assert($bits(status) <= $bits(data)) else begin
$error("ChdrPacket::read_status: Not enough data for status payload");
end
header = this.header;
for (int i = 0; i < $bits(status)/$bits(chdr_word_t); i++) begin
status[i*$bits(chdr_word_t) +: $bits(chdr_word_t)] = data[i];
end
endfunction : read_stream_status
// Populate this packet as a command packet
function void ChdrPacket::write_stream_cmd (
ref chdr_header_t header,
ref chdr_str_command_t command
);
header.pkt_type = CHDR_STRM_CMD; // Update packet type in header
this.header = header;
data = {};
for (int i = 0; i < $bits(command); i += $bits(chdr_word_t)) begin
data.push_back( command[i +: $bits(chdr_word_t)] );
end
update_lengths();
endfunction : write_stream_cmd
// Read this packet as a command packet
function void ChdrPacket::read_stream_cmd (
output chdr_header_t header,
output chdr_str_command_t command
);
// Make sure it's a stream command packet
assert(this.header.pkt_type == CHDR_STRM_CMD) else begin
$error("ChdrPacket::read_command: Packet type is not CHDR_STRM_CMD");
end
// Make sure we have enough payload
assert($bits(command) <= $bits(data)) else begin
$error("ChdrPacket::read_command: Not enough data for command payload");
end
header = this.header;
for (int i = 0; i < $bits(command)/$bits(chdr_word_t); i++) begin
command[i*$bits(chdr_word_t) +: $bits(chdr_word_t)] = data[i];
end
endfunction : read_stream_cmd
// Populate this packet as a management packet
function void ChdrPacket::write_mgmt (
ref chdr_header_t header,
ref chdr_mgmt_t mgmt
);
header.pkt_type = CHDR_MANAGEMENT; // Update packet type in header
this.header = header;
data = {};
// Insert the header
data.push_back( mgmt.header );
// Insert the ops
foreach (mgmt.ops[i]) begin
data.push_back( mgmt.ops[i] );
end
update_lengths();
endfunction : write_mgmt
// Read this packet as a management packet
function void ChdrPacket::read_mgmt (
output chdr_header_t header,
output chdr_mgmt_t mgmt
);
int num_ops;
// Make sure it's a management packet
assert(header.pkt_type == CHDR_MANAGEMENT) else begin
$error("ChdrPacket::read_mgmt: Packet type is not CHDR_MANAGEMENT");
end
header = this.header;
num_ops = data_bytes()/8 - 1; // Num words, minus one for the header
// Make sure we have enough payload
assert(1 + num_ops <= data.size()) else begin
$error("ChdrPacket::read_mgmt: Not enough data for management payload");
end
// Read the management header
mgmt.header = data[0];
// Read the management operations
for (int i = 0; i < num_ops; i++) begin
mgmt.ops.push_back(data[i+1]);
end
endfunction : read_mgmt
// Populate this packet as a control packet
function void ChdrPacket::write_ctrl (
ref chdr_header_t header,
ref chdr_ctrl_header_t ctrl_header,
ref ctrl_op_word_t ctrl_op_word,
ref ctrl_word_t ctrl_data[$],
input chdr_word_t ctrl_timestamp = 0
);
bit partial_word;
ctrl_word_t mandatory_data;
header.pkt_type = CHDR_CONTROL; // Update packet type in header
this.header = header;
data = {};
// Insert word 0 of control payload
data.push_back(ctrl_header[63:0]);
// Insert word 1 of control payload, if timestamp is used
if (ctrl_header.has_time) begin
data.push_back(ctrl_timestamp);
end
// Insert word 2 of control payload, the operation word
// and first word of control data.
mandatory_data = (ctrl_header.num_data > 0) ? ctrl_data[0] : '0;
data.push_back({mandatory_data, ctrl_op_word[31:0]});
// We have a half CHDR word if num_data is even
partial_word = (ctrl_header.num_data[0] == '0);
// Insert remaining data, if present
for (int i = 1; i < ctrl_data.size(); i+=2) begin
data.push_back({(partial_word ? '0 : ctrl_data[i+1]), ctrl_data[i]});
end
update_lengths();
endfunction : write_ctrl
// Read this packet as a control packet
function void ChdrPacket::read_ctrl (
output chdr_header_t header,
output chdr_ctrl_header_t ctrl_header,
output ctrl_op_word_t ctrl_op_word,
output ctrl_word_t ctrl_data[$],
output chdr_word_t ctrl_timestamp
);
chdr_word_t chdr_op_word;
int dptr = 0;
// Make sure it's a stream status packet
assert(this.header.pkt_type == CHDR_CONTROL) else begin
$error("ChdrPacket::read_status: Packet type is not CHDR_CONTROL");
end
// Make sure we have enough payload
assert($bits(ctrl_header)+$bits(ctrl_op_word) <= $bits(data)) else begin
$error("ChdrPacket::read_status: Not enough data for status payload");
end
header = this.header;
// Word 0
ctrl_header[63:0] = data[dptr++];
// Word 1
if (ctrl_header.has_time) begin
ctrl_timestamp = data[dptr++];
end
// Word 2, last 32-bits of control header and first word of control data
chdr_op_word = data[dptr++];
ctrl_op_word = chdr_op_word[31:0];
ctrl_data.delete();
if (ctrl_header.num_data > 0) begin
ctrl_data[0] = chdr_op_word[63:32];
end
// Copy any remaining data words
for (int i = dptr; i < data.size(); i++) begin
ctrl_data.push_back(data[i][31:0]);
if (i != data.size()-1 || ctrl_header.num_data[0] != 0)
ctrl_data.push_back(data[i][63:32]);
end
endfunction : read_ctrl
// Calculate the header size (including timestamp), in bytes, from the header
// information.
function int ChdrPacket::header_bytes();
if (BUS_WIDTH == $bits(chdr_word_t) && header.pkt_type == CHDR_DATA_WITH_TS) begin
header_bytes = 2 * (BUS_WIDTH / 8); // Two words (header + timestamp)
end else begin
header_bytes = (BUS_WIDTH / 8); // One word, regardless of timestamp
end
endfunction : header_bytes
// Calculate the metadata size from the header information.
function int ChdrPacket::mdata_bytes();
mdata_bytes = header.num_mdata * BUS_WIDTH/8;
endfunction : mdata_bytes
// Calculate the data payload size, in bytes, from the header information
function int ChdrPacket::data_bytes();
data_bytes = header.length - header_bytes() - mdata_bytes();
endfunction : data_bytes;
// Update the length and num_mdata header fields of the packet based on the
// size of the metadata queue and the data queue.
function void ChdrPacket::update_lengths();
int num_bytes;
int num_mdata;
// Calculate NumMData based on the size of metadata queue
num_mdata = metadata.size() / (BUS_WIDTH / $bits(chdr_word_t));
if (metadata.size() % (BUS_WIDTH / $bits(chdr_word_t)) != 0) begin
num_mdata++;
end
assert(num_mdata < 2**$bits(chdr_num_mdata_t)) else
$fatal(1, "ChdrPacket::update_lengths(): Calculated NumMData exceeds maximum size");
// Calculate the Length field
num_bytes = data.size() * $bits(chdr_word_t) / 8; // Payload length
num_bytes = num_bytes + header_bytes() + mdata_bytes(); // Payload + header length
assert(num_bytes < 2**$bits(chdr_length_t)) else
$fatal(1, "ChdrPacket::update_lengths(): Calculated Length exceeds maximum size");
// Update header
header.num_mdata = num_mdata;
header.length = num_bytes;
endfunction : update_lengths
//---------------------------------------------------------------------------
// CHDR BFM Class Methods
//---------------------------------------------------------------------------
// Class constructor. This must be given an interface for the master
// connection and an interface for the slave connection.
function ChdrBfm::new (
virtual AxiStreamIf #(BUS_WIDTH, USER_WIDTH).master master,
virtual AxiStreamIf #(BUS_WIDTH, USER_WIDTH).slave slave
);
super.new(master, slave);
assert(BUS_WIDTH % 64 == 0) else begin
$fatal(1, "ChdrBfm::new: CHDR bus width must be a multiple of 64 bits");
end
endfunction : new
// Queue the provided packet for transmission
task ChdrBfm::put_chdr (ChdrPacket chdr_packet);
AxisPacket axis_packet;
axis_packet = chdr_to_axis(chdr_packet);
super.put(axis_packet);
endtask : put_chdr
// Attempt to queue the provided packet for transmission. Return 1 if
// successful, return 0 if the queue is full.
function bit ChdrBfm::try_put_chdr (ChdrPacket chdr_packet);
AxisPacket axis_packet;
bit status;
axis_packet = chdr_to_axis(chdr_packet);
return super.try_put(axis_packet);
endfunction : try_put_chdr
// Get the next packet when it becomes available (wait if necessary)
task ChdrBfm::get_chdr (output ChdrPacket chdr_packet);
AxisPacket axis_packet;
super.get(axis_packet);
chdr_packet = axis_to_chdr(axis_packet);
endtask : get_chdr
// Get the next packet if there's one available and return 1. Return 0 if
// there's no packet available.
function bit ChdrBfm::try_get_chdr (output ChdrPacket chdr_packet);
AxisPacket axis_packet;
if (!super.try_get(axis_packet)) return 0;
chdr_packet = axis_to_chdr(axis_packet);
return 1;
endfunction : try_get_chdr
// Get the next packet when it becomes available (wait if necessary), but
// don't remove it from the receive queue.
task ChdrBfm::peek_chdr (output ChdrPacket chdr_packet);
AxisPacket axis_packet;
super.peek(axis_packet);
chdr_packet = axis_to_chdr(axis_packet);
endtask : peek_chdr
// 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 ChdrBfm::try_peek_chdr (output ChdrPacket chdr_packet);
AxisPacket axis_packet;
if (!super.try_get(axis_packet)) return 0;
chdr_packet = axis_to_chdr(axis_packet);
return 1;
endfunction : try_peek_chdr
// Convert the data payload of an AXI Stream packet data structure to a CHDR
// packet data structure.
function ChdrBfm::ChdrPacket ChdrBfm::axis_to_chdr (AxisPacket axis_packet);
enum int { ST_HEADER, ST_TIMESTAMP, ST_METADATA, ST_PAYLOAD } rx_state;
data_t word;
int num_rx_mdata;
ChdrPacket chdr_packet = new();
rx_state = ST_HEADER;
for(int i = 0; i < axis_packet.data.size(); i++) begin
word = axis_packet.data[i];
case (rx_state)
ST_HEADER : begin
chdr_packet.header = word[63:0];
// Depending on the size of the word, we could have just the header
// or both the header and the timestamp in this word.
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin
if ($bits(word) >= 128) begin
chdr_packet.timestamp = word[127:64];
rx_state = ST_METADATA;
end else begin
rx_state = ST_TIMESTAMP;
end
end else begin
rx_state = ST_METADATA;
end
// Check if there's no metadata, in which case we can skip it
if (rx_state == ST_METADATA && chdr_packet.header.num_mdata == 0) begin
rx_state = ST_PAYLOAD;
end
end
ST_TIMESTAMP : begin
chdr_packet.timestamp = word;
rx_state = (chdr_packet.header.num_mdata > 0) ? ST_METADATA : ST_PAYLOAD;
end
ST_METADATA : begin
for(int w = 0; w < CHDR_PER_BUS; w++) begin
// Grab the next chdr_word_t worth of bits
//$display("Grabbing meta word %d (%016X)", w, word[w*$bits(chdr_word_t) +: $bits(chdr_word_t)]);
chdr_packet.metadata.push_back(word[w*$bits(chdr_word_t) +: $bits(chdr_word_t)]);
end
num_rx_mdata++;
if (num_rx_mdata == chdr_packet.header.num_mdata) rx_state = ST_PAYLOAD;
end
ST_PAYLOAD : begin
for(int w = 0; w < CHDR_PER_BUS; w++) begin
// Grab the next chdr_word_t worth of bits
//$display("Grabbing data word %d (%016X)", w, word[w*$bits(chdr_word_t) +: $bits(chdr_word_t)]);
chdr_packet.data.push_back(word[w*$bits(chdr_word_t) +: $bits(chdr_word_t)]);
end
end
endcase
end
assert(rx_state == ST_PAYLOAD) else begin
$error("ChdrBfm::axis_to_chdr: Malformed CHDR packet");
end
return chdr_packet;
endfunction : axis_to_chdr
// Convert a CHDR packet data structure to a an AXI-Stream packet data
// structure.
function ChdrBfm::AxisPacket ChdrBfm::chdr_to_axis (ChdrPacket chdr_packet);
int num_bus_words, num_chdr_words, expected_bus_words;
data_t bus_word = 0;
AxisPacket axis_packet = new();
// Check that we have the right number of metadata words
num_chdr_words = chdr_packet.metadata.size();
num_bus_words = num_chdr_words / CHDR_PER_BUS;
if (num_chdr_words % CHDR_PER_BUS != 0) num_bus_words++;
assert (num_bus_words == chdr_packet.header.num_mdata) else begin
$error("ChdrBfm::chdr_to_axis: Packet metadata size doesn't match header NumMData field");
end
// Calculate the number of words needed to represent this packet
num_bus_words = 0;
num_bus_words += chdr_packet.data.size() / CHDR_PER_BUS;
if (chdr_packet.data.size() % CHDR_PER_BUS != 0) num_bus_words++;
num_bus_words += chdr_packet.metadata.size() / CHDR_PER_BUS;
if (chdr_packet.metadata.size() % CHDR_PER_BUS != 0) num_bus_words++;
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS && CHDR_PER_BUS == 1) begin
// Add two words, one for header and one for timestamp
num_bus_words += 2;
end else begin
// Add one word only for header (which may or may not include a timestamp)
num_bus_words += 1;
end
// Calculate the number of words represented by the Length field
expected_bus_words = chdr_packet.header.length / (BUS_WIDTH/8);
if (chdr_packet.header.length % (BUS_WIDTH/8) != 0) expected_bus_words++;
// Make sure length field matches actual packet length
assert (num_bus_words == expected_bus_words) else begin
$error("ChdrBfm::chdr_to_axis: Packet size doesn't match header Length field");
end
// Insert header
bus_word[63:0] = chdr_packet.header;
if (BUS_WIDTH == 64) begin
axis_packet.data.push_back(bus_word);
if (chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS) begin
// Insert timestamp
axis_packet.data.push_back(chdr_packet.timestamp);
end
end else begin
// Copy the timestamp word from the header, regardless of whether or not
// this packet uses the timestamp field.
bus_word[127:64] = chdr_packet.timestamp;
axis_packet.data.push_back(bus_word);
end
// Insert metadata
while (chdr_packet.metadata.size() > 0) begin
bus_word = 0;
for (int w = 0; w < CHDR_PER_BUS; w++) begin
bus_word[w*$bits(chdr_word_t) +: $bits(chdr_word_t)] = chdr_packet.metadata.pop_front();
if (chdr_packet.metadata.size() == 0) break;
end
axis_packet.data.push_back(bus_word);
end
// Insert payload
while (chdr_packet.data.size() > 0) begin
bus_word = 0;
for (int word_count = 0; word_count < CHDR_PER_BUS; word_count++) begin
bus_word[word_count*64 +: 64] = chdr_packet.data.pop_front();
if (chdr_packet.data.size() == 0) break;
end
axis_packet.data.push_back(bus_word);
end
return axis_packet;
endfunction : chdr_to_axis
endpackage : PkgChdrBfm
+276
View File
@@ -0,0 +1,276 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgChdrUtils
//
// Description: Various types, constants, and functions for interacting with
// the RFNoC CHDR bus infrastructure.
//
package PkgChdrUtils;
//---------------------------------------------------------------------------
// Type Definitions
//---------------------------------------------------------------------------
// CHDR Definitions
// ----------------
// The fundamental unit of the CHDR bus, which is always a multiple of 64-bits
typedef logic [63:0] chdr_word_t;
typedef chdr_word_t chdr_word_queue_t[$];
// CHDR header fields
typedef enum bit [2:0] {
CHDR_MANAGEMENT = 3'd0,
CHDR_STRM_STATUS = 3'd1,
CHDR_STRM_CMD = 3'd2,
CHDR_RESERVED_0 = 3'd3,
CHDR_CONTROL = 3'd4,
CHDR_RESERVED_1 = 3'd5,
CHDR_DATA_NO_TS = 3'd6,
CHDR_DATA_WITH_TS = 3'd7
} chdr_pkt_type_t; // CHDR Packet Type
typedef bit [ 5:0] chdr_vc_t; // CHDR Virtual Channel field
typedef bit [ 4:0] chdr_num_mdata_t; // CHDR Num Metadata field
typedef bit [15:0] chdr_seq_num_t; // CHDR SeqNum field
typedef bit [15:0] chdr_length_t; // CHDR Length field
typedef bit [15:0] chdr_epid_t; // CHDR EPID field
// CHDR Context Field Identifiers
typedef enum bit [3:0] {
CONTEXT_FIELD_HDR = 4'd0,
CONTEXT_FIELD_HDR_TS = 4'd1,
CONTEXT_FIELD_TS = 4'd2,
CONTEXT_FIELD_MDATA = 4'd3
} chdr_context_type_t;
// AXIS-Ctrl Definitions
// ---------------------
// The fundamental unit of the AXIS-Ctrl (control) bus, which is always 32 bits
typedef logic [31:0] ctrl_word_t;
typedef enum bit [3:0] {
CTRL_OP_SLEEP = 4'd0,
CTRL_OP_WRITE = 4'd1,
CTRL_OP_READ = 4'd2,
CTRL_OP_WRITE_READ = 4'd3,
CTRL_OP_RESERVED_0 = 4'd4,
CTRL_OP_RESERVED_1 = 4'd5,
CTRL_OP_RESERVED_2 = 4'd6,
CTRL_OP_RESERVED_3 = 4'd7,
CTRL_OP_RESERVED_4 = 4'd8,
CTRL_OP_RESERVED_5 = 4'd9
} ctrl_opcode_t; // Control OpCode Type
typedef enum bit [1:0] {
CTRL_STS_OKAY = 2'd0,
CTRL_STS_CMDERR = 2'd1,
CTRL_STS_TSERR = 2'd2,
CTRL_STS_WARNING = 2'd3
} ctrl_status_t; // Control OpCode Type
typedef bit [5:0] ctrl_seq_num_t; // AXIS-Ctrl SeqNum field
typedef bit [3:0] ctrl_num_data_t; // AXIS-Ctrl NumData field
typedef bit [9:0] ctrl_port_t; // AXIS-Ctrl source/destination port field
typedef bit [3:0] ctrl_byte_en_t; // AXIS-Ctrl ByteEnable field
typedef bit [19:0] ctrl_address_t; // AXIS-Ctrl Address field
// CHDR Type-Specific Definitions
// ------------------------------
// CHDR Status packet fields
typedef enum bit [3:0] {
STRS_OKAY = 4'd0,
STRS_CMDERR = 4'd1,
STRS_SEQERR = 4'd2,
STRS_DATAERR = 4'd3,
STRS_RTERR = 4'd4
} chdr_strs_status_t; // CHDR stream status packet status field
// CHDR Control packet fields
typedef enum bit [3:0] {
STRC_INIT = 4'd0,
STRC_PING = 4'd1,
STRC_RESYNC = 4'd2
} chdr_strc_opcode_t; // CHDR stream command packet opcode filed
// CHDR Management packet field
typedef enum bit [2:0] {
CHDR_W_64 = 3'd0,
CHDR_W_128 = 3'd1,
CHDR_W_256 = 3'd2,
CHDR_W_512 = 3'd3,
CHDR_W_INVALID = 3'd7
} chdr_mgmt_width_t; // CHDR management packet CHDR Width field
function automatic chdr_mgmt_width_t translate_chdr_w(int bitwidth);
case (bitwidth)
64: return CHDR_W_64;
128: return CHDR_W_128;
256: return CHDR_W_256;
512: return CHDR_W_512;
default: return CHDR_W_INVALID;
endcase
endfunction : translate_chdr_w
typedef enum bit [7:0] {
MGMT_OP_NOP = 8'd0,
MGMT_OP_ADVERTISE = 8'd1,
MGMT_OP_SEL_DEST = 8'd2,
MGMT_OP_RETURN = 8'd3,
MGMT_OP_INFO_REQ = 8'd4,
MGMT_OP_INFO_RESP = 8'd5,
MGMT_OP_CFG_WR_REQ = 8'd6,
MGMT_OP_CFG_RD_REQ = 8'd7,
MGMT_OP_CFG_RD_RESP = 8'd8
} chdr_mgmt_opcode_t; // CHDR management packet OpCode field
//---------------------------------------------------------------------------
// Packet Data Structures
//---------------------------------------------------------------------------
// CHDR packet header
typedef struct packed {
chdr_vc_t vc;
bit eob;
bit eov;
chdr_pkt_type_t pkt_type;
chdr_num_mdata_t num_mdata;
chdr_seq_num_t seq_num;
chdr_length_t length;
chdr_epid_t dst_epid;
} chdr_header_t;
// AXIS-Ctrl packet header
typedef struct packed {
// Word 1
bit [ 5:0] _rsvd_0;
ctrl_port_t rem_dst_port;
chdr_epid_t rem_dst_epid;
// Word 0
bit is_ack;
bit has_time;
ctrl_seq_num_t seq_num;
ctrl_num_data_t num_data;
ctrl_port_t src_port;
ctrl_port_t dst_port;
} axis_ctrl_header_t;
// AXIS-Ctrl packet header
typedef struct packed {
ctrl_status_t status;
bit [ 1:0] _rsvd_0;
ctrl_opcode_t op_code;
bit [ 3:0] byte_enable;
bit [19:0] address;
} ctrl_op_word_t;
// Ctrl packet header when in the payload of a CHDR packet
typedef struct packed {
bit [15:0] _rsvd_0;
chdr_epid_t src_epid;
bit is_ack;
bit has_time;
ctrl_seq_num_t seq_num;
ctrl_num_data_t num_data;
ctrl_port_t src_port;
ctrl_port_t dst_port;
} chdr_ctrl_header_t;
// CHDR stream status packet payload
typedef struct packed {
// Word 3
bit [47:0] status_info;
bit [15:0] buff_info;
// Word 2
bit [63:0] xfer_count_bytes;
// Word 1
bit [39:0] xfer_count_pkts;
bit [23:0] capacity_pkts;
// Word 0
bit [39:0] capacity_bytes;
bit [ 3:0] _rsvd_0;
chdr_strs_status_t status;
chdr_epid_t src_epid;
} chdr_str_status_t;
// CHDR stream command packet payload
typedef struct packed {
// Word 1
bit [63:0] num_bytes;
// Word 0
bit [39:0] num_pkts;
bit [ 3:0] op_data;
chdr_strc_opcode_t op_code;
chdr_epid_t src_epid;
} chdr_str_command_t;
// CHDR management packet header
typedef struct packed {
bit [15:0] prot_ver;
chdr_mgmt_width_t chdr_width;
bit [18:0] _rsvd_0;
bit [ 9:0] num_hops;
chdr_epid_t src_epid;
} chdr_mgmt_header_t;
// CHDR management packet operation
typedef struct packed {
bit [47:0] op_payload;
chdr_mgmt_opcode_t op_code;
bit [ 7:0] ops_pending;
} chdr_mgmt_op_t;
// CHDR management packet
typedef struct {
chdr_mgmt_header_t header;
chdr_mgmt_op_t ops[$];
} chdr_mgmt_t;
//---------------------------------------------------------------------------
// Functions
//---------------------------------------------------------------------------
// Returns 1 if the queues have the same contents, otherwise returns 0. This
// function is equivalent to (a == b), but this doesn't work correctly yet in
// Vivado 2018.3.
function automatic bit chdr_word_queues_equal(ref chdr_word_t a[$], ref chdr_word_t b[$]);
chdr_word_t x, y;
if (a.size() != b.size()) return 0;
foreach (a[i]) begin
x = a[i];
y = b[i];
if (x !== y) return 0;
end
return 1;
endfunction : chdr_word_queues_equal
// Returns 1 if the queues have the same contents, otherwise returns 0. This
// function is equivalent to (a == b), but this doesn't work correctly yet in
// Vivado 2018.3.
function automatic bit chdr_mgmt_op_queues_equal(ref chdr_mgmt_op_t a[$], ref chdr_mgmt_op_t b[$]);
chdr_mgmt_op_t x, y;
if (a.size() != b.size()) return 0;
foreach (a[i]) begin
x = a[i];
y = b[i];
if (x !== y) return 0;
end
return 1;
endfunction : chdr_mgmt_op_queues_equal
endpackage : PkgChdrUtils
File diff suppressed because it is too large Load Diff
+215
View File
@@ -0,0 +1,215 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgRfnocItemUtils
//
// Description: This package contains utilities to handle and process items.
//
// - ItemDataBuff: A class that holds a collection of items of arbitrary width
// To can convert to and from a CHDR vector
// - ItemDataBuffQueue: A queue of ItemDataBuff buffers
//
package PkgRfnocItemUtils;
import PkgChdrUtils::*;
//---------------------------------------------------------------------------
// Item Data Buffer
//---------------------------------------------------------------------------
//
// This class items of an arbitrary type
//
//---------------------------------------------------------------------------
class ItemDataBuff #(type data_t);
typedef ItemDataBuff #(data_t) ItemDataBuffQueue[$];
// Store data in the user-specified format
local data_t buff[$];
// Create a new empty buffer
function new();
buff.delete();
endfunction : new
// Get the bitwidth of a item in this buffer
function int item_w();
return $size(data_t);
endfunction : item_w
// Delete the contents of this buffer
function void delete();
buff.delete();
endfunction : delete
// Get the size (in number of words) of this buffer
function int size();
return buff.size();
endfunction : size
// Get the size (in number of bytes) of this buffer
function int get_bytes();
return size() * (item_w() / 8);
endfunction : get_bytes
// Get the i'th element in this buffer
function data_t get(int i);
return buff[i];
endfunction : get
// Put and element in this buffer. If i is negative
// then put it at the end
function void put(data_t d, int i = -1);
if (i < 0)
buff.push_back(d);
else
buff.insert(i, d);
endfunction : put
// Convert the contents of this buffer to a CHDR payload
// A CHDR payload can be transmitted using the block controller
// BFM.
function chdr_word_queue_t to_chdr_payload();
int samps_per_word = $size(chdr_word_t) / $size(data_t);
int num_chdr_lines = ((buff.size() + samps_per_word - 1) / samps_per_word);
chdr_word_t chdr_w_vtr[$];
for (int i = 0; i < num_chdr_lines; i++) begin
chdr_word_t tmp = 'x;
for (int j = 0; j < samps_per_word; j++) begin
tmp[j*$size(data_t) +: $size(data_t)] = buff[i*samps_per_word + j];
end
chdr_w_vtr.push_back(tmp);
end
return chdr_w_vtr;
endfunction : to_chdr_payload
// Populate this buffer using a CHDR payload
// A CHDR payload can be received using the block controller
// BFM.
function void from_chdr_payload(chdr_word_queue_t chdr_w_vtr, int bytes);
int samps_per_word = $size(chdr_word_t) / $size(data_t);
int bytes_left = bytes;
buff.delete();
foreach (chdr_w_vtr[i]) begin
for (int j = 0; j < samps_per_word; j++) begin
if (bytes_left > 0) begin
buff.push_back(chdr_w_vtr[i][j*$size(data_t) +: $size(data_t)]);
bytes_left -= ($size(data_t)/8);
end
end
end
endfunction : from_chdr_payload
// Output a string representation of the contents of the buffer
function string sprint(string format = "%X", bit as_chdr = 0);
if (as_chdr) begin
string str = $sformatf("ItemDataBuff (64-bit CHDR, %0d bytes)\n", get_bytes());
chdr_word_queue_t chdr_q = this.to_chdr_payload();
foreach (chdr_q[i]) begin
str = { str, $sformatf({"%5d> ", format, "\n"}, i, chdr_q[i]) };
end
return str;
end else begin
string str = $sformatf("ItemDataBuff (%0d-bit)\n", $size(data_t));
foreach (buff[i]) begin
str = { str, $sformatf({"%5d> ", format, "\n"}, i, buff[i]) };
end
return str;
end
endfunction : sprint
// Print a string representation of the contents of the buffer
function void print(string format = "%X", bit as_chdr = 0);
$display(sprint(format, as_chdr));
endfunction : print
// Check if the contents of two buffers is equal
function bit equal(
ItemDataBuff #(data_t) rhs
);
if (this.size() != rhs.size()) return 0;
for (int i = 0; i < this.size(); i++) begin
if (this.get(i) != rhs.get(i)) return 0;
end
return 1;
endfunction : equal
endclass
class ItemDataBuffQueue #(type data_t);
local ItemDataBuff #(data_t) queue[$];
// Create a new empty queue
function new();
queue.delete();
endfunction : new
// Delete the contents of this queue
function void delete();
queue.delete();
endfunction : delete
// Get the size (in number of buffers) of this queue
function int size();
return queue.size();
endfunction : size
// Get the i'th element in this buffer
function ItemDataBuff #(data_t) get(int i);
return queue[i];
endfunction : get
// Put an element in this buffer. If i is negative
// then put it at the end
function void put(ItemDataBuff #(data_t) buff, int i = -1);
if (i < 0)
queue.push_back(buff);
else
queue.insert(i, buff);
endfunction : put
// Create a queue of buffers and fill them using the contents of the specified file.
function void from_hex_file(
int max_buff_size,
string filename
);
string line;
int word_i = 0;
int handle = $fopen(filename, "r");
queue.delete();
while ($fgets(line, handle) > 0) begin
data_t word = 'x;
int buff_i = word_i++ / max_buff_size;
if (queue.size() < buff_i + 1) begin
ItemDataBuff #(data_t) buff = new;
queue.push_back(buff);
end
if ($sscanf(line, "%x", word) > 0) begin
queue[buff_i].put(word);
end else begin
$error("File parse error");
end
end
$fclose(handle);
handle = 0;
endfunction : from_hex_file
// Write the contents of the queue of buffers to the specified file.
function void to_hex_file(
string filename
);
int handle = $fopen(filename, "w");
foreach (queue[i]) begin
for (int j = 0; j < queue[i].size(); j++) begin
$fdisplay(handle, $sformatf("%x", queue[i].get(j)));
end
end
$fclose(handle);
handle = 0;
endfunction : to_hex_file
endclass
endpackage : PkgRfnocItemUtils
+297
View File
@@ -0,0 +1,297 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: PkgTestExec
//
// Description: This package provides infrastructure for tracking the state of
// testbench execution and the results of each test.
//
package PkgTestExec;
timeunit 1ns;
timeprecision 1ps;
typedef enum { SEV_INFO, SEV_WARNING, SEV_ERROR, SEV_FATAL } severity_t;
typedef std::process timeout_t;
class TestExec;
string tb_name; // Name of the testbench
string current_test; // Name of the current test being run
int num_started, num_finished; // Number of tests started and finished
int num_passed; // Number of tests that have passed
int num_assertions; // Number of assertions checked for the current test
time start_time, end_time; // Start and end time of the testbench
bit stop_on_error = 1; // Configuration option to stop when an error occurs
bit test_status[$]; // Pass/fail status of each test
timeout_t tb_timeout; // Handle to timeout for the overall testbench
timeout_t test_timeout; // Handle to timeout for current test
semaphore test_sem; // Testbench semaphore
function new();
$timeformat(-9, 0, " ns", 12);
this.test_sem = new(1);
endfunction : new
// Call start_tb() at the start of a testbench to initialize state tracking
// properties.
//
// time_limit: Max simulation time allowed before at timeout occurs.
// This is a catch-all, in case things go very wrong during
// simulation and cause it to never end.
//
task start_tb(string tb_name, realtime time_limit = 10ms);
// Get the sempahore, to prevent multiple overlapping instances of the
// same testbench.
test_sem.get();
$display("========================================================");
$display("TESTBENCH STARTED: %s", tb_name);
$display("========================================================");
this.tb_name = tb_name;
start_time = $time;
test_status = {};
num_started = 0;
num_finished = 0;
num_passed = 0;
start_timeout(
tb_timeout,
time_limit,
$sformatf("Testbench \"%s\" time limit exceeded", tb_name),
SEV_FATAL
);
endtask : start_tb
// Call end_tb() at the end of a testbench to report final statistics and,
// optionally, end simulation.
//
// finish: Set to 1 (default) to cause $finish() to be called at the
// end of simulation, cuasing the simulator to close.
//
function void end_tb(bit finish = 1);
assert (num_started == num_finished) else begin
$fatal(1, "Testbench ended before test completed");
end
end_time = $time;
$display("========================================================");
$display("TESTBENCH FINISHED: %s", tb_name);
$display(" - Time elapsed: %0t", end_time - start_time);
$display(" - Tests Run: %0d", num_finished);
$display(" - Tests Passed: %0d", num_passed);
$display(" - Tests Failed: %0d", num_started - num_passed);
$display("Result: %s",
(num_started == num_passed) && (num_finished > 0)
? "PASSED " : "FAILED!!!");
$display("========================================================");
end_timeout(tb_timeout);
if (finish) $finish();
// Release the semaphore to allow new instances of the testbench to run
test_sem.put();
endfunction : end_tb
// Call at the start of each test with the name of the test.
//
// test_name: String name for the test to be started
//
task start_test(string test_name, realtime time_limit = 0);
// Make sure a there isn't already a test running
assert (num_started == num_finished) else begin
$fatal(1, "Test started before completing previous test");
end
// Create a timeout for this test
if (time_limit > 0) begin
start_timeout(
test_timeout,
time_limit,
$sformatf("Test \"%s\" time limit exceeded", test_name),
SEV_FATAL
);
end
current_test = test_name;
num_started++;
$display("[TEST CASE %3d] (t = %t) BEGIN: %s...", num_started, $time, test_name);
test_status.push_back(1); // Set status to 1 (passed) initially
num_assertions = 0;
endtask : start_test
// Call end_test() at the end of each test.
//
// test_result: Optional value to indicate the overall pass/fail result
// of the test. Use non-zero for pass, 0 for fail.
//
task end_test(int test_result = 1);
bit passed;
assert (num_started == num_finished + 1) else begin
$fatal(1, "No test running");
end
end_timeout(test_timeout);
passed = test_status[num_started-1] && test_result;
num_finished++;
$display("[TEST CASE %3d] (t = %t) DONE... %s",
num_started, $time, passed ? "Passed" : "FAILED");
if (passed) num_passed++;
current_test = "";
endtask : end_test
// Assert the given expression and call $error() if it fails. Simulation
// will also be stopped (using $stop) if stop_on_error is true.
//
// expr: The expression value to be asserted
// message: String to report if the assertion fails
//
function void assert_error(int expr, string message = "");
num_assertions++;
assert (expr) else begin
test_status[num_started] = 0;
$error(message);
if (stop_on_error) $stop;
end
endfunction : assert_error
// Assert the given expression and call $fatal() if it fails.
//
// expr: The expression value to be asserted
// message: String to report if the assertion fails
//
function void assert_fatal(int expr, string message = "");
num_assertions++;
assert (expr) else begin
test_status[num_started] = 0;
$fatal(1, message);
end
endfunction : assert_fatal
// Assert the given expression and call $warning() if it fails.
//
// expr: The expression value to be asserted
// message: String to report if the assertion fails
//
function void assert_warning(int expr, string message = "");
num_assertions++;
assert (expr) else begin
$warning(message);
end
endfunction : assert_warning
// Assert the given expression and call the appropriate severity or fatal
// task if the expression fails.
//
// expr: The expression value to be asserted
// message: String to report if the assertion fails
// severity: Indicates the type of severity task that should be used if
// the assertion fails ($info, $warning, $error, $fatal).
// Default value is SEV_ERROR.
//
function void assert_sev(
int expr,
string message = "",
severity_t severity = SEV_ERROR
);
num_assertions++;
assert (expr) else begin
case (severity)
SEV_INFO: begin
$info(message);
end
SEV_WARNING: begin
$warning(message);
end
SEV_ERROR: begin
$error(message);
test_status[num_started] = 0;
if (stop_on_error) $stop;
end
default: begin // SEV_FATAL
test_status[num_started] = 0;
$fatal(1, message);
end
endcase
end
endfunction : assert_sev
// Create a timeout that will expire after the indicated delay, causing an
// error if the timeout is not canceled before the delay has elapsed.
//
// handle: A handle to the timeout process created. Use this
// handle to end the timeout.
// timeout_delay: Amount of time to wait before the timeout expires.
// message: String message to display if the timeout expires.
// severity: Indicates the type of severity task that should be
// used if the timeout expires. Default is SEV_ERROR.
//
task start_timeout(
output timeout_t handle,
input realtime timeout_delay,
input string message = "Timeout",
input severity_t severity = SEV_ERROR
);
mailbox #(std::process) pid = new(1);
fork
begin : timeout
pid.put(process::self());
#(timeout_delay);
assert_sev(0, {"Timeout: ", message}, severity);
end
join_none
#0; // Start the timeout process so we can get its process ID
// Return the process ID
pid.get(handle);
endtask
// Cancel the timeout with the given handle. This kills the process
// running the timeout delay.
//
// handle: Handle created by start_timeout() for the timeout process.
//
function void end_timeout(timeout_t handle);
if (handle != null)
if (handle.status != process::FINISHED) handle.kill();
endfunction;
endclass : TestExec
//---------------------------------------------------------------------------
// Test Object
//---------------------------------------------------------------------------
//
// This is the default TestExec object instance shared by all instances of
// the running the testbench.
//
//---------------------------------------------------------------------------
TestExec test = new();
endpackage : PkgTestExec
+127
View File
@@ -0,0 +1,127 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: sim_clock_gen
//
// Description: This module generates a clock and reset signal for the purposes
// of simulation. Both clock and reset are configurable at run time for
// software-based simulation control.
//
module sim_clock_gen #(
parameter realtime PERIOD = 10.0, // Period in ns
parameter real DUTY_CYCLE = 0.5, // Duty cycle, in the range (0.0, 1.0)
parameter bit AUTOSTART = 1 // Start clock automatically at time 0
) (
output bit clk,
output bit rst
);
timeunit 1ns;
timeprecision 1ps;
realtime period = PERIOD;
real duty = DUTY_CYCLE;
realtime low_time = PERIOD * (1.0 - DUTY_CYCLE);
realtime high_time = PERIOD * DUTY_CYCLE;
bit toggle = AUTOSTART;
bit alive = 1;
//-----------------------
// Clock and Reset Tasks
//-----------------------
// Set the period and duty cycle for the clock
function void set_clock(real new_period, real new_duty);
low_time = new_period * (1.0 - new_duty);
high_time = new_period * new_duty;
endfunction
// Set the period, only, for the clock
function void set_period(real new_period);
set_clock(new_period, duty);
endfunction
// Set the duty cycle, only, for the clock
function void set_duty(real new_duty);
set_clock(period, new_duty);
endfunction
// Start toggling the clock
function void start();
toggle = 1;
endfunction
// Stop toggling the clock
function void stop();
toggle = 0;
endfunction
// Stop running the clock loop (no new simulation events will be created)
function void kill();
alive = 0;
endfunction
// Start running the clock loop (new simulation events will be created)
function void revive();
alive = 1;
endfunction
// Asynchronously assert the reset signal and synchronously deassert it after
// "length" clock cycles.
task reset(int length = 8);
fork
begin
rst <= 1;
repeat (length) @(posedge clk);
rst <= 0;
end
join_none
// Make sure rst asserts before we return
wait(rst);
endtask : reset
// Assert reset
task set_reset();
rst <= 1'b1;
endtask : set_reset
// Deassert reset
task clr_reset();
rst <= 1'b0;
endtask : clr_reset
// Wait for num rising edges of the clock
task clk_wait_r(int num = 1);
repeat(num) @(posedge clk);
endtask
// Wait for num falling edges of the clock
task clkd_wait_f(int num = 1);
repeat(num) @(negedge clk);
endtask
//--------------------------
// Clock Generation Process
//--------------------------
initial begin : clock_block
// Toggle the clock in a loop
forever begin : clock_loop
#(low_time);
if (toggle) clk = 0;
#(high_time);
if (toggle) clk = 1;
wait (alive);
end
end
endmodule : sim_clock_gen
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
//
// Copyright 2019 Ettus Research, A National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: test_exec (Header)
//
// Description: Useful macros and definitions for PkgTestExec. This file should
// be included by modules that use PkgTestExec.
//
`ifndef TEST_EXEC_H
`define TEST_EXEC_H
//-----------------------------------------------------------------------------
// Simulation Timing
//-----------------------------------------------------------------------------
//
// In order for simulations to work correctly, it's important that that all
// modules use the same time unit and a compatible precision. Otherwise the
// times passed between the module and PkgTestExec may not be consistent.
//
//-----------------------------------------------------------------------------
timeunit 1ns;
timeprecision 1ps;
//-----------------------------------------------------------------------------
// Assertion Macros
//-----------------------------------------------------------------------------
//
// These are mirrors of the equivalently named class methods. These are
// re-implemented as macros here so that the correct line and file information
// gets reported by the simulator.
//
// NOTE: This assumes that there is a PkgTestExec object within scope of where
// the macro is used.
//
//-----------------------------------------------------------------------------
// To change the name of the TestExec object being used by the assertion
// macros, `define TEST_EXEC_OBJ before including this file and `undef it at
// the end of your testbench. Otherwise, it defaults to the shared object
// "PkgTestExec::test".
`ifndef TEST_EXEC_OBJ
`define TEST_EXEC_OBJ PkgTestExec::test
`endif
// Assert the given expression and call $fatal() if it fails.
//
// EXPR: The expression value to be asserted
// MESSAGE: String to report if the assertion fails
//
`define ASSERT_FATAL(EXPR, MESSAGE) \
begin \
`TEST_EXEC_OBJ.num_assertions++; \
assert (EXPR) else begin \
`TEST_EXEC_OBJ.test_status[`TEST_EXEC_OBJ.num_started] = 0; \
$fatal(1, MESSAGE); \
end \
end
// Assert the given expression and call $error() if it fails. Simulation
// will also be stopped (using $stop) if stop_on_error is true.
//
// EXPR: The expression value to be asserted
// MESSAGE: String to report if the assertion fails
//
`define ASSERT_ERROR(EXPR, MESSAGE) \
begin \
`TEST_EXEC_OBJ.num_assertions++; \
assert (EXPR) else begin \
`TEST_EXEC_OBJ.test_status[`TEST_EXEC_OBJ.num_started] = 0; \
$error(MESSAGE); \
if (`TEST_EXEC_OBJ.stop_on_error) $stop(1); \
end \
end
// Assert the given expression and call $warning() if it fails.
//
// EXPR: The expression value to be asserted
// MESSAGE: String to report if the assertion fails
//
`define ASSERT_WARNING(EXPR, MESSAGE) \
begin \
`TEST_EXEC_OBJ.num_assertions++; \
assert (EXPR) else begin \
$warning(MESSAGE); \
end \
end
// Assert the given expression and call $info() if it fails.
//
// EXPR: The expression value to be asserted
// MESSAGE: String to report if the assertion fails
//
`define ASSERT_INFO(EXPR, MESSAGE) \
begin \
assert (EXPR) else begin \
$info(MESSAGE) \
end \
end
`endif