aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/sim/rfnoc
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-01-23 16:10:22 -0800
committerMartin Braun <martin.braun@ettus.com>2020-01-28 09:35:36 -0800
commitbafa9d95453387814ef25e6b6256ba8db2df612f (patch)
tree39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/sim/rfnoc
parent3075b981503002df3115d5f1d0b97d2619ba30f2 (diff)
downloaduhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2
uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip
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>
Diffstat (limited to 'fpga/usrp3/sim/rfnoc')
-rw-r--r--fpga/usrp3/sim/rfnoc/Makefile.srcs27
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv474
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv232
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgChdrBfm.sv780
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgChdrUtils.sv276
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgRfnocBlockCtrlBfm.sv1074
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgRfnocItemUtils.sv215
-rw-r--r--fpga/usrp3/sim/rfnoc/PkgTestExec.sv297
-rw-r--r--fpga/usrp3/sim/rfnoc/sim_clock_gen.sv127
-rw-r--r--fpga/usrp3/sim/rfnoc/sim_rfnoc_lib.svh1023
-rw-r--r--fpga/usrp3/sim/rfnoc/test_exec.svh108
11 files changed, 4633 insertions, 0 deletions
diff --git a/fpga/usrp3/sim/rfnoc/Makefile.srcs b/fpga/usrp3/sim/rfnoc/Makefile.srcs
new file mode 100644
index 000000000..329ffbd27
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/Makefile.srcs
@@ -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 \
+))
diff --git a/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv b/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv
new file mode 100644
index 000000000..f68054dc7
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv
@@ -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
diff --git a/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv b/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv
new file mode 100644
index 000000000..8c88792c7
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv
@@ -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
diff --git a/fpga/usrp3/sim/rfnoc/PkgChdrBfm.sv b/fpga/usrp3/sim/rfnoc/PkgChdrBfm.sv
new file mode 100644
index 000000000..fd0bd0048
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgChdrBfm.sv
@@ -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
diff --git a/fpga/usrp3/sim/rfnoc/PkgChdrUtils.sv b/fpga/usrp3/sim/rfnoc/PkgChdrUtils.sv
new file mode 100644
index 000000000..95b9d5473
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgChdrUtils.sv
@@ -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
diff --git a/fpga/usrp3/sim/rfnoc/PkgRfnocBlockCtrlBfm.sv b/fpga/usrp3/sim/rfnoc/PkgRfnocBlockCtrlBfm.sv
new file mode 100644
index 000000000..8712884c0
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgRfnocBlockCtrlBfm.sv
@@ -0,0 +1,1074 @@
+//
+// Copyright 2019 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: PkgRfnocBlockCtrlBfm
+//
+// Description: This package includes high-level bus functional models (BFMs)
+// for communicating with RFNoC. This includes the following:
+//
+// - ChdrDataStreamBfm: Model for the AXIS CHDR interface of a Transport
+// Adapter or Stream Endpoint.
+//
+// - RegisterIfaceBfm: Model for the AXIS CTRL interface of a Stream Endpoint.
+//
+// - RfnocBlockCtrlBfm: Model for a software block controller, which includes
+// both a ChdrDataStreamBfm and a RegisterIfaceBfm.
+//
+
+
+//-----------------------------------------------------------------------------
+// SV Interface for the RFNoC Backend Iface
+//-----------------------------------------------------------------------------
+
+typedef struct packed {
+ bit [476:0] reserved0;
+ bit soft_chdr_rst;
+ bit soft_ctrl_rst;
+ bit flush_en;
+ bit [31:0] flush_timeout;
+} backend_config_v1_t;
+
+typedef struct packed {
+ bit [439:0] reserved0;
+ bit [5:0] mtu;
+ bit flush_done;
+ bit flush_active;
+ bit [31:0] noc_id;
+ bit [7:0] ctrl_max_async_msgs;
+ bit [5:0] ctrl_fifosize;
+ bit [5:0] num_data_o;
+ bit [5:0] num_data_i;
+ bit [5:0] proto_ver;
+} backend_status_v1_t;
+
+typedef union packed {
+ backend_config_v1_t v1;
+} backend_config_t;
+
+typedef union packed {
+ backend_status_v1_t v1;
+} backend_status_t;
+
+
+interface RfnocBackendIf(
+ input logic chdr_clk,
+ input logic ctrl_clk
+);
+ backend_config_t cfg;
+ backend_status_t sts;
+
+ modport master (
+ input chdr_clk,
+ input ctrl_clk,
+ output cfg,
+ input sts
+ );
+ modport slave (
+ input chdr_clk,
+ input ctrl_clk,
+ input cfg,
+ output sts
+ );
+endinterface : RfnocBackendIf
+
+
+//-----------------------------------------------------------------------------
+// RFNoC Block Controller Bus Functional Model
+//-----------------------------------------------------------------------------
+
+package PkgRfnocBlockCtrlBfm;
+
+ import PkgChdrUtils::*;
+ import PkgChdrBfm::*;
+ import PkgAxisCtrlBfm::*;
+ import PkgRfnocItemUtils::*;
+
+
+ typedef struct packed {
+ chdr_vc_t vc;
+ logic eob;
+ logic eov;
+ logic has_time;
+ chdr_word_t timestamp;
+ } packet_info_t;
+
+
+ //---------------------------------------------------------------------------
+ // CHDR Stream BFM
+ //---------------------------------------------------------------------------
+ //
+ // This class models an AXIS CHDR interface, such as that on a Transport
+ // Adapter or in a Stream Endpoint.
+ //
+ //---------------------------------------------------------------------------
+
+ class ChdrDataStreamBfm #(CHDR_W = 64) extends ChdrBfm #(CHDR_W);
+ chdr_seq_num_t seq_num; // Sequence number
+
+ protected int max_payload_length; // Maximum number of payload bytes per packet
+ protected int ticks_per_word; // Timestamp increment per CHDR_W sized word
+
+
+ // Class constructor to create a new BFM instance.
+ //
+ // m_chdr: Interface for the master connection (BFM's CHDR output)
+ // s_chdr: Interface for the slave connection (BFM's CHDR input)
+ //
+ function new(
+ virtual AxiStreamIf #(CHDR_W).master m_chdr,
+ virtual AxiStreamIf #(CHDR_W).slave s_chdr,
+ input int max_payload_length = 2**$bits(chdr_length_t),
+ input int ticks_per_word = CHDR_W/32
+ );
+ super.new(m_chdr, s_chdr);
+ this.seq_num = 0;
+ set_max_payload_length(max_payload_length);
+ set_ticks_per_word(ticks_per_word);
+ endfunction : new
+
+
+ // Set the maximum payload size for packets. This value is used to split
+ // large send requests across multiple packets.
+ //
+ // max_length: Maximum payload length in bytes for each packet
+ //
+ function void set_max_payload_length(int max_payload_length);
+ assert (max_payload_length % (CHDR_W/8) == 0) else begin
+ $fatal(1, "ChdrDataStreamBfm::set_max_payload_length: max_payload_length must be a multiple of CHDR_W in bytes");
+ end
+ this.max_payload_length = max_payload_length;
+ endfunction
+
+
+ // Return the maximum payload size for packets. This value is used to split
+ // large send requests across multiple packets.
+ function int get_max_payload_length();
+ return max_payload_length;
+ endfunction
+
+
+ // Set the timestamp ticks per CHDR_W sized word.
+ //
+ // ticks_per_word: Amount to increment the timestamp per CHDR_W sized word
+ //
+ function void set_ticks_per_word(int ticks_per_word);
+ this.ticks_per_word = ticks_per_word;
+ endfunction
+
+
+ // Return the timestamp ticks per CHDR_W sized word.
+ function int get_ticks_per_word();
+ return ticks_per_word;
+ endfunction
+
+
+ // Send a CHDR data packet.
+ //
+ // data: Data words to insert into the CHDR packet.
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ // metadata: Metadata words to insert into the CHDR packet. Omit this
+ // argument (or set to an empty array) to not include
+ // metadata.
+ // pkt_info: Data structure containing packet header information.
+ //
+ task send (
+ input chdr_word_t data[$],
+ input int data_bytes = -1,
+ input chdr_word_t metadata[$] = {},
+ input packet_info_t pkt_info = 0
+ );
+ ChdrPacket chdr_packet;
+ chdr_header_t chdr_header;
+
+ // Build packet
+ chdr_packet = new();
+ chdr_header = '{
+ vc : pkt_info.vc,
+ eob : pkt_info.eob,
+ eov : pkt_info.eov,
+ seq_num : seq_num++,
+ pkt_type : pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS,
+ dst_epid : dst_epid,
+ default : 0
+ };
+ chdr_packet.write_raw(chdr_header, data, metadata, pkt_info.timestamp, data_bytes);
+
+ // Send the packet
+ put_chdr(chdr_packet);
+ endtask : send
+
+
+ // Send data as one or more CHDR data packets. The input data and metadata
+ // is automatically broken into max_payload_length'd packets. If multiple
+ // packets are needed, EOB and EOV are only applied to the last packet.
+ //
+ // data: Data words to insert into the CHDR packet.
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ // metadata: Metadata words to insert into the CHDR packet. Omit this
+ // argument (or set to an empty array) to not include
+ // metadata.
+ // pkt_info: Data structure containing packet header information.
+ //
+ task send_packets (
+ input chdr_word_t data[$],
+ input int data_bytes = -1,
+ input chdr_word_t metadata[$] = {},
+ input packet_info_t pkt_info = 0
+ );
+ ChdrPacket chdr_packet;
+ chdr_header_t chdr_header;
+ chdr_pkt_type_t pkt_type;
+ chdr_word_t timestamp;
+ int num_pkts;
+ int payload_length;
+ int first_dword, last_dword;
+ int first_mword, last_mword;
+ bit eob, eov;
+ chdr_word_t temp_data[$];
+ chdr_word_t temp_mdata[$];
+
+ num_pkts = $ceil(real'(data.size()*($bits(chdr_word_t)/8)) / max_payload_length);
+ pkt_type = pkt_info.has_time ? CHDR_DATA_WITH_TS : CHDR_DATA_NO_TS;
+ timestamp = pkt_info.timestamp;
+
+ // Make sure there's not too much metadata for this number of packets
+ assert(metadata.size()*$bits(chdr_word_t) < num_pkts * 2**$bits(chdr_num_mdata_t) * CHDR_W) else
+ $fatal(1, "ChdrDataStreamBfm::send: Too much metadata for this send request");
+
+ // Send the data, one packet at a time.
+ for (int i = 0; i < num_pkts; i++) begin
+ chdr_packet = new();
+
+ // Figure out which data chunk to send next
+ if (i == num_pkts-1) begin
+ // The last packet, which may or may not be full-sized
+ eob = pkt_info.eob;
+ eov = pkt_info.eov;
+ payload_length = (data_bytes < 0) ? data_bytes : data_bytes % max_payload_length;
+ first_dword = i*max_payload_length/($bits(chdr_word_t)/8);
+ last_dword = data.size()-1;
+ first_mword = i*(2**$bits(chdr_num_mdata_t) * CHDR_W / $bits(chdr_word_t));
+ last_mword = metadata.size()-1;
+ end else begin
+ // A full-sized packet, not the last
+ eob = 1'b0;
+ eov = 1'b0;
+ payload_length = max_payload_length;
+ first_dword = (i+0)*max_payload_length / ($bits(chdr_word_t)/8);
+ last_dword = (i+1)*max_payload_length / ($bits(chdr_word_t)/8) - 1;
+ first_mword = (i+0)*(2**$bits(chdr_num_mdata_t) * CHDR_W / $bits(chdr_word_t));
+ last_mword = (i+1)*(2**$bits(chdr_num_mdata_t) * CHDR_W / $bits(chdr_word_t)) - 1;
+ last_mword = last_mword > metadata.size() ? metadata.size() : last_mword;
+ end
+
+ // Build the packet
+ chdr_header = '{
+ vc : pkt_info.vc,
+ eob : eob,
+ eov : eov,
+ seq_num : seq_num++,
+ pkt_type : pkt_type,
+ dst_epid : dst_epid,
+ default : 0
+ };
+
+ // Copy region of data and metadata to be sent in next packet
+ temp_data = data[first_dword : last_dword];
+ if (first_mword < metadata.size()) temp_mdata = metadata[first_mword : last_mword];
+ else temp_mdata = {};
+
+ // Build the packet
+ chdr_packet.write_raw(
+ chdr_header,
+ temp_data,
+ temp_mdata,
+ timestamp,
+ payload_length
+ );
+
+ // Send the packet
+ put_chdr(chdr_packet);
+
+ // Update timestamp for next packet (in case this is not the last)
+ timestamp += max_payload_length/(CHDR_W/8) * ticks_per_word;
+ end
+ endtask : send_packets
+
+
+ // Receive a CHDR data packet and extract its contents.
+ //
+ // data: Data words from the received CHDR packet.
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ // metadata: Metadata words from the received CHDR packet. This
+ // will be an empty array if there was no metadata.
+ // pkt_info: Data structure to receive packet header information.
+ //
+ task recv_adv (
+ output chdr_word_t data[$],
+ output int data_bytes,
+ output chdr_word_t metadata[$],
+ output packet_info_t pkt_info
+ );
+ ChdrPacket chdr_packet;
+ get_chdr(chdr_packet);
+
+ data = chdr_packet.data;
+ data_bytes = chdr_packet.data_bytes();
+ metadata = chdr_packet.metadata;
+ pkt_info.timestamp = chdr_packet.timestamp;
+ pkt_info.vc = chdr_packet.header.vc;
+ pkt_info.eob = chdr_packet.header.eob;
+ pkt_info.eov = chdr_packet.header.eov;
+ pkt_info.has_time = chdr_packet.header.pkt_type == CHDR_DATA_WITH_TS ? 1 : 0;
+ endtask : recv_adv
+
+
+ // Receive a CHDR data packet and extract the data. Any metadata or
+ // timestamp, if present, are discarded.
+ //
+ // data: Data words from the received CHDR packet.
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ //
+ task recv(output chdr_word_t data[$], output int data_bytes);
+ ChdrPacket chdr_packet;
+ get_chdr(chdr_packet);
+ data = chdr_packet.data;
+ data_bytes = chdr_packet.data_bytes();
+ endtask : recv
+
+ endclass : ChdrDataStreamBfm
+
+
+
+ //---------------------------------------------------------------------------
+ // CTRL Stream BFM
+ //---------------------------------------------------------------------------
+ //
+ // This class models an AXIS CTRL interface, such as that in a Stream
+ // Endpoint.
+ //
+ //---------------------------------------------------------------------------
+
+ class RegisterIfaceBfm extends AxisCtrlBfm;
+ ctrl_port_t dst_port;
+ ctrl_port_t src_port;
+ ctrl_seq_num_t seq_num;
+
+ // Class constructor to create a new BFM instance.
+ //
+ // m_chdr: Interface for the master connection (BFM's AXIS output)
+ // s_chdr: Interface for the slave connection (BFM's AXIS input)
+ // src_port: Source port to use in generated control packets
+ //
+ function new(
+ virtual AxiStreamIf #(32).master m_chdr,
+ virtual AxiStreamIf #(32).slave s_chdr,
+ ctrl_port_t dst_port,
+ ctrl_port_t src_port
+ );
+ super.new(m_chdr, s_chdr);
+ this.dst_port = dst_port;
+ this.src_port = src_port;
+ this.seq_num = '0;
+ endfunction : new
+
+
+ // Send an AXIS-Ctrl read request packet and get the response.
+ //
+ // addr: Address for the read request
+ // word: Data word that was returned in response to the read
+ //
+ task reg_read (
+ input ctrl_address_t addr,
+ output ctrl_word_t word
+ );
+ AxisCtrlPacket ctrl_packet;
+
+ // Create the AXIS-Ctrl packet
+ ctrl_packet = new();
+ ctrl_packet.header = '{
+ seq_num : seq_num++,
+ num_data : 1,
+ src_port : src_port,
+ dst_port : dst_port,
+ default : 0
+ };
+ ctrl_packet.op_word = '{
+ op_code : CTRL_OP_READ,
+ byte_enable : ~0,
+ address : addr,
+ default : 0
+ };
+ ctrl_packet.data = { 0 };
+
+ // Send the control packet and get the response
+ put_ctrl(ctrl_packet);
+ get_ctrl(ctrl_packet);
+ word = ctrl_packet.data[0];
+
+ assert(ctrl_packet.header.is_ack == 1 &&
+ ctrl_packet.op_word.status == CTRL_STS_OKAY) else begin
+ $fatal(1, "RegisterIfaceBfm::reg_read: Did not receive CTRL_STS_OKAY status");
+ end
+ endtask : reg_read
+
+
+ // Send an AXIS-Ctrl write request packet and get the response.
+ //
+ // addr: Address for the write request
+ // word: Data word to write
+ //
+ task reg_write (
+ ctrl_address_t addr,
+ ctrl_word_t word
+ );
+ AxisCtrlPacket ctrl_packet;
+
+ // Create the AXIS-Ctrl packet
+ ctrl_packet = new();
+ ctrl_packet.header = '{
+ seq_num : seq_num++,
+ num_data : 1,
+ src_port : src_port,
+ dst_port : dst_port,
+ default : 0
+ };
+ ctrl_packet.op_word = '{
+ op_code : CTRL_OP_WRITE,
+ byte_enable : ~0,
+ address : addr,
+ default : 0
+ };
+
+ // Send the packet and get the response
+ ctrl_packet.data = { word };
+ put_ctrl(ctrl_packet);
+ get_ctrl(ctrl_packet);
+ word = ctrl_packet.data[0];
+
+ assert(ctrl_packet.header.is_ack == 1 &&
+ ctrl_packet.op_word.status == CTRL_STS_OKAY) else begin
+ $fatal(1, "RegisterIfaceBfm::reg_write: Did not receive CTRL_STS_OKAY status");
+ end
+ endtask : reg_write
+
+ endclass : RegisterIfaceBfm
+
+
+ //---------------------------------------------------------------------------
+ // Block Controller BFM
+ //---------------------------------------------------------------------------
+ //
+ // This class models a block controller in software
+ //
+ //---------------------------------------------------------------------------
+
+ class RfnocBlockCtrlBfm #(CHDR_W = 64);
+
+ local virtual RfnocBackendIf.master backend;
+ local RegisterIfaceBfm ctrl;
+ local ChdrDataStreamBfm #(CHDR_W) m_data[$];
+ local ChdrDataStreamBfm #(CHDR_W) s_data[$];
+ local bit running;
+
+ localparam CMD_PROP_CYC = 5;
+
+ // Class constructor to create a new BFM instance.
+ //
+ // backend: Interface for the backend signals of a block
+ // m_ctrl: Interface for the CTRL master connection (EP's AXIS-Ctrl output)
+ // s_ctrl: Interface for the CTRL slave connection (EP's AXIS-Ctrl input)
+ // dst_port: Destination port to use in generated control packets
+ // src_port: Source port to use in generated control packets
+ //
+ function new(
+ virtual RfnocBackendIf.master backend,
+ virtual AxiStreamIf #(32).master m_ctrl,
+ virtual AxiStreamIf #(32).slave s_ctrl,
+ input ctrl_port_t dst_port = 10'd2,
+ input ctrl_port_t src_port = 10'd1
+ );
+ this.backend = backend;
+ this.ctrl = new(m_ctrl, s_ctrl, dst_port, src_port);
+ this.running = 0;
+ endfunction : new
+
+ // Add a master data port. This should connect to a DUT slave input.
+ //
+ // m_chdr: Virtual master interface to connect new port to.
+ // max_payload_length: Maximum payload length to create when building
+ // packets from data.
+ // ticks_per_word: Number of timebase clock ticks to increment per
+ // CHDR word.
+ //
+ function int add_master_data_port(
+ virtual AxiStreamIf #(CHDR_W).master m_chdr,
+ int max_payload_length = 2**$bits(chdr_length_t),
+ int ticks_per_word = CHDR_W/32
+ );
+ ChdrDataStreamBfm #(CHDR_W) bfm = new(m_chdr, null, max_payload_length, ticks_per_word);
+ m_data.push_back(bfm);
+ return m_data.size() - 1;
+ endfunction : add_master_data_port
+
+ // Add a slave data port. This should connect to a DUT master output.
+ //
+ // s_chdr: Virtual slave interface to connect new port to
+ //
+ function int add_slave_data_port(
+ virtual AxiStreamIf #(CHDR_W).slave s_chdr
+ );
+ ChdrDataStreamBfm #(CHDR_W) bfm = new(null, s_chdr);
+ s_data.push_back(bfm);
+ return s_data.size() - 1;
+ endfunction : add_slave_data_port
+
+ // Add a master data port. This is equivalent to add_master_data_port()
+ // except it accepts a port number and it waits until the preceding ports
+ // are connected to ensure that ports are connected in the correct order.
+ //
+ // port_num: The port number to which m_chdr should be connected
+ // m_chdr: Master CHDR interface to connect to the port
+ // max_payload_length: Maximum payload length to create when building
+ // packets from data.
+ // ticks_per_word: Number of timebase clock ticks to increment per
+ // CHDR word.
+ //
+ task connect_master_data_port(
+ int port_num,
+ virtual AxiStreamIf #(CHDR_W).master m_chdr,
+ int max_payload_length = 2**$bits(chdr_length_t),
+ int ticks_per_word = CHDR_W/32
+ );
+ ChdrDataStreamBfm #(CHDR_W) bfm = new(m_chdr, null, max_payload_length, ticks_per_word);
+ wait (m_data.size() == port_num);
+ m_data.push_back(bfm);
+ endtask : connect_master_data_port
+
+ // Add a slave data port. This is equivalent to add_slave_data_port()
+ // except it accepts a port number and it waits until the preceding ports
+ // are connected to ensure that ports are connected in the correct order.
+ //
+ // port_num: The port number to which m_chdr should be connected
+ // s_chdr: Master CHDR interface to connect to the port
+ //
+ task connect_slave_data_port(
+ int port_num,
+ virtual AxiStreamIf #(CHDR_W).slave s_chdr
+ );
+ ChdrDataStreamBfm #(CHDR_W) bfm = new(null, s_chdr);
+ wait (s_data.size() == port_num);
+ s_data.push_back(bfm);
+ endtask : connect_slave_data_port
+
+ // Start the data and control BFM's processes running.
+ task run();
+ assert (backend.sts.v1.proto_ver == 1) else begin
+ $fatal(1, "The connected block has an incompatible backend interface");
+ end
+ if (!running) begin
+ ctrl.run();
+ foreach (m_data[i])
+ m_data[i].run();
+ foreach (s_data[i])
+ s_data[i].run();
+ running = 1;
+ end
+ endtask : run
+
+ // Return a handle to the control BFM
+ function RegisterIfaceBfm get_ctrl_bfm();
+ return ctrl;
+ endfunction : get_ctrl_bfm
+
+ // Return a handle to the indicated master port BFM
+ function ChdrDataStreamBfm #(CHDR_W) get_master_data_bfm(int port);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+ return m_data[port];
+ endfunction : get_master_data_bfm
+
+ // Return a handle to the indicated slave port BFM
+ function ChdrDataStreamBfm #(CHDR_W) get_slave_data_bfm(int port);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+ return s_data[port];
+ endfunction : get_slave_data_bfm
+
+ // Set the maximum payload size for packets. This value is used to split
+ // large send requests across multiple packets.
+ //
+ // port: Master port whose maximum length you want to set
+ // max_length: Maximum payload length in bytes for each packet
+ //
+ function void set_max_payload_length(int port, int max_length);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+ m_data[port].set_max_payload_length(max_length);
+ endfunction
+
+ // Return the maximum payload size for packets. This value is used to split
+ // large send requests across multiple packets.
+ //
+ // port: Master port whose maximum length you want to get
+ //
+ function int get_max_payload_length(int port);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+ return m_data[port].get_max_payload_length();
+ endfunction
+
+ // Set the timestamp ticks per CHDR_W sized word.
+ //
+ // port: Master port whose timestamp increment you want to set
+ // ticks_per_word: Amount to increment the timestamp per CHDR_W sized word
+ //
+ function void set_ticks_per_word(int port, int ticks_per_word);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+ m_data[port].set_ticks_per_word(ticks_per_word);
+ endfunction
+
+ // Return the timestamp ticks per CHDR_W sized word.
+ //
+ // port: Master port whose timestamp increment you want to get
+ //
+ function int get_ticks_per_word(int port);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+ return m_data[port].get_ticks_per_word();
+ endfunction
+
+ // Get static info about the block
+ function logic [7:0] get_proto_ver();
+ return backend.sts.v1.proto_ver;
+ endfunction : get_proto_ver
+
+ function logic [31:0] get_noc_id();
+ return backend.sts.v1.noc_id;
+ endfunction : get_noc_id
+
+ function logic [5:0] get_num_data_i();
+ return backend.sts.v1.num_data_i;
+ endfunction : get_num_data_i
+
+ function logic [5:0] get_num_data_o();
+ return backend.sts.v1.num_data_o;
+ endfunction : get_num_data_o
+
+ function logic [5:0] get_ctrl_fifosize();
+ return backend.sts.v1.ctrl_fifosize;
+ endfunction : get_ctrl_fifosize
+
+ function logic [5:0] get_mtu();
+ return backend.sts.v1.mtu;
+ endfunction : get_mtu
+
+ // Soft-Reset the CHDR path
+ //
+ // rst_cyc: Number of cycles to wait for reset completion
+ //
+ task reset_chdr(input int rst_cyc = 100);
+ assert (running) else begin
+ $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
+ end
+
+ // Assert soft_chdr_rst then wait
+ // Note: soft_chdr_rst must be driven in the ctrl_clk domain
+ @(posedge backend.ctrl_clk);
+ backend.cfg.v1.soft_chdr_rst = 1;
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ backend.cfg.v1.soft_chdr_rst = 0;
+ @(posedge backend.ctrl_clk);
+ repeat (rst_cyc) @(posedge backend.ctrl_clk);
+ endtask : reset_chdr
+
+ // Soft-Reset the Control path
+ //
+ // rst_cyc: Number of cycles to wait for reset completion
+ //
+ task reset_ctrl(input int rst_cyc = 100);
+ assert (running) else begin
+ $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
+ end
+
+ // Assert soft_ctrl_rst then wait
+ @(posedge backend.ctrl_clk);
+ backend.cfg.v1.soft_ctrl_rst = 1;
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ backend.cfg.v1.soft_ctrl_rst = 0;
+ repeat (rst_cyc) @(posedge backend.ctrl_clk);
+ endtask : reset_ctrl
+
+ // Flush the data ports of the block
+ //
+ // idle_cyc: Number of idle cycles before done is asserted
+ //
+ task flush(input logic [31:0] idle_cyc = 100);
+ assert (running) else begin
+ $fatal(1, "Cannot call flush until RfnocBlockCtrlBfm is running");
+ end
+
+ // Set flush timeout then wait
+ backend.cfg.v1.flush_timeout = idle_cyc;
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
+ // Start flush then wait for done
+ @(posedge backend.ctrl_clk);
+ backend.cfg.v1.flush_en = 1;
+ @(posedge backend.ctrl_clk);
+ while (~backend.sts.v1.flush_done) @(posedge backend.ctrl_clk);
+ // Deassert flush then wait
+ backend.cfg.v1.flush_en = 0;
+ while (backend.sts.v1.flush_active) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
+ endtask : flush
+
+ // Flush the data ports of the block then reset the CHDR
+ // path, wait then reset the ctrl path
+ //
+ // idle_cyc: Number of idle cycles before done is asserted
+ // chdr_rst_cyc: Number of cycles to wait for chdr_rst completion
+ // ctrl_rst_cyc: Number of cycles to wait for ctrl_rst completion
+ //
+ task flush_and_reset(
+ input logic [31:0] idle_cyc = 100,
+ input int chdr_rst_cyc = 100,
+ input int ctrl_rst_cyc = 100
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call flush_and_reset until RfnocBlockCtrlBfm is running");
+ end
+
+ // Set flush timeout then wait
+ backend.cfg.v1.flush_timeout = idle_cyc;
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
+ // Start flush then wait for done
+ @(posedge backend.ctrl_clk);
+ backend.cfg.v1.flush_en = 1;
+ @(posedge backend.ctrl_clk);
+ while (~backend.sts.v1.flush_done) @(posedge backend.ctrl_clk);
+ // Assert chdr_rst then wait
+ reset_chdr(chdr_rst_cyc);
+ // Assert ctrl_rst then wait
+ reset_ctrl(ctrl_rst_cyc);
+ // Deassert flush then wait
+ backend.cfg.v1.flush_en = 0;
+ while (backend.sts.v1.flush_active) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.ctrl_clk);
+ repeat (CMD_PROP_CYC) @(posedge backend.chdr_clk);
+ endtask : flush_and_reset
+
+
+ // Send a CHDR data packet out the CHDR data interface.
+ //
+ // port: Port to send the CHDR packet on.
+ // data: Data words to insert into the CHDR packet.
+ // data_bytes: Size of data in bytes. If omitted or -1, data_bytes will
+ // be calculated based on the number of words in data.
+ // metadata: Metadata words to insert into the CHDR packet. Omit this
+ // argument (or set to an empty array) to not include
+ // metadata.
+ // pkt_info: Data structure containing packet header information.
+ //
+ task send(
+ input int port,
+ input chdr_word_t data[$],
+ input int data_bytes = -1,
+ input chdr_word_t metadata[$] = {},
+ input packet_info_t pkt_info = 0
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call send until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+
+ m_data[port].send(data, data_bytes, metadata, pkt_info);
+ endtask : send
+
+
+ // Send data as one or more CHDR data packets out the CHDR data interface.
+ //
+ // port: Port to send the CHDR packet(s) on.
+ // data: Data words to insert into the CHDR packet.
+ // data_bytes: Size of data in bytes. If omitted or -1, data_bytes will
+ // be calculated based on the number of words in data.
+ // metadata: Metadata words to insert into the CHDR packet(s). Omit
+ // this argument (or set to an empty array) to not include
+ // metadata.
+ // pkt_info: Data structure containing packet header information.
+ //
+ task send_packets(
+ input int port,
+ input chdr_word_t data[$],
+ input int data_bytes = -1,
+ input chdr_word_t metadata[$] = {},
+ input packet_info_t pkt_info = 0
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call send_packets until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+
+ m_data[port].send_packets(data, data_bytes, metadata, pkt_info);
+ endtask : send_packets
+
+
+ // Receive a CHDR data packet on the CHDR data interface and extract its
+ // contents.
+ //
+ // port: Port to receive the CHDR packet from.
+ // data: Data words from the received CHDR packet.
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ // metadata: Metadata words from the received CHDR packet. This
+ // will be an empty array if there was no metadata.
+ // pkt_info: Data structure to receive packet header information.
+ //
+ task recv_adv(
+ input int port,
+ output chdr_word_t data[$],
+ output int data_bytes,
+ output chdr_word_t metadata[$],
+ output packet_info_t pkt_info
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call recv_adv until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ s_data[port].recv_adv(data, data_bytes, metadata, pkt_info);
+ endtask : recv_adv
+
+
+ // Receive a CHDR data packet on the CHDR data interface and extract the
+ // data. Any metadata or timestamp, if present, are discarded.
+ //
+ // port: Port number for the block to receive from
+ // data: Data words from the received CHDR packet
+ // data_bytes: The number of data bytes in the CHDR packet. This
+ // is useful if the data is not a multiple of the
+ // chdr_word_t size.
+ //
+ task recv(
+ input int port,
+ output chdr_word_t data[$],
+ output int data_bytes
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call recv until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ s_data[port].recv(data, data_bytes);
+ endtask : recv
+
+
+ // Transmit a raw CHDR packet.
+ //
+ // port: Port number on which to transmit the packet
+ // packet: Packet to transmit
+ //
+ task put_chdr(
+ input int port,
+ input ChdrPacket #(CHDR_W) packet
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call put_chdr until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+
+ m_data[port].put_chdr(packet);
+ endtask : put_chdr
+
+
+ // Receive a raw CHDR packet.
+ //
+ // port: Port number on which to receive the packet
+ // packet: Data structure to store received packet
+ //
+ task get_chdr(
+ input int port,
+ output ChdrPacket #(CHDR_W) packet
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call get_chdr until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ s_data[port].get_chdr(packet);
+ endtask : get_chdr
+
+
+ // Receive a raw CHDR packet, but don't remove it from the receive queue.
+ //
+ // port: Port number on which to peek
+ // packet: Data structure to store received packet
+ //
+ task peek_chdr(
+ input int port,
+ output ChdrPacket #(CHDR_W) packet
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call peek_chdr until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ s_data[port].peek_chdr(packet);
+ endtask : peek_chdr
+
+
+ // Return the number of packets available in the receive queue for the
+ // given port.
+ //
+ // port: Port for which to get the number of received packets
+ //
+ function int num_received(int port);
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ return s_data[port].num_received();
+ endfunction
+
+
+ // Wait until packets have completed transmission.
+ //
+ // port: Port for which to wait
+ // num: Number of packets to wait for. Set to -1 or omit the argument
+ // to wait for all currently queued packets to complete
+ // transmission.
+ //
+ task wait_complete(int port, int num = -1);
+ assert (running) else begin
+ $fatal(1, "Cannot call wait_complete until RfnocBlockCtrlBfm is running");
+ end
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+
+ m_data[port].wait_complete(num);
+ endtask
+
+
+ // Set the stall probability for the indicated slave port.
+ //
+ // port: Port for which to set the probability
+ // stall_prob: Probability as a percentage (0-100)
+ //
+ function void set_slave_stall_prob(int port, int stall_prob);
+ assert (port >= 0 && port < s_data.size()) else begin
+ $fatal(1, "Invalid slave port number");
+ end
+
+ s_data[port].set_slave_stall_prob(stall_prob);
+ endfunction
+
+
+ // Set the stall probability for the indicated master port.
+ //
+ // port: Port for which to set the probability
+ // stall_prob: Probability as a percentage (0-100)
+ //
+ function void set_master_stall_prob(int port, int stall_prob);
+ assert (port >= 0 && port < m_data.size()) else begin
+ $fatal(1, "Invalid master port number");
+ end
+
+ m_data[port].set_master_stall_prob(stall_prob);
+ endfunction
+
+
+ // Send a read request packet on the AXIS-Ctrl interface and get the
+ // response.
+ //
+ // addr: Address for the read request
+ // word: Data word that was returned in response to the read
+ //
+ task reg_read(
+ input ctrl_address_t addr,
+ output ctrl_word_t word
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call reg_read until RfnocBlockCtrlBfm is running");
+ end
+
+ ctrl.reg_read(addr, word);
+ endtask : reg_read
+
+
+ // Send a a write request packet on the AXIS-Ctrl interface and get the
+ // response.
+ //
+ // addr: Address for the write request
+ // word: Data word to write
+ //
+ task reg_write(
+ ctrl_address_t addr,
+ ctrl_word_t word
+ );
+ assert (running) else begin
+ $fatal(1, "Cannot call reg_write until RfnocBlockCtrlBfm is running");
+ end
+
+ ctrl.reg_write(addr, word);
+ endtask : reg_write
+
+ // Compare data vectors
+ static function bit compare_data(
+ input chdr_word_t lhs[$],
+ input chdr_word_t rhs[$],
+ input int bytes = -1
+ );
+ int bytes_left;
+ if (lhs.size() != rhs.size()) return 0;
+ bytes_left = (bytes > 0) ? bytes : ((lhs.size()*$size(chdr_word_t))/8);
+ for (int i = 0; i < lhs.size(); i++) begin
+ chdr_word_t mask = {$size(chdr_word_t){1'b1}};
+ if (bytes_left < $size(chdr_word_t)/8) begin
+ mask = (1 << (bytes_left * 8)) - 1;
+ end else if (bytes_left < 0) begin
+ return 1;
+ end
+ if ((lhs[i] & mask) != (rhs[i] & mask)) return 0;
+ end
+ return 1;
+ endfunction : compare_data
+
+ endclass : RfnocBlockCtrlBfm
+
+
+endpackage : PkgRfnocBlockCtrlBfm
diff --git a/fpga/usrp3/sim/rfnoc/PkgRfnocItemUtils.sv b/fpga/usrp3/sim/rfnoc/PkgRfnocItemUtils.sv
new file mode 100644
index 000000000..2c671ad93
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgRfnocItemUtils.sv
@@ -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
diff --git a/fpga/usrp3/sim/rfnoc/PkgTestExec.sv b/fpga/usrp3/sim/rfnoc/PkgTestExec.sv
new file mode 100644
index 000000000..ba4455912
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/PkgTestExec.sv
@@ -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
+
diff --git a/fpga/usrp3/sim/rfnoc/sim_clock_gen.sv b/fpga/usrp3/sim/rfnoc/sim_clock_gen.sv
new file mode 100644
index 000000000..ce7d4880a
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/sim_clock_gen.sv
@@ -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 \ No newline at end of file
diff --git a/fpga/usrp3/sim/rfnoc/sim_rfnoc_lib.svh b/fpga/usrp3/sim/rfnoc/sim_rfnoc_lib.svh
new file mode 100644
index 000000000..76fa54614
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/sim_rfnoc_lib.svh
@@ -0,0 +1,1023 @@
+//
+// Copyright 2015 Ettus Research
+//
+// RFNoC sim lib's main goals are to:
+// - Provide macros so the user can easily simulate a RFNoC design
+// - Provide a fully compliant (i.e. has packetization, flow control) RFNoC interface
+// to the user's test bench.
+// The RFNoC interface comes from a Export I/O RFNoC block (noc_block_tb) that exposes
+// CVITA and AXI-Stream interfaces interfaces via the block's top level port list.
+//
+// Block Diagram:
+// ------------------------------------------------------------- -----------------------------
+// | AXI Crossbar | | bus_clk, bus_rst generator |
+// ------------------------------------------------------------- -----------------------------
+// |^ |^ |^
+// v| v| || -----------------------------
+// ------------- -------------------------------------- || | ce_clk, ce_rst generator |
+// | | | noc_block_tb | v| -----------------------------
+// | User DUT | | (Export I/O RFNoC Block) | tb_config
+// | RFNoC Block | | | (rfnoc_block_streamer)
+// | | | ---------------------------------- |
+// ------------- | | NoC Shell | |
+// | | | |
+// | | (replicated x NUM_STREAMS) | |
+// | | block port cmdout ackin | |
+// | ---------------------------------- |
+// | |^ ^ | |
+// | |'-----------. | | |
+// | v | | | |
+// | ------- ------- | | | // Demux / Mux allows using both AXI Wrapper
+// | / Demux \ / Mux \ | | | // and direct CVITA paths to NoC Shell
+// | --------- --------- | | | // simultaneously
+// | | | ^ ^ | | |
+// | | | .------' | | | |
+// | | | | | | | |
+// | | --)------ | | | |
+// | v | |.---' | | |
+// | ------------- || | | |
+// | | AXI Wrapper | || | | |
+// | ------------- || | | |
+// -------|^--------||-------|-----|-----
+// || || | |
+// v| || | v
+// --------------------------------------
+// | tb_streamer |
+// | (rfnoc_block_streamer) |
+// | |
+// | Interface to provide user with easy |
+// | to use tasks for sending and |
+// | receiving data from RFNoC blocks in |
+// | the simulation. |
+// --------------------------------------
+// |^
+// v|
+// --------------------------------------
+// | User Testbench |
+// --------------------------------------
+//
+//
+// Usage: Include sim_rfnoc_lib.sv, setup prerequisites with `RFNOC_SIM_INIT(), and add RFNoC blocks
+// with `RFNOC_ADD_BLOCK(). Connect RFNoC blocks into a flow graph using `RFNOC_CONNECT().
+//
+// Example:
+// `include "sim_rfnoc_lib.svh"
+// module rfnoc_testbench();
+// localparam BUS_CLK_PERIOD = $ceil(1e9/166.67e6); // Bus clk at 166 MHz
+// localparam CE_CLK_PERIOD = $ceil(1e9/200e6); // Computation Engine clk at 200 MHz
+// localparam NUM_CE = 2; // Two computation engines
+// localparam NUM_STREAMS = 1; // One test bench stream
+// `RFNOC_SIM_INIT(NUM_CE, NUM_STREAMS, BUS_CLK_PERIOD, CE_CLK_PERIOD);
+// `RFNOC_ADD_BLOCK(noc_block_fir,0); // Instantiate FIR and connect to crossbar port 0
+// `RFNOC_ADD_BLOCK(noc_block_fft,1); // Instantiate FFT and connect to crossbar port 1
+// initial begin
+// // Note the special block 'noc_block_tb' which is added in `RFNOC_SIM_INIT()
+// `RFNOC_CONNECT(noc_block_tb,noc_block_fir,SC16,256); // Connect test bench to FIR
+// `RFNOC_CONNECT(noc_block_fir,noc_block_fft,SC16,256); // Connect FIR to FFT. Packet size 256, stream's data type SC16
+// end
+// endmodule
+//
+// Warning: Most of the macros create specifically named signals used by other macros
+`ifndef INCLUDED_RFNOC_SIM_LIB
+`define INCLUDED_RFNOC_SIM_LIB
+
+`include "sim_clks_rsts.vh"
+`include "sim_cvita_lib.svh"
+`include "sim_set_rb_lib.svh"
+`include "noc_shell_regs.vh"
+`include "data_types.vh"
+
+// Interface with tasks for users to send and receive packets on CVITA and AXI-Stream interfaces
+// Most test benches only need to use send(), recv(), write_user_reg(), read_user_reg()
+interface rfnoc_block_streamer #(
+ parameter AXIS_DWIDTH = 32,
+ parameter CVITA_DWIDTH = 64,
+ parameter SR_AWIDTH = 8,
+ parameter SR_DWIDTH = 32,
+ parameter RB_AWIDTH = 8,
+ parameter RB_DWIDTH = 64,
+ parameter STREAM_DWIDTH = 32, // Width used with send() / recv() calls
+ parameter NUM_STREAMS = 1 // Limited to 16
+)(
+ input clk
+);
+
+ settings_bus_slave #(
+ .SR_AWIDTH(SR_AWIDTH), .SR_DWIDTH(SR_DWIDTH),
+ .RB_AWIDTH(RB_AWIDTH), .RB_DWIDTH(RB_DWIDTH),
+ .NUM_BUSES(NUM_STREAMS))
+ settings_bus_slave (
+ .clk(clk));
+ cvita_slave #(.DWIDTH(CVITA_DWIDTH)) s_cvita_ack(.clk(clk));
+ cvita_master #(.DWIDTH(CVITA_DWIDTH)) m_cvita_cmd(.clk(clk));
+ cvita_master #(.DWIDTH(CVITA_DWIDTH), .NUM_STREAMS(NUM_STREAMS)) m_cvita_data(.clk(clk));
+ cvita_slave #(.DWIDTH(CVITA_DWIDTH), .NUM_STREAMS(NUM_STREAMS)) s_cvita_data(.clk(clk));
+ axis_master #(.DWIDTH(AXIS_DWIDTH), .NUM_STREAMS(NUM_STREAMS)) m_axis_data(.clk(clk));
+ axis_slave #(.DWIDTH(AXIS_DWIDTH), .NUM_STREAMS(NUM_STREAMS)) s_axis_data(.clk(clk));
+ localparam bytes_per_cvita_line = CVITA_DWIDTH/8;
+ logic [15:0] src_sid[0:NUM_STREAMS-1] = '{NUM_STREAMS{16'd0}};
+ logic [15:0] dst_sid[0:NUM_STREAMS-1] = '{NUM_STREAMS{16'd0}};
+ logic [11:0] cmd_seqnum = 12'd0;
+ logic [11:0] data_seqnum[0:NUM_STREAMS-1] = '{NUM_STREAMS{12'd0}};
+ int unsigned spp[0:NUM_STREAMS-1] = '{NUM_STREAMS{0}};
+ int unsigned ticks_per_word[0:NUM_STREAMS-1] = '{NUM_STREAMS{1}};
+ bit upstream_connected[0:NUM_STREAMS-1] = '{NUM_STREAMS{0}};
+ bit downstream_connected[0:NUM_STREAMS-1] = '{NUM_STREAMS{0}};
+ cvita_data_type_t data_type[0:NUM_STREAMS-1];
+
+ function void check_upstream(int unsigned stream);
+ check_stream(stream);
+ assert (upstream_connected[stream] != 0) else begin
+ $error("rfnoc_block_streamer::check_upstream(): Stream %0d: upstream is not connected! Check for missing RFNOC_CONNECT().", stream);
+ $finish(0);
+ end
+ endfunction
+
+ function void check_downstream(int unsigned stream);
+ check_stream(stream);
+ assert (downstream_connected[stream] != 0) else begin
+ $error("rfnoc_block_streamer::check_downstream(): Stream %0d: downstream is not connected! Check for missing RFNOC_CONNECT().", stream);
+ $finish(0);
+ end
+ assert (spp[stream] > 0) else begin
+ $error("rfnoc_block_streamer::check_downstream(): Stream %0d: SPP cannot be 0!", stream);
+ $finish(0);
+ end
+ endfunction
+
+ function void check_stream(int unsigned stream);
+ assert (stream < NUM_STREAMS) else begin
+ $error("rfnoc_block_streamer::check_stream(): Stream %0d: Tried to perform operation on non-existent stream!", stream);
+ $finish(0);
+ end
+ endfunction
+
+ function void set_src_sid(input logic [15:0] _src_sid, input int unsigned stream = 0);
+ check_stream(stream);
+ src_sid[stream] = _src_sid;
+ endfunction
+
+ function logic [15:0] get_src_sid(input stream = 0);
+ check_stream(stream);
+ return(src_sid[stream]);
+ endfunction
+
+ function void set_dst_sid(input logic [15:0] _dst_sid, input int unsigned stream = 0);
+ check_stream(stream);
+ dst_sid[stream] = _dst_sid;
+ data_seqnum[stream] = 12'd0;
+ endfunction
+
+ function logic [15:0] get_dst_sid(input int unsigned stream = 0);
+ check_stream(stream);
+ return(dst_sid[stream]);
+ endfunction
+
+ function void set_seqnum(input logic [11:0] _seqnum, input int unsigned stream = 0);
+ check_stream(stream);
+ data_seqnum[stream] = _seqnum;
+ endfunction
+
+ function logic [11:0] get_seqnum(input int unsigned stream);
+ check_stream(stream);
+ return(data_seqnum[stream]);
+ endfunction
+
+ function void clear_seqnum(input int unsigned stream);
+ check_stream(stream);
+ data_seqnum[stream] = 12'd0;
+ endfunction
+
+ function void set_cmd_seqnum(input logic [11:0] seqnum);
+ cmd_seqnum = seqnum;
+ endfunction
+
+ function logic [11:0] get_cmd_seqnum;
+ return(cmd_seqnum);
+ endfunction
+
+ function void clear_cmd_seqnum;
+ cmd_seqnum = 12'd0;
+ endfunction
+
+ function void set_spp(input int unsigned _spp, input int unsigned stream = 0);
+ check_stream(stream);
+ spp[stream] = _spp;
+ endfunction
+
+ function int unsigned get_spp(input int unsigned stream = 0);
+ check_stream(stream);
+ return(spp[stream]);
+ endfunction
+
+ function void set_ticks_per_word(input int unsigned _ticks_per_word, input int unsigned stream = 0);
+ check_stream(stream);
+ ticks_per_word[stream] = _ticks_per_word;
+ endfunction
+
+ function int unsigned get_ticks_per_word(input int unsigned stream = 0);
+ check_stream(stream);
+ return(ticks_per_word[stream]);
+ endfunction
+
+ function void set_data_type(input cvita_data_type_t _data_type, input int unsigned stream = 0);
+ check_stream(stream);
+ data_type[stream] = _data_type;
+ endfunction
+
+ function cvita_data_type_t get_data_type(input int unsigned stream = 0);
+ check_stream(stream);
+ return(data_type[stream]);
+ endfunction
+
+ function void connect_upstream(input int unsigned stream);
+ upstream_connected[stream] = 1;
+ endfunction
+
+ function void disconnect_upstream(input int unsigned stream);
+ upstream_connected[stream] = 0;
+ endfunction
+
+ function bit get_upstream_connected(input int unsigned stream);
+ return(upstream_connected[stream]);
+ endfunction
+
+ function void connect_downstream(input int unsigned stream);
+ downstream_connected[stream] = 1;
+ endfunction
+
+ function void disconnect_downstream(input int unsigned stream);
+ downstream_connected[stream] = 0;
+ endfunction
+
+ function bit get_downstream_connected(input int unsigned stream);
+ return(downstream_connected[stream]);
+ endfunction
+
+ // Reset state of all signals / properties
+ task automatic reset;
+ begin
+ settings_bus_slave.reset();
+ s_cvita_ack.reset();
+ m_cvita_cmd.reset();
+ m_cvita_data.reset();
+ s_cvita_data.reset();
+ m_axis_data.reset();
+ s_axis_data.reset();
+ src_sid = '{NUM_STREAMS{16'd0}};
+ dst_sid = '{NUM_STREAMS{16'd0}};
+ cmd_seqnum = 12'd0;
+ data_seqnum[0:NUM_STREAMS-1] = '{NUM_STREAMS{12'd0}};
+ spp[0:NUM_STREAMS-1] = '{NUM_STREAMS{0}};
+ upstream_connected = '{NUM_STREAMS{0}};
+ downstream_connected = '{NUM_STREAMS{0}};
+ end
+ endtask
+
+ // Send CVITA data packet(s) into the specified stream.
+ // Handles header, breaking payload into multiple packets
+ // (based on spp), and incrementing sequence number.
+ //
+ // Args:
+ // - payload: Packet payload
+ // - metadata: Settings for packet (Optional)
+ // - stream: Stream to send packet on (Optional)
+ task automatic send (
+ input cvita_payload_t payload,
+ input cvita_metadata_t metadata = '{eob:1'b0, has_time:1'b0, timestamp:64'd0},
+ input int unsigned stream = 0);
+ begin
+ cvita_pkt_t pkt;
+ int unsigned bytes_per_word;
+ int unsigned lines_per_pkt;
+ int unsigned num_pkts;
+ logic [63:0] timestamp;
+
+ check_downstream(stream);
+
+ bytes_per_word = data_type[stream].bytes_per_word;
+ lines_per_pkt = spp[stream]*bytes_per_word/bytes_per_cvita_line;
+ num_pkts = $ceil(real'(payload.size())/lines_per_pkt);
+ timestamp = metadata.timestamp;
+ // Break up payload across multiple packets
+ for (int unsigned i = 0; i < num_pkts; i++) begin
+ if (i == num_pkts-1) begin
+ pkt.payload = payload[i*lines_per_pkt:payload.size-1];
+ pkt.hdr = '{pkt_type:DATA, has_time:metadata.has_time, eob:metadata.eob /* Set EOB on final packet */,
+ seqnum:data_seqnum[stream], length:8*(pkt.payload.size()+metadata.has_time+1),
+ src_sid:src_sid[stream], dst_sid:dst_sid[stream], timestamp:timestamp};
+ end else begin
+ pkt.payload = payload[i*lines_per_pkt:(i+1)*lines_per_pkt-1];
+ pkt.hdr = '{pkt_type:DATA, has_time:metadata.has_time, eob:1'b0,
+ seqnum:data_seqnum[stream], length:8*(pkt.payload.size()+metadata.has_time+1),
+ src_sid:src_sid[stream], dst_sid:dst_sid[stream], timestamp:timestamp};
+ end
+ if (pkt.payload.size() != lines_per_pkt) begin
+ $info("rfnoc_block_streamer::push(): Sending partial packet with %0d words (type: %s)",
+ bytes_per_cvita_line*pkt.payload.size()/bytes_per_word, data_type[stream].name);
+ end
+ push_pkt(pkt, stream);
+ data_seqnum[stream] += 1'b1;
+ timestamp += ticks_per_word[stream]*spp[stream];
+ end
+ end
+ endtask
+
+ // Receive CVITA data packet(s) from the specified stream.
+ //
+ // Args:
+ // - payload: Packet payload
+ // - metadata: Packet settings
+ // - stream: Stream to send packet on (Optional)
+ task automatic recv (
+ output cvita_payload_t payload,
+ output cvita_metadata_t metadata,
+ input int unsigned stream = 0);
+ begin
+ cvita_pkt_t pkt;
+ check_upstream(stream);
+ pull_pkt(pkt, stream);
+ payload = pkt.payload;
+ metadata.eob = pkt.hdr.eob;
+ metadata.has_time = pkt.hdr.has_time;
+ metadata.timestamp = pkt.hdr.timestamp;
+ end
+ endtask
+
+ // Push a user defined CVITA packet into the stream.
+ // Note: This is a direct packet interface, it is up
+ // to the caller setup the header correctly!
+ // Args:
+ // - pkt: Packet payload
+ // - stream: Stream to send packet on (Optional)
+ task automatic push_pkt (
+ input cvita_pkt_t pkt,
+ input int unsigned stream = 0);
+ begin
+ check_downstream(stream);
+ m_cvita_data.push_pkt(pkt, stream);
+ end
+ endtask
+
+ // Push a word onto the AXI-Stream bus and wait for it to transfer
+ // Args:
+ // - word: Data to push onto the bus
+ // - eop: End of packet (asserts tlast) (Optional)
+ // - stream: Stream to use (Optional)
+ task automatic push_word (
+ input logic [AXIS_DWIDTH-1:0] word,
+ input logic eop = 1'b0,
+ input int unsigned stream = 0);
+ begin
+ check_downstream(stream);
+ m_axis_data.push_word(word, eop, stream);
+ end
+ endtask
+
+ // Pull a packet from the AXI Stream bus.
+ // Args:
+ // - pkt: Packet data (queue)
+ // - stream: Stream to use (Optional)
+ task automatic pull_pkt (
+ output cvita_pkt_t pkt,
+ input int unsigned stream = 0);
+ begin
+ check_upstream(stream);
+ s_cvita_data.pull_pkt(pkt, stream);
+ end
+ endtask
+
+ // Pull a word from the AXI Stream bus and
+ // return the data and last
+ // Args:
+ // - word: The data pulled from the bus
+ // - eop: End of packet (tlast)
+ // - stream: Stream to use (Optional)
+ task automatic pull_word (
+ output logic [AXIS_DWIDTH-1:0] word,
+ output logic eop,
+ input int unsigned stream = 0);
+ begin
+ check_upstream(stream);
+ s_axis_data.pull_word(word, eop, stream);
+ end
+ endtask
+
+ // Drop a CVITA packet
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic drop_pkt (
+ input int unsigned stream = 0);
+ begin
+ cvita_pkt_t dropped_pkt;
+ check_upstream(stream);
+ pull_pkt(dropped_pkt, stream);
+ end
+ endtask
+
+ // Drop a word on the AXI-stream bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic drop_word (
+ input int unsigned stream = 0);
+ begin
+ logic [AXIS_DWIDTH-1:0] dropped_word;
+ logic dropped_eop;
+ check_upstream(stream);
+ pull_word(dropped_word, dropped_eop, stream);
+ end
+ endtask
+
+ // Push a command packet into the stream.
+ // Args:
+ // - dst_sid: Destination SID (Stream ID, Optional)
+ // - word: Word to send
+ // - response: Response word
+ // - metadata: Packet settings (Optional)
+ task automatic push_cmd (
+ input logic [15:0] dst_sid = dst_sid[0],
+ input logic [63:0] word,
+ output logic [63:0] response,
+ input cvita_metadata_t metadata = '{eob:1'b0, has_time:1'b0, timestamp:64'd0});
+ begin
+ cvita_pkt_t cmd_pkt;
+ cvita_payload_t payload;
+ cvita_metadata_t md;
+ cmd_pkt.hdr = '{pkt_type:CMD, has_time:metadata.has_time, eob:metadata.eob,
+ seqnum:cmd_seqnum, length:8*(metadata.has_time+2),
+ src_sid:src_sid[0], dst_sid:dst_sid, timestamp:metadata.timestamp};
+ // Vivado XSIM workaround... assigning to queue in a struct works but push_back() does not
+ payload.push_back(word);
+ cmd_pkt.payload = payload;
+ m_cvita_cmd.push_pkt(cmd_pkt);
+ pull_resp(response,md);
+ cmd_seqnum += 1'b1;
+ end
+ endtask
+
+ // Push a command packet on the stream.
+ // Args:
+ // - cmd_pkt: Command CVITA packet
+ task automatic push_cmd_pkt (
+ input cvita_pkt_t cmd_pkt);
+ begin
+ m_cvita_cmd.push_pkt(cmd_pkt);
+ end
+ endtask
+
+ // Pull a response packet from the stream.
+ // Args:
+ // - response: Response word
+ // - eob: State of End of Burst flag
+ task automatic pull_resp (
+ output logic [63:0] response,
+ output cvita_metadata_t metadata);
+ begin
+ cvita_pkt_t resp_pkt;
+ s_cvita_ack.pull_pkt(resp_pkt);
+ response = resp_pkt.payload[0];
+ metadata.eob = resp_pkt.hdr.eob;
+ metadata.has_time = resp_pkt.hdr.has_time;
+ metadata.timestamp = resp_pkt.hdr.timestamp;
+ end
+ endtask
+
+ // Pull a response packet from the stream.
+ // Args:
+ // - resp_pkt: Response CVITA packet
+ task automatic pull_resp_pkt (
+ output cvita_pkt_t resp_pkt);
+ begin
+ s_cvita_ack.pull_pkt(resp_pkt);
+ end
+ endtask
+
+ // Write register and return readback value
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - readback: Readback word
+ // - block_port: Block port
+ task automatic write_reg_readback (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ output logic [63:0] readback,
+ input int unsigned block_port = 0);
+ begin
+ push_cmd(_dst_sid+block_port, {24'd0,addr,data}, readback);
+ end
+ endtask
+
+ // Timed write register and return readback value
+ // Note: Readback can stall this call as response
+ // packet is output after register is written
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - timestamp: Command execution time
+ // - readback: Readback word
+ // - block_port: Block port
+ task automatic write_reg_readback_timed (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ input logic [63:0] timestamp,
+ output logic [63:0] readback,
+ input int unsigned block_port = 0);
+ begin
+ cvita_metadata_t md;
+ md.has_time = 1;
+ md.timestamp = timestamp;
+ push_cmd(_dst_sid+block_port, {24'd0,addr,data}, readback, md);
+ end
+ endtask
+
+ // Write register, drop readback word
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - block_port: Block port
+ task automatic write_reg (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ input int unsigned block_port = 0);
+ begin
+ logic [63:0] readback;
+ write_reg_readback(_dst_sid, addr, data, readback, block_port);
+ end
+ endtask
+
+ // Timed write register, drop readback word
+ // Note: Readback can stall this call as response
+ // packet is output after register is written
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - timestamp: Command execution time
+ // - block_port: Block port
+ task automatic write_reg_timed (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ input logic [63:0] timestamp,
+ input int unsigned block_port = 0);
+ begin
+ logic [63:0] readback;
+ write_reg_readback_timed(_dst_sid, addr, data, timestamp, readback, block_port);
+ end
+ endtask
+
+ // Write user register, drop readback word
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - block_port: Block port
+ task automatic write_user_reg (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ input int unsigned block_port = 0);
+ begin
+ write_reg(_dst_sid, addr, data, block_port);
+ end
+ endtask
+
+ // Timed write user register, drop readback word
+ // Note: Readback can stall this call as response
+ // packet is output after register is written
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - word: Value to write to register
+ // - timestamp: Command execution time
+ // - block_port: Block port
+ task automatic write_user_reg_timed (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ input logic [31:0] data,
+ input logic [63:0] timestamp,
+ input int unsigned block_port = 0);
+ begin
+ write_reg_timed(_dst_sid, addr, data, timestamp, block_port);
+ end
+ endtask
+
+ // Read user register
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - readback: Readback word
+ // - block_port: Block port
+ task automatic read_user_reg (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr, // Only 128 user registers (128-255)
+ output logic [63:0] readback,
+ input int unsigned block_port = 0);
+ begin
+ // Set user readback mux
+ write_reg(_dst_sid, SR_RB_ADDR_USER, addr, block_port);
+ read_reg(_dst_sid, RB_USER_RB_DATA, readback, block_port);
+ end
+ endtask
+
+ // Read NoC shell register
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - addr: Register address
+ // - readback: Readback word
+ // - block_port: Block port
+ task automatic read_reg (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ input logic [7:0] addr,
+ output logic [63:0] readback,
+ input int unsigned block_port = 0);
+ begin
+ // Set NoC Shell readback mux, response packet will have readback data
+ write_reg_readback(_dst_sid, SR_RB_ADDR, addr, readback, block_port);
+ end
+ endtask
+
+ // Read NoC shell FIFO size register
+ // Args:
+ // - dst_sid: Destination SID (Stream ID)
+ // - readback: Readback word
+ // - block_port: Block port
+ task automatic read_fifo_size_reg (
+ input logic [15:0] _dst_sid = dst_sid[0],
+ output logic [63:0] readback,
+ input int unsigned block_port = 0);
+ begin
+ read_reg(_dst_sid, RB_FIFOSIZE, readback, block_port);
+ end
+ endtask
+endinterface
+
+// Setup a RFNoC simulation. Creates clocks (bus_clk, ce_clk), resets (bus_rst, ce_rst), an
+// AXI crossbar, and Export IO RFNoC block instance. Export IO is a special RFNoC block used
+// to expose CVITA and AXI-stream streams to the user.
+//
+// Note: Several of the called macros instantiate signals with hard coded names for the
+// test bench to use.
+//
+// Usage: `RFNOC_SIM_INIT()
+// - num_user_blocks: Number of RFNoC blocks in simulation. Max 14.
+// - num_testbench_streams: Number of streams available to user test bench
+// - bus_clk_period, ce_clk_period: Bus, RFNoC block clock frequencies
+//
+`define RFNOC_SIM_INIT(num_user_blocks, num_testbench_streams, bus_clk_period, ce_clk_period) \
+ `DEFINE_CLK(bus_clk, bus_clk_period, 50); \
+ `DEFINE_RESET(bus_rst, 0, 1000); \
+ `DEFINE_CLK(ce_clk, ce_clk_period, 50); \
+ `DEFINE_RESET(ce_rst, 0, 1000); \
+ `RFNOC_ADD_AXI_CROSSBAR(0, num_user_blocks+2); \
+ `RFNOC_ADD_TESTBENCH_BLOCK(tb, num_testbench_streams, num_user_blocks, ce_clk, ce_rst); \
+ `RFNOC_ADD_CONFIG_PORT(tb_config, num_user_blocks+1);
+
+// Instantiates an AXI crossbar and related signals. Instantiates several signals
+// starting with the prefix 'xbar_'.
+//
+// Usage: `INST_AXI_CROSSBAR()
+// - _xbar_addr: Crossbar address
+// - _num_ports: Number of crossbar ports
+//
+`define RFNOC_ADD_AXI_CROSSBAR(_xbar_addr, _num_ports) \
+ localparam [7:0] xbar_addr = _xbar_addr; \
+ settings_bus_master #(.SR_AWIDTH(16), .SR_DWIDTH(32)) xbar_set_bus(.clk(bus_clk)); \
+ settings_bus_master #(.RB_AWIDTH(8), .RB_DWIDTH(32)) xbar_rb_bus(.clk(bus_clk)); \
+ initial begin \
+ xbar_set_bus.reset(); \
+ xbar_rb_bus.reset(); \
+ end \
+ logic [(_num_ports)*64-1:0] xbar_i_tdata; \
+ logic [_num_ports-1:0] xbar_i_tlast, xbar_i_tvalid, xbar_i_tready; \
+ logic [(_num_ports)*64-1:0] xbar_o_tdata; \
+ logic [_num_ports-1:0] xbar_o_tlast, xbar_o_tvalid, xbar_o_tready; \
+ logic xbar_set_stb, xbar_rb_stb; \
+ logic [15:0] xbar_set_addr; \
+ logic [8:0] xbar_rb_addr; \
+ logic [31:0] xbar_set_data; \
+ logic [31:0] xbar_rb_data; \
+ assign xbar_set_stb = xbar_set_bus.settings_bus.set_stb; \
+ assign xbar_set_addr = xbar_set_bus.settings_bus.set_addr; \
+ assign xbar_set_data = xbar_set_bus.settings_bus.set_data; \
+ assign xbar_rb_stb = xbar_rb_bus.settings_bus.set_stb; \
+ assign xbar_rb_addr = xbar_rb_bus.settings_bus.rb_addr; \
+ assign xbar_rb_bus.settings_bus.rb_data = xbar_rb_data; \
+ always @(posedge bus_clk) xbar_rb_bus.settings_bus.rb_stb <= xbar_rb_stb; \
+ axi_crossbar #( \
+ .BASE(0), \
+ .FIFO_WIDTH(64), \
+ .DST_WIDTH(16), \
+ .NUM_INPUTS(_num_ports), \
+ .NUM_OUTPUTS(_num_ports)) \
+ axi_crossbar ( \
+ .clk(bus_clk), \
+ .reset(bus_rst), \
+ .clear(1'b0), \
+ .local_addr(8'(_xbar_addr)), \
+ .i_tdata(xbar_i_tdata), \
+ .i_tvalid(xbar_i_tvalid), \
+ .i_tlast(xbar_i_tlast), \
+ .i_tready(xbar_i_tready), \
+ .pkt_present(xbar_i_tvalid), \
+ .set_stb(xbar_set_stb), \
+ .set_addr(xbar_set_addr), \
+ .set_data(xbar_set_data), \
+ .o_tdata(xbar_o_tdata), \
+ .o_tvalid(xbar_o_tvalid), \
+ .o_tlast(xbar_o_tlast), \
+ .o_tready(xbar_o_tready), \
+ .rb_rd_stb(xbar_rb_stb), \
+ .rb_addr(xbar_rb_addr[2*($clog2(_num_ports+1)-1):0]), \
+ .rb_data(xbar_rb_data));
+
+// Instantiate and connect a RFNoC block to a crossbar. Expects clock & reset
+// signals to be defined with the names ce_clk & ce_rst.
+//
+// Usage: `RFNOC_ADD_BLOCK()
+// - noc_block_name: Name of RFNoC block to instantiate, i.e. noc_block_fft
+// - port_num: Crossbar port to connect RFNoC block to
+//
+`define RFNOC_ADD_BLOCK(noc_block_name, port_num) \
+ `RFNOC_ADD_BLOCK_EXTENDED(noc_block_name, port_num, ce_clk, ce_rst,)
+
+// Instantiate and connect a RFNoC block to a crossbar. Includes extra parameters
+// for custom clock / reset signals and expanding the RFNoC block's name.
+//
+// Usage: `RFNOC_ADD_BLOCK_EXTENDED()
+// - noc_block_name: Name of RFNoC block to instantiate, i.e. noc_block_fft
+// - port_num: Crossbar port to connect block to
+// - ce_clk, ce_rst: RFNoC block clock and reset
+// - append: Append to instance name, useful if instantiating
+// several of the same kind of RFNoC block and need unique
+// instance names. Otherwise leave blank.
+//
+`define RFNOC_ADD_BLOCK_EXTENDED(noc_block_name, port_num, ce_clk, ce_rst, append) \
+ `RFNOC_ADD_BLOCK_CUSTOM(``noc_block_name``append``, port_num) \
+ noc_block_name \
+ noc_block_name``append ( \
+ .bus_clk(bus_clk), \
+ .bus_rst(bus_rst), \
+ .ce_clk(ce_clk), \
+ .ce_rst(ce_rst), \
+ .i_tdata(``noc_block_name``append``_i_tdata), \
+ .i_tlast(``noc_block_name``append``_i_tlast), \
+ .i_tvalid(``noc_block_name``append``_i_tvalid), \
+ .i_tready(``noc_block_name``append``_i_tready), \
+ .o_tdata(``noc_block_name``append``_o_tdata), \
+ .o_tlast(``noc_block_name``append``_o_tlast), \
+ .o_tvalid(``noc_block_name``append``_o_tvalid), \
+ .o_tready(``noc_block_name``append``_o_tready), \
+ .debug());
+
+// Only creates signals and assignments to connect RFNoC block
+// to the crossbar. User is responsible for instantiating the
+// block and connecting the signals.
+//
+// Usage: `RFNOC_ADD_BLOCK_CUSTOM()
+// - noc_block_name: Name of RFNoC block
+// - port_num: Crossbar port to connect
+`define RFNOC_ADD_BLOCK_CUSTOM(noc_block_name, port_num) \
+ localparam [15:0] sid_``noc_block_name`` = {xbar_addr,4'd0+port_num,4'd0}; \
+ logic [63:0] ``noc_block_name``_i_tdata; \
+ logic ``noc_block_name``_i_tlast; \
+ logic ``noc_block_name``_i_tvalid; \
+ logic ``noc_block_name``_i_tready; \
+ logic [63:0] ``noc_block_name``_o_tdata; \
+ logic ``noc_block_name``_o_tlast; \
+ logic ``noc_block_name``_o_tvalid; \
+ logic ``noc_block_name``_o_tready; \
+ assign ``noc_block_name``_i_tdata = xbar_o_tdata[64*(port_num)+63:64*(port_num)]; \
+ assign ``noc_block_name``_i_tlast = xbar_o_tlast[port_num]; \
+ assign ``noc_block_name``_i_tvalid = xbar_o_tvalid[port_num]; \
+ assign xbar_o_tready[port_num] = ``noc_block_name``_i_tready; \
+ assign xbar_i_tdata[64*(port_num)+63:64*(port_num)] = ``noc_block_name``_o_tdata; \
+ assign xbar_i_tlast[port_num] = ``noc_block_name``_o_tlast; \
+ assign xbar_i_tvalid[port_num] = ``noc_block_name``_o_tvalid; \
+ assign ``noc_block_name``_o_tready = xbar_i_tready[port_num]; \
+
+// Instantiate and connect the export I/O RFNoC block to a crossbar. Export I/O is a block that exports
+// the internal NoC Shell & AXI Wrapper I/O to the port list. The block is useful for test benches to
+// use to interact with other RFNoC blocks via the standard RFNoC user interfaces.
+//
+// Instantiates several signals starting with the prefix specified by name, usually 'tb'
+//
+// Usage: `RFNOC_ADD_TESTBENCH_BLOCK()
+// - name: Instance name
+// - num_streams: Sets number of block ports on noc_block_tb
+// - port_num: Crossbar port to connect block to
+//
+`define RFNOC_ADD_TESTBENCH_BLOCK(name, num_streams, port_num, _clk, _rst) \
+ `RFNOC_ADD_BLOCK_CUSTOM(noc_block_``name``, port_num) \
+ rfnoc_block_streamer #(.NUM_STREAMS(num_streams)) ``name``_streamer(.clk(ce_clk)); \
+ initial begin \
+ ``name``_streamer.reset(); \
+ for (int unsigned k = 0; k < num_streams; k++) begin \
+ name``_streamer.set_src_sid(16'(sid_noc_block_``name + k), k); \
+ end \
+ end \
+ logic [num_streams-1:0] ``name``_set_stb; \
+ logic [num_streams*8-1:0] ``name``_set_addr; \
+ logic [(num_streams)*32-1:0] ``name``_set_data; \
+ logic [num_streams-1:0] ``name``_rb_stb; \
+ logic [num_streams*8-1:0] ``name``_rb_addr; \
+ logic [(num_streams)*64-1:0] ``name``_rb_data; \
+ logic [63:0] ``name``_s_cvita_cmd_tdata; \
+ logic ``name``_s_cvita_cmd_tlast; \
+ logic ``name``_s_cvita_cmd_tvalid; \
+ logic ``name``_s_cvita_cmd_tready; \
+ logic [63:0] ``name``_m_cvita_ack_tdata; \
+ logic ``name``_m_cvita_ack_tlast; \
+ logic ``name``_m_cvita_ack_tvalid; \
+ logic ``name``_m_cvita_ack_tready; \
+ logic [(num_streams)*64-1:0] ``name``_m_cvita_data_tdata; \
+ logic [num_streams-1:0] ``name``_m_cvita_data_tlast; \
+ logic [num_streams-1:0] ``name``_m_cvita_data_tvalid; \
+ logic [num_streams-1:0] ``name``_m_cvita_data_tready; \
+ logic [(num_streams)*64-1:0] ``name``_s_cvita_data_tdata; \
+ logic [num_streams-1:0] ``name``_s_cvita_data_tlast; \
+ logic [num_streams-1:0] ``name``_s_cvita_data_tvalid; \
+ logic [num_streams-1:0] ``name``_s_cvita_data_tready; \
+ logic [(num_streams)*32-1:0] ``name``_m_axis_data_tdata; \
+ logic [num_streams-1:0] ``name``_m_axis_data_tlast; \
+ logic [num_streams-1:0] ``name``_m_axis_data_tvalid; \
+ logic [num_streams-1:0] ``name``_m_axis_data_tready; \
+ logic [(num_streams)*32-1:0] ``name``_s_axis_data_tdata; \
+ logic [num_streams-1:0] ``name``_s_axis_data_tlast; \
+ logic [num_streams-1:0] ``name``_s_axis_data_tvalid; \
+ logic [num_streams-1:0] ``name``_s_axis_data_tready; \
+ assign ``name``_s_cvita_cmd_tdata = ``name``_streamer.m_cvita_cmd.axis.tdata; \
+ assign ``name``_s_cvita_cmd_tlast = ``name``_streamer.m_cvita_cmd.axis.tlast; \
+ assign ``name``_s_cvita_cmd_tvalid = ``name``_streamer.m_cvita_cmd.axis.tvalid; \
+ assign ``name``_streamer.m_cvita_cmd.axis.tready = ``name``_s_cvita_cmd_tready; \
+ assign ``name``_streamer.s_cvita_ack.axis.tdata = ``name``_m_cvita_ack_tdata; \
+ assign ``name``_streamer.s_cvita_ack.axis.tlast = ``name``_m_cvita_ack_tlast; \
+ assign ``name``_streamer.s_cvita_ack.axis.tvalid = ``name``_m_cvita_ack_tvalid; \
+ assign ``name``_m_cvita_ack_tready = ``name``_streamer.s_cvita_ack.axis.tready; \
+ generate \
+ for (genvar i = 0; i < num_streams; i = i + 1) begin \
+ assign ``name``_streamer.settings_bus_slave.settings_bus.set_stb[i] = ``name``_set_stb[i]; \
+ assign ``name``_streamer.settings_bus_slave.settings_bus.set_addr[8*i+7:8*i] = ``name``_set_addr[8*i+7:8*i]; \
+ assign ``name``_streamer.settings_bus_slave.settings_bus.set_data[32*i+31:32*i] = ``name``_set_data[32*i+31:32*i]; \
+ assign ``name``_streamer.settings_bus_slave.settings_bus.rb_addr[8*i+7:8*i] = ``name``_rb_addr[8*i+7:8*i]; \
+ assign ``name``_rb_stb[i] = ``name``_streamer.settings_bus_slave.settings_bus.rb_stb[i]; \
+ assign ``name``_rb_data[64*i+63:64*i] = ``name``_streamer.settings_bus_slave.settings_bus.rb_data[64*i+63:64*i]; \
+ assign ``name``_s_cvita_data_tdata[i*64+63:i*64] = ``name``_streamer.m_cvita_data.axis.tdata[i*64+63:i*64]; \
+ assign ``name``_s_cvita_data_tlast[i] = ``name``_streamer.m_cvita_data.axis.tlast[i]; \
+ assign ``name``_s_cvita_data_tvalid[i] = ``name``_streamer.m_cvita_data.axis.tvalid[i]; \
+ assign ``name``_streamer.m_cvita_data.axis.tready[i] = ``name``_s_cvita_data_tready[i]; \
+ assign ``name``_streamer.s_cvita_data.axis.tdata[i*64+63:i*64] = ``name``_m_cvita_data_tdata[i*64+63:i*64]; \
+ assign ``name``_streamer.s_cvita_data.axis.tlast[i] = ``name``_m_cvita_data_tlast[i]; \
+ assign ``name``_streamer.s_cvita_data.axis.tvalid[i] = ``name``_m_cvita_data_tvalid[i]; \
+ assign ``name``_m_cvita_data_tready[i] = ``name``_streamer.s_cvita_data.axis.tready[i]; \
+ assign ``name``_s_axis_data_tdata[i*32+31:i*32] = ``name``_streamer.m_axis_data.axis.tdata[i*32+31:i*32]; \
+ assign ``name``_s_axis_data_tlast[i] = ``name``_streamer.m_axis_data.axis.tlast[i]; \
+ assign ``name``_s_axis_data_tvalid[i] = ``name``_streamer.m_axis_data.axis.tvalid[i]; \
+ assign ``name``_streamer.m_axis_data.axis.tready[i] = ``name``_s_axis_data_tready[i]; \
+ assign ``name``_streamer.s_axis_data.axis.tdata[i*32+31:i*32] = ``name``_m_axis_data_tdata[i*32+31:i*32]; \
+ assign ``name``_streamer.s_axis_data.axis.tlast[i] = ``name``_m_axis_data_tlast[i]; \
+ assign ``name``_streamer.s_axis_data.axis.tvalid[i] = ``name``_m_axis_data_tvalid[i]; \
+ assign ``name``_m_axis_data_tready[i] = ``name``_streamer.s_axis_data.axis.tready[i]; \
+ end \
+ endgenerate \
+ noc_block_export_io #(.NUM_PORTS(num_streams)) \
+ noc_block_``name ( \
+ .bus_clk(bus_clk), \
+ .bus_rst(bus_rst), \
+ .ce_clk(_clk), \
+ .ce_rst(_rst), \
+ .i_tdata(noc_block_``name``_i_tdata), \
+ .i_tlast(noc_block_``name``_i_tlast), \
+ .i_tvalid(noc_block_``name``_i_tvalid), \
+ .i_tready(noc_block_``name``_i_tready), \
+ .o_tdata(noc_block_``name``_o_tdata), \
+ .o_tlast(noc_block_``name``_o_tlast), \
+ .o_tvalid(noc_block_``name``_o_tvalid), \
+ .o_tready(noc_block_``name``_o_tready), \
+ .set_stb(``name``_set_stb), \
+ .set_addr(``name``_set_addr), \
+ .set_data(``name``_set_data), \
+ .rb_stb(``name``_rb_stb), \
+ .rb_addr(``name``_rb_addr), \
+ .rb_data(``name``_rb_data), \
+ .s_cvita_cmd_tdata(``name``_s_cvita_cmd_tdata), \
+ .s_cvita_cmd_tlast(``name``_s_cvita_cmd_tlast), \
+ .s_cvita_cmd_tvalid(``name``_s_cvita_cmd_tvalid), \
+ .s_cvita_cmd_tready(``name``_s_cvita_cmd_tready), \
+ .m_cvita_ack_tdata(``name``_m_cvita_ack_tdata), \
+ .m_cvita_ack_tlast(``name``_m_cvita_ack_tlast), \
+ .m_cvita_ack_tvalid(``name``_m_cvita_ack_tvalid), \
+ .m_cvita_ack_tready(``name``_m_cvita_ack_tready), \
+ .s_cvita_data_tdata(``name``_s_cvita_data_tdata), \
+ .s_cvita_data_tlast(``name``_s_cvita_data_tlast), \
+ .s_cvita_data_tvalid(``name``_s_cvita_data_tvalid), \
+ .s_cvita_data_tready(``name``_s_cvita_data_tready), \
+ .m_cvita_data_tdata(``name``_m_cvita_data_tdata), \
+ .m_cvita_data_tlast(``name``_m_cvita_data_tlast), \
+ .m_cvita_data_tvalid(``name``_m_cvita_data_tvalid), \
+ .m_cvita_data_tready(``name``_m_cvita_data_tready), \
+ .s_axis_data_tdata(``name``_s_axis_data_tdata), \
+ .s_axis_data_tlast(``name``_s_axis_data_tlast), \
+ .s_axis_data_tvalid(``name``_s_axis_data_tvalid), \
+ .s_axis_data_tready(``name``_s_axis_data_tready), \
+ .m_axis_data_tdata(``name``_m_axis_data_tdata), \
+ .m_axis_data_tlast(``name``_m_axis_data_tlast), \
+ .m_axis_data_tvalid(``name``_m_axis_data_tvalid), \
+ .m_axis_data_tready(``name``_m_axis_data_tready), \
+ .debug());
+
+// Instantiate and connect a rfnoc block streamer instance directly to the crossbar,
+// but only the command and acknowledge interfaces. This interface is only useful
+// for sending command packets.
+//
+// Usage: `RFNOC_ADD_CONFIG_PORT()
+// - name: Instance name
+// - port_num: Crossbar port to connect to
+//
+`define RFNOC_ADD_CONFIG_PORT(name, port_num) \
+ localparam [15:0] sid_``name = {xbar_addr,4'd0+port_num,4'd0}; \
+ rfnoc_block_streamer #(.NUM_STREAMS(1)) ``name``(.clk(bus_clk)); \
+ initial begin \
+ ``name``.reset(); \
+ ``name``.set_src_sid(16'(sid_``name), 0); \
+ end \
+ assign ``name``.s_cvita_ack.axis.tdata = xbar_o_tdata[64*(port_num)+63:64*(port_num)]; \
+ assign ``name``.s_cvita_ack.axis.tvalid = xbar_o_tvalid[port_num]; \
+ assign ``name``.s_cvita_ack.axis.tlast = xbar_o_tlast[port_num]; \
+ assign xbar_o_tready[port_num] = ``name``.s_cvita_ack.axis.tready; \
+ assign xbar_i_tdata[64*(port_num)+63:64*(port_num)] = ``name``.m_cvita_cmd.axis.tdata; \
+ assign xbar_i_tvalid[port_num] = ``name``.m_cvita_cmd.axis.tvalid; \
+ assign xbar_i_tlast[port_num] = ``name``.m_cvita_cmd.axis.tlast; \
+ assign ``name``.m_cvita_cmd.axis.tready = xbar_i_tready[port_num];
+
+// Connecting two RFNoC blocks requires setting up flow control and
+// their next destination registers.
+//
+// Usage: `RFNOC_CONNECT()
+// - from_noc_block_name: Name of producer (or upstream) RFNoC block
+// - to_noc_block_name: Name of consuming (or downstream) RFNoC block
+// - data_type: Data type for data stream (i.e. SC16, FC32, etc)
+// - spp: Samples per packet
+//
+`define RFNOC_CONNECT(from_noc_block_name,to_noc_block_name,data_type,spp) \
+ `RFNOC_CONNECT_BLOCK_PORT(from_noc_block_name,0,to_noc_block_name,0,data_type,spp);
+
+// Setup RFNoC block flow control per block port
+//
+// Usage: `RFNOC_CONNECT_BLOCK_PORT()
+// - from_noc_block_name: Name of producer (or upstream) RFNoC block
+// - from_block_port: Block port of producer RFNoC block
+// - to_noc_block_name: Name of consumer (or downstream) RFNoC block
+// - to_block_port: Block port of consumer RFNoC block
+// - data_type: Data type for data stream (i.e. SC16, FC32, etc)
+// - spp: Samples per packet
+//
+`define RFNOC_CONNECT_BLOCK_PORT(from_noc_block_name,from_block_port,to_noc_block_name,to_block_port,data_type,spp) \
+ $display("Connecting %s (SID: %0d:%0d) to %s (SID: %0d:%0d)", \
+ `"from_noc_block_name`",sid_``from_noc_block_name >> 4,from_block_port, \
+ `"to_noc_block_name`",sid_``to_noc_block_name >> 4,to_block_port); \
+ // Clear block, write be any value \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_CLEAR_TX_FC, 32'h1, from_block_port); \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_CLEAR_TX_FC, 32'h0, from_block_port); \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_CLEAR_RX_FC, 32'h1, to_block_port); \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_CLEAR_RX_FC, 32'h0, to_block_port); \
+ // Set block's src_sid, next_dst_sid, resp_in_dst_sid, resp_out_dst_sid \
+ // Default response dst in / out SIDs to test bench block \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_SRC_SID, sid_``from_noc_block_name + from_block_port, from_block_port); \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_SRC_SID, sid_``to_noc_block_name + to_block_port, to_block_port); \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_NEXT_DST_SID, sid_``to_noc_block_name + to_block_port, from_block_port); \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_RESP_IN_DST_SID, sid_noc_block_tb, from_block_port); \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_RESP_OUT_DST_SID, sid_noc_block_tb, from_block_port); \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_RESP_IN_DST_SID, sid_noc_block_tb, to_block_port); \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_RESP_OUT_DST_SID, sid_noc_block_tb, to_block_port); \
+ // If connection involves testbench block, set the SID and packet size. \
+ if (sid_``from_noc_block_name == sid_noc_block_tb) begin \
+ tb_streamer.set_dst_sid(16'(sid_``to_noc_block_name + to_block_port), from_block_port); \
+ tb_streamer.set_spp(spp, from_block_port); \
+ tb_streamer.set_data_type(data_type, from_block_port); \
+ tb_streamer.connect_downstream(from_block_port); \
+ end \
+ if (sid_``to_noc_block_name == sid_noc_block_tb) begin \
+ tb_streamer.connect_upstream(to_block_port); \
+ end \
+ // Send a flow control response packet every (receive window buffer size)/16 bytes \
+ tb_config.write_reg(sid_``to_noc_block_name, SR_FLOW_CTRL_BYTES_PER_ACK, \
+ {1 /* enable consumed */, 31'(8*2**(``to_noc_block_name``.noc_shell.STR_SINK_FIFOSIZE[to_block_port*8 +: 8])/16)}, \
+ to_block_port); \
+ // Set up window size (in bytes) \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_FLOW_CTRL_WINDOW_SIZE, \
+ 32'(8*2**(``to_noc_block_name``.noc_shell.STR_SINK_FIFOSIZE[to_block_port*8 +: 8])), \
+ from_block_port); \
+ // Set up packet limit (Unused and commented out for now) \
+ // tb_config.write_reg(sid_``from_noc_block_name, SR_FLOW_CTRL_PKT_LIMIT, 32, from_block_port); \
+ // Enable source flow control output and byte based flow control, disable packet limit \
+ tb_config.write_reg(sid_``from_noc_block_name, SR_FLOW_CTRL_EN, 3'b011, from_block_port); \
+
+`endif
diff --git a/fpga/usrp3/sim/rfnoc/test_exec.svh b/fpga/usrp3/sim/rfnoc/test_exec.svh
new file mode 100644
index 000000000..9dd81e77b
--- /dev/null
+++ b/fpga/usrp3/sim/rfnoc/test_exec.svh
@@ -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