aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/sim
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/sim')
-rw-r--r--fpga/usrp3/sim/axi/Makefile.srcs12
-rw-r--r--fpga/usrp3/sim/axi/sim_axi4_lib.svh93
-rw-r--r--fpga/usrp3/sim/axi/sim_axis_lib.svh219
-rw-r--r--fpga/usrp3/sim/axi/sim_cvita_lib.svh440
-rw-r--r--fpga/usrp3/sim/control/Makefile.srcs10
-rw-r--r--fpga/usrp3/sim/control/sim_set_rb_lib.svh169
-rw-r--r--fpga/usrp3/sim/general/Makefile.srcs13
-rw-r--r--fpga/usrp3/sim/general/sim_clks_rsts.vh94
-rw-r--r--fpga/usrp3/sim/general/sim_exec_report.vh138
-rw-r--r--fpga/usrp3/sim/general/sim_file_io.svh125
-rw-r--r--fpga/usrp3/sim/general/sim_math.vh276
-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
22 files changed, 6222 insertions, 0 deletions
diff --git a/fpga/usrp3/sim/axi/Makefile.srcs b/fpga/usrp3/sim/axi/Makefile.srcs
new file mode 100644
index 000000000..96f7eb196
--- /dev/null
+++ b/fpga/usrp3/sim/axi/Makefile.srcs
@@ -0,0 +1,12 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+
+##################################################
+# Simulation Libraries/Headers for AXI based interfaces
+##################################################
+SIM_AXI_SRCS = $(abspath $(addprefix $(BASE_DIR)/../sim/axi/, \
+sim_axi4_lib.svh \
+sim_axis_lib.svh \
+sim_cvita_lib.svh \
+))
diff --git a/fpga/usrp3/sim/axi/sim_axi4_lib.svh b/fpga/usrp3/sim/axi/sim_axi4_lib.svh
new file mode 100644
index 000000000..588c8d367
--- /dev/null
+++ b/fpga/usrp3/sim/axi/sim_axi4_lib.svh
@@ -0,0 +1,93 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+
+interface axi4_addr_t #(parameter AWIDTH=32, parameter IDWIDTH=4)
+ (input clk);
+
+ logic [IDWIDTH-1:0] id;
+ logic [AWIDTH-1:0] addr;
+ logic [7:0] len;
+ logic [2:0] size;
+ logic [1:0] burst;
+ logic lock;
+ logic [3:0] cache;
+ logic [2:0] prot;
+ logic [3:0] qos;
+ logic [3:0] region;
+ logic user;
+ logic valid;
+ logic ready;
+
+// modport master(output id,addr,len,size,burst,lock,cache,prot,qos,valid, input ready);
+// modport slave(input id,addr,len,size,burst,lock,cache,prot,qos,valid, output ready);
+
+endinterface
+
+interface axi4_wdata_t #(parameter DWIDTH=64)
+ (input clk);
+
+ logic [DWIDTH-1:0] data;
+ logic [(DWIDTH/8)-1:0] strb;
+ logic last;
+ logic user;
+ logic valid;
+ logic ready;
+
+// modport master(output data,strb,last,valid, input ready);
+// modport slave(input data,strb,last,valid, output ready);
+
+endinterface
+
+interface axi4_resp_t #(parameter IDWIDTH=4)
+ (input clk);
+
+ logic ready;
+ logic [IDWIDTH-1:0] id;
+ logic [1:0] resp;
+ logic user;
+ logic valid;
+
+// modport master(output ready, input id,resp,valid);
+// modport slave(input ready, output id,resp,valid);
+
+endinterface
+
+interface axi4_rdata_t #(parameter DWIDTH=64, parameter IDWIDTH=4)
+ (input clk);
+
+ logic ready;
+ logic [IDWIDTH-1:0] id;
+ logic [DWIDTH-1:0] data;
+ logic [1:0] resp;
+ logic user;
+ logic last;
+ logic valid;
+
+// modport master(output ready, input id,data,resp,last,valid);
+// modport slave(input ready, output id,data,resp,last,valid);
+
+endinterface
+
+interface axi4_wr_t #(parameter DWIDTH=64, parameter AWIDTH=32, parameter IDWIDTH=4)
+ (input clk);
+
+ axi4_addr_t #(.AWIDTH(AWIDTH), .IDWIDTH(IDWIDTH)) addr (.clk(clk));
+ axi4_wdata_t #(.DWIDTH(DWIDTH)) data (.clk(clk));
+ axi4_resp_t #(.IDWIDTH(IDWIDTH)) resp (.clk(clk));
+
+// modport master(output addr, output data, input resp);
+// modport slave(input addr, input data, output resp);
+
+endinterface
+
+interface axi4_rd_t #(parameter DWIDTH=64, parameter AWIDTH=32, parameter IDWIDTH=4)
+ (input clk);
+
+ axi4_addr_t #(.AWIDTH(AWIDTH), .IDWIDTH(IDWIDTH)) addr (.clk(clk));
+ axi4_rdata_t #(.DWIDTH(DWIDTH), .IDWIDTH(IDWIDTH)) data (.clk(clk));
+
+// modport master(output addr, output data);
+// modport slave(input addr, input data);
+
+endinterface
diff --git a/fpga/usrp3/sim/axi/sim_axis_lib.svh b/fpga/usrp3/sim/axi/sim_axis_lib.svh
new file mode 100644
index 000000000..bdee113de
--- /dev/null
+++ b/fpga/usrp3/sim/axi/sim_axis_lib.svh
@@ -0,0 +1,219 @@
+//
+// Copyright 2016 Ettus Research
+//
+`ifndef INCLUDED_SIM_AXIS_LIB
+`define INCLUDED_SIM_AXIS_LIB
+
+interface axis_t #(parameter DWIDTH = 32, parameter NUM_STREAMS = 1)(input clk);
+ logic [NUM_STREAMS*DWIDTH-1:0] tdata;
+ logic [NUM_STREAMS-1:0] tvalid;
+ logic [NUM_STREAMS-1:0] tlast;
+ logic [NUM_STREAMS-1:0] tready;
+
+ modport master (
+ output tdata,
+ output tvalid,
+ output tlast,
+ input tready);
+
+ modport slave (
+ input tdata,
+ input tvalid,
+ input tlast,
+ output tready);
+endinterface
+
+// Interface to push data onto a master AXI-stream bus
+interface axis_master #(parameter DWIDTH = 32, parameter NUM_STREAMS = 1)(input clk);
+ axis_t #(.DWIDTH(DWIDTH), .NUM_STREAMS(NUM_STREAMS)) axis(.clk(clk));
+
+ // Check that stream is actually in use
+ function void check_stream(int stream);
+ assert (stream < NUM_STREAMS) else
+ $error("axis_master::check_stream(): Tried to perform operation on unused stream %0d", stream);
+ endfunction
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ begin
+ axis.tvalid = 0;
+ axis.tlast = 0;
+ axis.tdata = 0;
+ end
+ endtask
+
+ // Push a word onto the AXI-Stream bus and wait for it to transfer
+ // Args:
+ // - word: The data to push onto the bus
+ // - eop (optional): End of packet (asserts tlast)
+ // - stream: Stream to use (Optional)
+ task automatic push_word (
+ input logic [DWIDTH-1:0] word,
+ input logic eop = 1'b0,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ if (clk) @(negedge clk); // Align with negative edge
+ axis.tvalid[stream] = 1;
+ axis.tlast[stream] = eop;
+ axis.tdata[DWIDTH*stream +: DWIDTH] = word;
+ @(posedge clk); // Put sample on data bus
+ while(~axis.tready[stream]) @(posedge clk); // Wait until receiver ready
+ @(negedge clk); // Put sample on data bus
+ axis.tvalid[stream] = 0;
+ axis.tlast[stream] = 0;
+ end
+ endtask
+
+ // Push a bubble cycle onto the AXI-Stream bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic push_bubble (input int stream = 0);
+ begin
+ check_stream(stream);
+ axis.tvalid[stream] = 0;
+ @(negedge clk);
+ end
+ endtask
+
+ // Push a packet with random data onto to the AXI Stream bus
+ // Args:
+ // - num_samps: Packet size.
+ task automatic push_rand_pkt (
+ input int num_samps,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ if (clk) @(negedge clk);
+ repeat(num_samps-1) begin
+ push_word({(((DWIDTH-1)/32)+1){$random}}, 0, stream);
+ end
+ push_word({(((DWIDTH-1)/32)+1){$random}}, 1, stream);
+ end
+ endtask
+
+ // Push a packet with a ramp on to the AXI Stream bus
+ // Args:
+ // - num_samps: Packet size.
+ // - ramp_start: Start value for the ramp
+ // - ramp_inc: Increment per clock cycle
+ // - stream: Stream to use (Optional)
+ task automatic push_ramp_pkt (
+ input integer num_samps,
+ input [DWIDTH-1:0] ramp_start,
+ input [DWIDTH-1:0] ramp_inc,
+ input int stream = 0);
+ begin
+ automatic integer counter = 0;
+ check_stream(stream);
+ if (clk) @(negedge clk);
+ repeat(num_samps-1) begin
+ push_word(ramp_start+(counter*ramp_inc), 0, stream);
+ counter = counter + 1;
+ end
+ push_word(ramp_start+(counter*ramp_inc), 1, stream);
+ end
+ endtask
+
+endinterface
+
+
+// Interface to push data onto a master AXI-stream bus
+interface axis_slave #(parameter DWIDTH = 32, parameter NUM_STREAMS = 1)(input clk);
+ axis_t #(.DWIDTH(DWIDTH), .NUM_STREAMS(NUM_STREAMS)) axis(.clk(clk));
+
+ // Check that stream is actually in use
+ function void check_stream(int stream);
+ assert (stream < NUM_STREAMS) else
+ $error("axis_slave::check_stream(): Tried to perform operation on unused stream %0d", stream);
+ endfunction
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ begin
+ axis.tready = 0;
+ 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 [DWIDTH-1:0] word,
+ output logic eop,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ if (clk) @(negedge clk);
+ axis.tready[stream] = 1;
+ while(~axis.tvalid[stream]) @(posedge clk);
+ word = axis.tdata[DWIDTH*stream +: DWIDTH];
+ eop = axis.tlast[stream];
+ @(negedge clk);
+ axis.tready[stream] = 0;
+ end
+ endtask
+
+ // Wait for a sample to be transferred on the AXI Stream
+ // bus and return the data and last. Note, this task only
+ // observes the bus and does not affect the AXI control
+ // signals.
+ // Args:
+ // - word: The data pulled from the bus
+ // - eop: End of packet (tlast)
+ // - stream: Stream to use (Optional)
+ task automatic copy_word (
+ output logic [DWIDTH-1:0] word,
+ output logic eop,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(~(axis.tready[stream]&axis.tvalid[stream])) @(posedge clk); // Wait until sample is transferred
+ word = axis.tdata[DWIDTH*stream +: DWIDTH];
+ eop = axis.tlast[stream];
+ @(negedge clk);
+ end
+ endtask
+
+ // Wait for a bubble cycle on the AXI Stream bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic wait_for_bubble (
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(axis.tready[stream]&axis.tvalid[stream]) @(posedge clk);
+ @(negedge clk);
+ end
+ endtask
+
+ // Wait for a packet to finish on the bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic wait_for_pkt (
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(~(axis.tready[stream]&axis.tvalid[stream]&axis.tlast[stream])) @(posedge clk);
+ @(negedge clk);
+ end
+ endtask
+
+ // Drop a word on the bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic drop_word (
+ input int stream = 0);
+ begin
+ logic [DWIDTH-1:0] dropped_word;
+ logic dropped_eop;
+ pull_word(dropped_word, dropped_eop, stream);
+ end
+ endtask
+
+endinterface
+
+`endif \ No newline at end of file
diff --git a/fpga/usrp3/sim/axi/sim_cvita_lib.svh b/fpga/usrp3/sim/axi/sim_cvita_lib.svh
new file mode 100644
index 000000000..97e3d9cd2
--- /dev/null
+++ b/fpga/usrp3/sim/axi/sim_cvita_lib.svh
@@ -0,0 +1,440 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+`ifndef INCLUDED_SIM_CVITA_LIB
+`define INCLUDED_SIM_CVITA_LIB
+
+`include "sim_axis_lib.svh"
+
+typedef logic [63:0] cvita_payload_t[$];
+
+// CVITA packet types
+typedef enum logic [1:0] {
+ DATA=2'b00, FC=2'b01, CMD=2'b10, RESP=2'b11
+} cvita_pkt_type_t;
+
+// CVITA Header
+typedef struct packed {
+ cvita_pkt_type_t pkt_type;
+ logic has_time;
+ logic eob;
+ logic [11:0] seqnum;
+ logic [15:0] length;
+ logic [15:0] src_sid;
+ logic [15:0] dst_sid;
+ logic [63:0] timestamp;
+} cvita_hdr_t;
+
+// CVITA Packet, Header + Payload
+typedef struct {
+ cvita_hdr_t hdr;
+ cvita_payload_t payload;
+} cvita_pkt_t;
+
+// CVITA packet metadata
+typedef struct {
+ logic eob = 1'b0;
+ logic has_time = 1'b0;
+ logic [63:0] timestamp = 'd0;
+} cvita_metadata_t;
+
+function logic[63:0] flatten_chdr_no_ts(input cvita_hdr_t hdr);
+ return {hdr.pkt_type, hdr.has_time, hdr.eob, hdr.seqnum, hdr.length, hdr.src_sid, hdr.dst_sid};
+endfunction
+
+//TODO: This should be a function but it segfaults XSIM.
+task automatic unflatten_chdr_no_ts;
+ input logic[63:0] hdr_bits;
+ output cvita_hdr_t hdr;
+ begin
+ hdr = '{
+ pkt_type:cvita_pkt_type_t'(hdr_bits[63:62]), has_time:hdr_bits[61], eob:hdr_bits[60],
+ seqnum:hdr_bits[59:48], length:hdr_bits[47:32], src_sid:hdr_bits[31:16], dst_sid:hdr_bits[15:0], timestamp:0 //Default timestamp
+ };
+ end
+endtask
+
+// Extracts header from CVITA packets.
+// Args:
+// - pkt: CVITA packet
+// - hdr: CVITA header
+task automatic extract_chdr(ref logic [63:0] pkt[$], ref cvita_hdr_t hdr);
+ begin
+ unflatten_chdr_no_ts(pkt[0],hdr);
+ if (hdr.has_time) begin
+ hdr.timestamp = pkt[1];
+ // Delete both header and time stamp
+ pkt = pkt[2:pkt.size-1];
+ end else begin
+ // Delete header
+ pkt = pkt[1:pkt.size-1];
+ end
+ end
+endtask
+
+// Drops header from CVITA packets leaving only payload data.
+// Args:
+// - pkt: CVITA packet
+task automatic drop_chdr(ref logic [63:0] pkt[$]);
+ begin
+ automatic cvita_hdr_t hdr;
+ extract_chdr(pkt,hdr);
+ end
+endtask
+
+task automatic unflatten_chdr;
+ input logic[63:0] hdr_bits;
+ input logic[63:0] timestamp;
+ output cvita_hdr_t hdr;
+ begin
+ hdr = '{
+ pkt_type:cvita_pkt_type_t'(hdr_bits[63:62]), has_time:hdr_bits[61], eob:hdr_bits[60],
+ seqnum:hdr_bits[59:48], length:hdr_bits[47:32], src_sid:hdr_bits[31:16], dst_sid:hdr_bits[15:0], timestamp:timestamp
+ };
+ end
+endtask
+
+function logic chdr_compare(input cvita_hdr_t a, input cvita_hdr_t b);
+ return ((a.pkt_type == b.pkt_type) && (a.has_time == b.has_time) && (a.eob == b.eob) &&
+ (a.seqnum == b.seqnum) && (a.length == b.length) && (a.src_sid == b.src_sid) && (a.dst_sid == b.dst_sid));
+endfunction
+
+typedef struct packed {
+ logic [31:0] count;
+ logic [63:0] sum;
+ logic [63:0] min;
+ logic [63:0] max;
+ logic [63:0] crc;
+} cvita_stats_t;
+
+// Interface and tasks to send CVITA packets over a master AXI-Stream bus
+interface cvita_master #(parameter DWIDTH = 64, parameter NUM_STREAMS = 1)(input clk);
+ axis_t #(.DWIDTH(DWIDTH), .NUM_STREAMS(NUM_STREAMS)) axis(.clk(clk));
+
+ // Check that stream is actually in use
+ function void check_stream(int stream);
+ assert (stream < NUM_STREAMS) else
+ $error("cvita_master::check_stream(): Tried to perform operation on unused stream %0d", stream);
+ endfunction
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ begin
+ axis.tvalid = 0;
+ axis.tlast = 0;
+ axis.tdata = 0;
+ end
+ endtask
+
+ // Push a word onto the AXI-Stream bus and wait for it to transfer
+ // Args:
+ // - word: The data to push onto the bus
+ // - eop: End of packet (asserts tlast) (Optional)
+ // - stream: Stream to use (Optional)
+ task automatic push_word (
+ input logic [DWIDTH-1:0] word,
+ input logic eop = 1'b0,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ if (clk) @(negedge clk); // Align with negative edge
+ axis.tvalid[stream] = 1;
+ axis.tlast[stream] = eop;
+ axis.tdata[DWIDTH*stream +: DWIDTH] = word;
+ @(posedge clk); // Put sample on data bus
+ while(~axis.tready[stream]) @(posedge clk); // Wait until receiver ready
+ @(negedge clk); // Put sample on data bus
+ axis.tvalid[stream] = 0;
+ axis.tlast[stream] = 0;
+ end
+ endtask
+
+ // Push a bubble cycle onto the AXI-Stream bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic push_bubble (input int stream = 0);
+ begin
+ check_stream(stream);
+ axis.tvalid[stream] = 0;
+ @(negedge clk);
+ end
+ endtask
+
+ // Push a CVITA header into the stream
+ // Args:
+ // - hdr: The header to push
+ // - stream: Stream to use (Optional)
+ task automatic push_hdr (
+ input cvita_hdr_t hdr,
+ input int stream = 0);
+ push_word(flatten_chdr_no_ts(hdr), 0, stream);
+ endtask
+
+ // Push a packet with random data onto to the AXI Stream bus
+ // Args:
+ // - num_samps: Packet size.
+ // - hdr: Header to attach to packet (length will be ignored)
+ // - stream: Stream to use (Optional)
+ task automatic push_rand_pkt (
+ input integer num_samps,
+ input cvita_hdr_t hdr,
+ input int stream = 0);
+ begin
+ cvita_hdr_t tmp_hdr = hdr;
+ tmp_hdr.length = (num_samps * 8) + (hdr.has_time ? 16 : 8);
+ @(negedge clk);
+ push_hdr(tmp_hdr);
+ if (hdr.has_time) push_word(hdr.timestamp, 0, stream);
+ repeat(num_samps-1) begin
+ push_word({$random,$random}, 0, stream);
+ end
+ push_word({$random,$random}, 1, stream);
+ end
+ endtask
+
+ // Push a packet with a ramp on to the AXI Stream bus
+ // Args:
+ // - num_samps: Packet size.
+ // - ramp_start: Start value for the ramp
+ // - ramp_inc: Increment per clock cycle
+ // - hdr: Header to attach to packet (length will be ignored)
+ // - stream: Stream to use (Optional)
+ task automatic push_ramp_pkt (
+ input integer num_samps,
+ input logic [63:0] ramp_start,
+ input logic [63:0] ramp_inc,
+ input cvita_hdr_t hdr,
+ input int stream = 0);
+ begin
+ automatic integer counter = 0;
+ cvita_hdr_t tmp_hdr = hdr;
+ tmp_hdr.length = (num_samps * 8) + (hdr.has_time ? 16 : 8);
+ @(negedge clk);
+ push_hdr(tmp_hdr);
+ if (hdr.has_time) push_word(hdr.timestamp, 0, stream);
+ repeat(num_samps-1) begin
+ push_word(ramp_start+(counter*ramp_inc), 0, stream);
+ counter = counter + 1;
+ end
+ push_word(ramp_start+(counter*ramp_inc), 1, stream);
+ end
+ endtask
+
+ // Push a packet on to the AXI Stream bus.
+ // Args:
+ // - pkt: Packet data (queue)
+ // - stream: Stream to use (Optional)
+ task automatic push_pkt (
+ input cvita_pkt_t pkt,
+ input int stream = 0);
+ begin
+ // Use $ceil() to match packets with partial lines
+ int pkt_size;
+ // Vivado XSIM workaround
+ cvita_payload_t payload = pkt.payload;
+ pkt_size = (pkt.hdr.has_time ? payload.size() + 2 : payload.size() + 1);
+ assert (pkt.hdr.length == 8*pkt_size || pkt.hdr.length == 8*pkt_size-4)
+ else $error("cvita_master::push_pkt(): Packet size does not match packet length in header! Header: %0d, Actual: %0d",
+ pkt.hdr.length, 8*pkt_size);
+ if (pkt.hdr.length == 8) begin
+ push_word(pkt.hdr[127:64], 1, stream);
+ end else begin
+ push_word(pkt.hdr[127:64], 0, stream);
+ if (pkt.hdr.has_time) begin
+ push_word(pkt.hdr[63:0], (pkt.hdr.length == 16), stream);
+ end
+ for (int i = 0; i < pkt.payload.size()-1; i = i + 1) begin
+ push_word(pkt.payload[i], 0, stream);
+ end
+ push_word(pkt.payload[pkt.payload.size()-1], 1, stream);
+ end
+ end
+ endtask
+
+endinterface
+
+
+// Interface and tasks to receive CVITA packets over a slave AXI-Stream bus
+interface cvita_slave #(parameter DWIDTH = 64, parameter NUM_STREAMS = 1)(input clk);
+ axis_t #(.DWIDTH(DWIDTH), .NUM_STREAMS(NUM_STREAMS)) axis(.clk(clk));
+
+ // Check that stream is actually in use
+ function void check_stream(int stream);
+ assert (stream < NUM_STREAMS) else
+ $error("cvita_slave::check_stream(): Tried to perform operation on unused stream %0d", stream);
+ endfunction
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ begin
+ axis.tready = 0;
+ end
+ endtask
+
+ // Accept a sample on 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 [DWIDTH-1:0] word,
+ output logic eop,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ if (clk) @(negedge clk);
+ axis.tready[stream] = 1;
+ while(~axis.tvalid[stream]) @(posedge clk);
+ word = axis.tdata[DWIDTH*stream +: DWIDTH];
+ eop = axis.tlast[stream];
+ @(negedge clk);
+ axis.tready[stream] = 0;
+ end
+ endtask
+
+ // Wait for a sample to be transferred on the AXI Stream
+ // bus and return the data and last. Note, this task only
+ // observes the bus and does not affect the AXI control
+ // signals.
+ // Args:
+ // - word: The data pulled from the bus
+ // - eop: End of packet (tlast)
+ // - stream: Stream to use (Optional)
+ task automatic copy_word (
+ output logic [DWIDTH-1:0] word,
+ output logic eop,
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(~(axis.tready[stream]&axis.tvalid[stream])) @(posedge clk); // Wait until sample is transferred
+ word = axis.tdata[DWIDTH*stream +: DWIDTH];
+ eop = axis.tlast[stream];
+ @(negedge clk);
+ end
+ endtask
+
+ // Wait for a bubble cycle on the AXI Stream bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic wait_for_bubble (
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(axis.tready[stream]&axis.tvalid[stream]) @(posedge clk);
+ @(negedge clk);
+ end
+ endtask
+
+ // Wait for a packet to finish on the bus
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic wait_for_pkt (
+ input int stream = 0);
+ begin
+ check_stream(stream);
+ while(~(axis.tready[stream]&axis.tvalid[stream]&axis.tlast[stream])) @(posedge clk);
+ @(negedge clk);
+ 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 stream = 0);
+ begin
+ cvita_payload_t payload; // Vivado XSIM work around
+ logic [63:0] word;
+ int i = 0;
+ logic eop = 0;
+ int pkt_size;
+
+ while(~eop) begin
+ pull_word(word, eop, stream);
+ if (i == 0) begin
+ pkt.hdr[127:64] = word;
+ end else if ((i == 1) && pkt.hdr.has_time) begin
+ pkt.hdr[63:0] = word;
+ end else begin
+ payload.push_back(word);
+ end
+ i++;
+ end
+ pkt.payload = payload;
+ pkt_size = (pkt.hdr.has_time ? payload.size() + 2 : payload.size() + 1);
+ assert (pkt.hdr.length == 8*pkt_size || pkt.hdr.length == 8*pkt_size-4)
+ else $error("cvita_slave::pull_pkt(): Packet size does not match packet length in header! Header: %0d, Actual: %0d",
+ pkt.hdr.length, pkt_size*8);
+ end
+ endtask
+
+ // Pull a packet from the AXI Stream bus and
+ // drop it instead of passing data to the user.
+ // Args:
+ // - stream: Stream to use (Optional)
+ task automatic drop_pkt (
+ input int stream = 0);
+ begin
+ cvita_pkt_t pkt;
+ pull_pkt(pkt, stream);
+ end
+ endtask
+
+ // Vivado XSIM workaround, cannot be made into a task (with ref inputs) due to segfault
+ `define WAIT_FOR_PKT_GET_INFO__UPDATE \
+ stats.count = stats.count + 1; \
+ stats.sum = stats.sum + axis.tdata[DWIDTH*stream +: DWIDTH]; \
+ stats.crc = stats.crc ^ axis.tdata[DWIDTH*stream +: DWIDTH]; \
+ if (axis.tdata < stats.min) stats.min = axis.tdata[DWIDTH*stream +: DWIDTH]; \
+ if (axis.tdata > stats.max) stats.max = axis.tdata[DWIDTH*stream +: DWIDTH];
+
+ // Wait for a packet to finish on the bus
+ task automatic wait_for_pkt_get_info (
+ output cvita_hdr_t hdr,
+ output cvita_stats_t stats,
+ input int stream = 0);
+ begin
+ automatic logic is_hdr = 1;
+ automatic logic is_time = 0;
+ stats.count = 32'h0;
+ stats.sum = 64'h0;
+ stats.min = 64'h7FFFFFFFFFFFFFFF;
+ stats.max = 64'h0;
+ stats.crc = 64'h0;
+ check_stream(stream);
+ @(posedge clk);
+ //Corner case. We are already looking at the end
+ //of a packet i.e. its just a header
+ if (axis.tready[stream]&axis.tvalid[stream]&axis.tlast[stream]) begin
+ unflatten_chdr_no_ts(axis.tdata[DWIDTH*stream +: DWIDTH], hdr);
+ @(negedge clk);
+ end else begin
+ while(~(axis.tready[stream]&axis.tvalid[stream]&axis.tlast[stream])) begin
+ if (axis.tready[stream]&axis.tvalid[stream]) begin
+ if (is_hdr) begin
+ unflatten_chdr_no_ts(axis.tdata[DWIDTH*stream +: DWIDTH], hdr);
+ is_time = hdr.has_time;
+ is_hdr = 0;
+ end else if (is_time) begin
+ hdr.timestamp = axis.tdata[DWIDTH*stream +: DWIDTH];
+ is_time = 0;
+ end else begin
+ `WAIT_FOR_PKT_GET_INFO__UPDATE
+ end
+ end
+ @(posedge clk);
+ end
+ `WAIT_FOR_PKT_GET_INFO__UPDATE
+ @(negedge clk);
+ end
+ end
+ endtask
+
+ `undef WAIT_FOR_PKT_GET_INFO__UPDATE
+
+endinterface
+
+`endif \ No newline at end of file
diff --git a/fpga/usrp3/sim/control/Makefile.srcs b/fpga/usrp3/sim/control/Makefile.srcs
new file mode 100644
index 000000000..88372ddd8
--- /dev/null
+++ b/fpga/usrp3/sim/control/Makefile.srcs
@@ -0,0 +1,10 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+
+##################################################
+# Control/Readback simulation libraries
+##################################################
+SIM_CONTROL_SRCS = $(abspath $(addprefix $(BASE_DIR)/../sim/control/, \
+sim_set_rb_lib.svh \
+))
diff --git a/fpga/usrp3/sim/control/sim_set_rb_lib.svh b/fpga/usrp3/sim/control/sim_set_rb_lib.svh
new file mode 100644
index 000000000..14169a9b5
--- /dev/null
+++ b/fpga/usrp3/sim/control/sim_set_rb_lib.svh
@@ -0,0 +1,169 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+`ifndef INCLUDED_SIM_SET_RB_LIB
+`define INCLUDED_SIM_SET_RB_LIB
+
+interface settings_bus_t #(
+ parameter SR_AWIDTH = 8,
+ parameter SR_DWIDTH = 32,
+ parameter RB_AWIDTH = 8,
+ parameter RB_DWIDTH = 64,
+ parameter NUM_BUSES = 1
+)(
+ input clk
+);
+ logic [NUM_BUSES-1:0] set_stb;
+ logic [NUM_BUSES*SR_AWIDTH-1:0] set_addr;
+ logic [NUM_BUSES*SR_DWIDTH-1:0] set_data;
+ logic [NUM_BUSES-1:0] rb_stb;
+ logic [NUM_BUSES*RB_AWIDTH-1:0] rb_addr;
+ logic [NUM_BUSES*RB_DWIDTH-1:0] rb_data;
+
+ modport master (output set_stb, output set_addr, output set_data,
+ input rb_stb, output rb_addr, input rb_data);
+ modport slave (input set_stb, input set_addr, input set_data,
+ output rb_stb, input rb_addr, output rb_data);
+endinterface
+
+interface settings_bus_master #(
+ parameter SR_AWIDTH = 8,
+ parameter SR_DWIDTH = 32,
+ parameter RB_AWIDTH = 8,
+ parameter RB_DWIDTH = 64,
+ parameter NUM_BUSES = 1,
+ parameter TIMEOUT = 65535 // readback() timeout
+)(
+ input clk
+);
+ settings_bus_t #(
+ .SR_AWIDTH(SR_AWIDTH), .SR_DWIDTH(SR_DWIDTH),
+ .RB_AWIDTH(RB_AWIDTH), .RB_DWIDTH(RB_DWIDTH),
+ .NUM_BUSES(NUM_BUSES))
+ settings_bus (.clk(clk));
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ settings_bus.set_stb = 0;
+ settings_bus.set_addr = 0;
+ settings_bus.set_data = 0;
+ settings_bus.rb_addr = 0;
+ endtask
+
+ // Push a transaction onto the settings bus
+ // Args:
+ // - set_addr: Settings bus address
+ // - set_data: Settings bus data
+ // - rb_addr: Readback bus address
+ task automatic write (
+ input logic [SR_AWIDTH-1:0] set_addr,
+ input logic [SR_DWIDTH-1:0] set_data,
+ input logic [RB_AWIDTH-1:0] rb_addr = 'd0,
+ input int bus = 0); // Optional
+ begin
+ if (clk) @(negedge clk);
+ settings_bus.set_stb[bus] = 1'b1;
+ settings_bus.set_addr[SR_AWIDTH*bus +: SR_AWIDTH] = set_addr;
+ settings_bus.set_data[SR_DWIDTH*bus +: SR_DWIDTH] = set_data;
+ settings_bus.rb_addr[RB_AWIDTH*bus +: RB_AWIDTH] = rb_addr;
+ @(negedge clk);
+ settings_bus.set_stb[bus] = 1'b0;
+ settings_bus.set_addr[SR_AWIDTH*bus +: SR_AWIDTH] = 'd0;
+ settings_bus.set_data[SR_DWIDTH*bus +: SR_DWIDTH] = 'd0;
+ settings_bus.rb_addr[RB_AWIDTH*bus +: RB_AWIDTH] = 'd0;
+ end
+ endtask
+
+ // Pull a transaction from the readback bus. Typically called immediately after write().
+ // Args:
+ // - rb_data: Readback data
+ task automatic readback (
+ output logic [RB_DWIDTH-1:0] rb_data,
+ input int bus = 0);
+ begin
+ integer timeout_counter = 0;
+ if (clk & ~settings_bus.rb_stb[bus]) @(negedge clk);
+ while (~settings_bus.rb_stb[bus]) begin
+ if (timeout_counter < TIMEOUT) begin
+ timeout_counter++;
+ end else begin
+ $error("settings_bus_t::readback(): Timeout waiting for readback strobe!");
+ break;
+ end
+ @(negedge clk);
+ end
+ rb_data = settings_bus.rb_data[RB_DWIDTH*bus +: RB_DWIDTH];
+ end
+ endtask
+
+endinterface
+
+interface settings_bus_slave #(
+ parameter SR_AWIDTH = 8,
+ parameter SR_DWIDTH = 32,
+ parameter RB_AWIDTH = 8,
+ parameter RB_DWIDTH = 64,
+ parameter NUM_BUSES = 1,
+ parameter TIMEOUT = 65535 // read() timeout
+)(
+ input clk
+);
+ settings_bus_t #(
+ .SR_AWIDTH(SR_AWIDTH), .SR_DWIDTH(SR_DWIDTH),
+ .RB_AWIDTH(RB_AWIDTH), .RB_DWIDTH(RB_DWIDTH),
+ .NUM_BUSES(NUM_BUSES))
+ settings_bus (.clk(clk));
+
+ // Reset signals / properties used by this interface
+ task automatic reset;
+ settings_bus.rb_stb = 0;
+ settings_bus.rb_data = 0;
+ endtask
+
+ // Pull a transaction from the settings bus
+ // Args:
+ // - set_addr: Settings bus address
+ // - set_data: Settings bus data
+ // - rb_addr: Readback bus address
+ task automatic read (
+ output logic [SR_AWIDTH-1:0] set_addr,
+ output logic [SR_DWIDTH-1:0] set_data,
+ output logic [RB_AWIDTH-1:0] rb_addr,
+ input int bus = 0);
+ begin
+ integer timeout_counter = 0;
+ while (~settings_bus.set_stb[bus]) begin
+ @(negedge clk);
+ if (timeout_counter < TIMEOUT) begin
+ timeout_counter++;
+ end else begin
+ $error("settings_bus_t::read(): Timeout waitling for settings bus strobe!");
+ break;
+ end
+ end
+ set_addr = settings_bus.set_addr[SR_AWIDTH*bus +: SR_AWIDTH];
+ set_data = settings_bus.set_data[SR_DWIDTH*bus +: SR_DWIDTH];
+ rb_addr = settings_bus.rb_addr[RB_AWIDTH*bus +: RB_AWIDTH];
+ @(negedge clk);
+ end
+ endtask
+
+ // Push a transaction onto the readback bus, typically called immediately after read()
+ // Args:
+ // - rb_data: Readback data
+ task writeback (
+ input logic [RB_AWIDTH-1:0] rb_data,
+ input int bus = 0);
+ begin
+ if (clk & ~settings_bus.set_stb[bus]) @(negedge clk);
+ settings_bus.rb_stb[bus] = 1'b1;
+ settings_bus.rb_data[RB_DWIDTH*bus +: RB_DWIDTH] = rb_data;
+ @(negedge clk);
+ settings_bus.rb_stb[bus] = 1'b0;
+ settings_bus.rb_data[RB_DWIDTH*bus +: RB_DWIDTH] = 'd0;
+ end
+ endtask
+
+endinterface
+
+`endif \ No newline at end of file
diff --git a/fpga/usrp3/sim/general/Makefile.srcs b/fpga/usrp3/sim/general/Makefile.srcs
new file mode 100644
index 000000000..90ce1a028
--- /dev/null
+++ b/fpga/usrp3/sim/general/Makefile.srcs
@@ -0,0 +1,13 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+
+##################################################
+# General Simulation Libraries/Headers
+##################################################
+SIM_GENERAL_SRCS = $(abspath $(addprefix $(BASE_DIR)/../sim/general/, \
+sim_clks_rsts.vh \
+sim_exec_report.vh \
+sim_math.vh \
+sim_file_io.svh \
+))
diff --git a/fpga/usrp3/sim/general/sim_clks_rsts.vh b/fpga/usrp3/sim/general/sim_clks_rsts.vh
new file mode 100644
index 000000000..62de98faa
--- /dev/null
+++ b/fpga/usrp3/sim/general/sim_clks_rsts.vh
@@ -0,0 +1,94 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+
+// Generates a persistent clock that starts at t=0 and runs forever
+//
+// Usage: `DEFINE_CLK(clk_name,period,duty_cycle)
+// where
+// - clk_name: The clock net to be generated
+// - period: Period of the clock in simulator ticks
+// - duty_cycle: Percentage duty cycle
+//
+`define DEFINE_CLK(clk_name, period, duty_cycle) \
+ reg clk_name = 0; \
+ always begin \
+ clk_name = 1; \
+ #((1.0*period)*(1.0*duty_cycle/100)); \
+ clk_name = 0; \
+ #((1.000*period)*(1.0-(1.0*duty_cycle/100))); \
+ end
+
+// Generates a persistent clock that starts at t=0 and runs forever
+//
+// Usage: `DEFINE_CLK(clk_name,period,duty_cycle)
+// where
+// - clk_name: The clock net to be generated
+// - period: Period of the clock in simulator ticks
+// - duty_cycle: Percentage duty cycle
+//
+`define DEFINE_DIFF_CLK(clk_name_p, clk_name_n, period, duty_cycle) \
+ reg clk_name_p = 0; \
+ reg clk_name_n = 1; \
+ always begin \
+ clk_name_p = 1; \
+ clk_name_n = 0; \
+ #((1.0*period)*(1.0*duty_cycle/100)); \
+ clk_name_p = 0; \
+ clk_name_n = 1; \
+ #((1.000*period)*(1.0-(1.0*duty_cycle/100))); \
+ end
+
+// Generates a clock that starts at the specified time and runs forever
+//
+// Usage: `DEFINE_LATE_START_CLK(clk_name,period,duty_cycle,start_time,start_time_res)
+// where
+// - clk_name: The clock net to be generated
+// - period: Period of the clock in simulator ticks
+// - duty_cycle: Percentage duty cycle
+// - start_time: Start time for clock in simulator ticks
+// - start_time_res: Start time resolution (must be > timescale increment and < start_time)
+//
+`define DEFINE_LATE_START_CLK(clk_name, period, duty_cycle, start_time, start_time_res) \
+ reg clk_name = 0; \
+ reg clk_name``_locked = 0; \
+ always begin \
+ while (!clk_name``_locked) #start_time_res; \
+ clk_name = 1; \
+ #((1.0*period)*(1.0*duty_cycle/100)); \
+ clk_name = 0; \
+ #((1.000*period)*(1.0-(1.0*duty_cycle/100))); \
+ end \
+ initial begin \
+ #(start_time) clk_name``_locked = 1; \
+ end
+
+// Generates an active high reset
+//
+// Usage: `DEFINE_RESET(reset_name,reset_time,reset_duration)
+// where
+// - reset_name: The reset net to be generated
+// - reset_time: Time at which reset will be asserted (i.e. rst=1)
+// - reset_duration: Duration of reset assertion
+//
+`define DEFINE_RESET(reset_name, reset_time, reset_duration) \
+ reg reset_name = (reset_time==0); \
+ initial begin \
+ #(reset_time) reset_name = 1; \
+ #(reset_time+reset_duration) reset_name = 0; \
+ end
+
+// Generates an active low reset
+//
+// Usage: `DEFINE_RESET_N(reset_name,reset_time,reset_duration)
+// where
+// - reset_name: The reset net to be generated
+// - reset_time: Time at which reset will be asserted (i.e. rst=0)
+// - reset_duration: Duration of reset assertion
+//
+`define DEFINE_RESET_N(reset_name, reset_time, reset_duration) \
+ reg reset_name = (reset_time!=0); \
+ initial begin \
+ #(reset_time) reset_name = 0; \
+ #(reset_time+reset_duration) reset_name = 1; \
+ end
diff --git a/fpga/usrp3/sim/general/sim_exec_report.vh b/fpga/usrp3/sim/general/sim_exec_report.vh
new file mode 100644
index 000000000..a6ede6a25
--- /dev/null
+++ b/fpga/usrp3/sim/general/sim_exec_report.vh
@@ -0,0 +1,138 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+
+// Initializes state for a test bench.
+// This macro *must be* called within the testbench module but
+// outside the primary initial block
+// Its sets up boilerplate code for:
+// - Logging to console
+// - Test execution tracking
+// - Gathering test results
+// - Bounding execution time based on the SIM_TIMEOUT_US vdef
+
+`ifndef SIM_TIMEOUT_US
+`define SIM_TIMEOUT_US 100000 // Default: 100 ms
+`endif
+
+// Usage: `TEST_BENCH_INIT(test_name,min_tc_run_count,ns_per_tick)
+// where
+// - tb_name: Name of the testbench. (Only used during reporting)
+// - min_tc_run_count: Number of test cases in testbench. (Used to detect stalls and inf-loops)
+// - ns_per_tick: The time_unit_base from the timescale declaration
+//
+`define TEST_BENCH_INIT(tb_name, min_tc_run_count, ns_per_tick) \
+ localparam sim_time_increment = 100; \
+ reg tc_running = 0; \
+ reg tc_failed = 0; \
+ reg tc_all_done = 0; \
+ real sim_time = 0.0; \
+ integer tc_run_count = 0; \
+ integer tc_pass_count = 0; \
+ \
+ initial begin : tb_timekeeper \
+ #0; \
+ $timeformat(-9, 0, " ns", 10); \
+ $display("========================================================"); \
+ $display("TESTBENCH STARTED: %s", tb_name); \
+ $display("========================================================"); \
+ if (1000.0*`SIM_TIMEOUT_US < sim_time_increment) begin \
+ $error("Total simulation time less than simulation step size!"); \
+ end \
+ tc_running = 0; \
+ tc_failed = 0; \
+ tc_run_count = 0; \
+ tc_pass_count = 0; \
+ while (~tc_all_done & sim_time < 1000.0*`SIM_TIMEOUT_US) begin \
+ #(sim_time_increment); \
+ sim_time += sim_time_increment; \
+ end \
+ $display("========================================================"); \
+ $display("TESTBENCH FINISHED: %s", tb_name); \
+ $display(" - Time elapsed: %0t%s", $realtime(), (sim_time >= 1000.0*`SIM_TIMEOUT_US) ? " (Timed out!)" : ""); \
+ $display(" - Tests Expected: %0d", min_tc_run_count); \
+ $display(" - Tests Run: %0d", tc_run_count); \
+ $display(" - Tests Passed: %0d", tc_pass_count); \
+ $display("Result: %s", ((tc_run_count>=min_tc_run_count)&&(tc_run_count==tc_pass_count)?"PASSED ":"FAILED!!!")); \
+ $display("========================================================"); \
+ $finish; \
+ end
+
+// Ends test bench. Place after final test.
+//
+// Usage: `TEST_BENCH_DONE
+`define TEST_BENCH_DONE tc_all_done = 1;
+
+// Indicates the start of a test case
+// This macro *must be* called inside the primary initial block
+//
+// Usage: `TEST_CASE_START(test_name)
+// where
+// - test_name: The name of the test.
+//
+`define TEST_CASE_START(test_name) \
+ #0; \
+ tc_running = 1; \
+ tc_failed = 0; \
+ tc_run_count = tc_run_count + 1; \
+ $display("[TEST CASE %3d] (t=%09d) BEGIN: %s...", tc_run_count, $time, test_name);
+
+// Indicates the end of a test case
+// This macro *must be* called inside the primary initial block
+// The pass/fail status of test case is determined based on the
+// the user specified outcome and the number of fatal or error
+// ASSERTs triggered in the test case.
+//
+// Usage: `TEST_CASE_DONE(test_result)
+// where
+// - test_result: User specified outcome
+//
+`define TEST_CASE_DONE(result) \
+ #0; \
+ tc_running = 0; \
+ $display("[TEST CASE %3d] (t=%09d) DONE... %s", tc_run_count, $time, ((((result)===1'b1)&~tc_failed)?"Passed":"FAILED")); \
+ if (((result)===1'b1)&~tc_failed) tc_pass_count = tc_pass_count + 1;
+
+// Wrapper around a an assert.
+// ASSERT_FATAL throws an error assertion and halts the simulator
+// if cond is not satisfied
+//
+// Usage: `ASSERT_FATAL(cond,msg)
+// where
+// - cond: Condition for the assert
+// - msg: Message for the assert
+//
+`define ASSERT_FATAL(cond, msg) \
+ assert(cond) else begin \
+ tc_failed = 1; \
+ $error(msg); \
+ tc_all_done = 1; \
+ #(sim_time_increment); \
+ end
+
+// Wrapper around a an assert.
+// ASSERT_ERROR throws an error assertion and fails the test case
+// if cond is not satisfied. The simulator will *not* halt
+//
+// Usage: `ASSERT_ERROR(cond,msg)
+// where
+// - cond: Condition for the assert
+// - msg: Message for the assert
+//
+`define ASSERT_ERROR(cond, msg) \
+ assert(cond) else begin \
+ tc_failed = 1; \
+ $error(msg); \
+ end
+
+// Wrapper around a an assert.
+// ASSERT_WARNING throws an warning assertion but does not fail the
+// test case if cond is not satisfied. The simulator will *not* halt
+//
+// Usage: `ASSERT_WARNING(cond,msg)
+// where
+// - cond: Condition for the assert
+// - msg: Message for the assert
+//
+`define ASSERT_WARN(cond, msg) \
+ assert(cond) else $warning(msg);
diff --git a/fpga/usrp3/sim/general/sim_file_io.svh b/fpga/usrp3/sim/general/sim_file_io.svh
new file mode 100644
index 000000000..97fadd741
--- /dev/null
+++ b/fpga/usrp3/sim/general/sim_file_io.svh
@@ -0,0 +1,125 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+
+`ifndef WORKING_DIR
+ `define WORKING_DIR "."
+`endif
+
+`define ABSPATH(name) {`WORKING_DIR, "/", name}
+
+typedef enum {
+ READ, WRITE, APPEND
+} fopen_mode_t;
+
+typedef enum {
+ HEX, DEC, OCT, BIN, FLOAT
+} fformat_t;
+
+// Create a handle to a data_file with
+// - FILENAME: Name of the file
+// - FORMAT: Data format (HEX, DEC, OCT, BIN, FLOAT)
+// - DWIDTH: Width of each element stored in the file (one line per word)
+//
+
+//TODO: We would ideally use a class but that is not
+// supported by most simulators.
+interface data_file_t #(
+ parameter FILENAME = "test.hex",
+ parameter FORMAT = HEX,
+ parameter DWIDTH = 64
+) (input clk);
+ bit is_open;
+ integer handle;
+
+ // Open the data file for reading or writing.
+ //
+ // Usage: open(mode)
+ // where
+ // - mode: RW mode (Choose from: READ, WRITE, APPEND)
+ //
+ function open(fopen_mode_t mode = READ);
+ if (mode == APPEND)
+ handle = $fopen(`ABSPATH(FILENAME), "a");
+ else if (mode == WRITE)
+ handle = $fopen(`ABSPATH(FILENAME), "w");
+ else
+ handle = $fopen(`ABSPATH(FILENAME), "r");
+
+ if (handle == 0) begin
+ $error("Could not open file: %s", `ABSPATH(FILENAME));
+ $finish();
+ end
+ is_open = 1;
+ endfunction
+
+
+ // Close an open data file. No-op if file isn't already open
+ //
+ // Usage: close()
+ //
+ function close();
+ $fclose(handle);
+ handle = 0;
+ is_open = 0;
+ endfunction
+
+ // Is end-of-file reached.
+ //
+ // Usage: is_eof() Returns eof
+ // where
+ // - eof: A boolean
+ //
+ function logic is_eof();
+ return ($feof(handle));
+ endfunction
+
+ // Read a line from the datafile
+ //
+ // Usage: readline() Returns data
+ // where
+ // - data: A logic array of width DWIDTH containing the read word
+ //
+ function logic [DWIDTH-1:0] readline();
+ automatic logic [DWIDTH-1:0] word = 64'h0;
+ automatic integer status;
+
+ if (FORMAT == HEX)
+ status = $fscanf(handle, "%x\n", word);
+ else if (FORMAT == DEC)
+ status = $fscanf(handle, "%d\n", word);
+ else if (FORMAT == OCT)
+ status = $fscanf(handle, "%o\n", word);
+ else if (FORMAT == BIN)
+ status = $fscanf(handle, "%b\n", word);
+ else if (FORMAT == DEC)
+ status = $fscanf(handle, "%g\n", word);
+ else
+ $error("Invalid format");
+
+ return word;
+ endfunction
+
+ // Write a line to the datafile
+ //
+ // Usage: writeline(data)
+ // where
+ // - data: A logic array of width DWIDTH to write to the file
+ //
+ function void writeline(logic [DWIDTH-1:0] word);
+ if (FORMAT == HEX)
+ $fdisplay(handle, "%x", word);
+ else if (FORMAT == DEC)
+ $fdisplay(handle, "%d", word);
+ else if (FORMAT == OCT)
+ $fdisplay(handle, "%o", word);
+ else if (FORMAT == BIN)
+ $fdisplay(handle, "%b", word);
+ else if (FORMAT == DEC)
+ $fdisplay(handle, "%g", word);
+ else
+ $error("Invalid format");
+ endfunction
+
+endinterface
+
diff --git a/fpga/usrp3/sim/general/sim_math.vh b/fpga/usrp3/sim/general/sim_math.vh
new file mode 100644
index 000000000..a08870740
--- /dev/null
+++ b/fpga/usrp3/sim/general/sim_math.vh
@@ -0,0 +1,276 @@
+// All code take from the HDLCon paper:
+// "Verilog Transcendental Functions for Numerical Testbenches"
+//
+// Authored by:
+// Mark G. Arnold marnold@co.umist.ac.uk,
+// Colin Walter c.walter@co.umist.ac.uk
+// Freddy Engineer freddy.engineer@xilinx.com
+//
+
+
+
+// The sine function is approximated with a polynomial which works
+// for -π/2 < x < π/2. (This polynomial, by itself, was used as a
+// Verilog example in [2]; unfortunately there was a typo with the
+// coefficients. The correct coefficients together with an error
+// analysis are given in [3].) For arguments outside of -π/2 < x < π/2,
+// the identities sin(x) = -sin(-x) and sin(x) = -sin(x-Ï€) allow the
+// argument to be shifted to be within this range. The latter identity
+// can be applied repeatedly. Doing so could cause inaccuracies for
+// very large arguments, but in practice the errors are acceptable
+// if the Verilog simulator uses double-precision floating point.
+
+function real sin;
+ input x;
+ real x;
+ real x1,y,y2,y3,y5,y7,sum,sign;
+ begin
+ sign = 1.0;
+ x1 = x;
+ if (x1<0)
+ begin
+ x1 = -x1;
+ sign = -1.0;
+ end
+ while (x1 > 3.14159265/2.0)
+ begin
+ x1 = x1 - 3.14159265;
+ sign = -1.0*sign;
+ end
+ y = x1*2/3.14159265;
+ y2 = y*y;
+ y3 = y*y2;
+ y5 = y3*y2;
+ y7 = y5*y2;
+ sum = 1.570794*y - 0.645962*y3 +
+ 0.079692*y5 - 0.004681712*y7;
+ sin = sign*sum;
+ end
+endfunction
+
+// The cosine and tangent are computed from the sine:
+function real cos;
+ input x;
+ real x;
+ begin
+ cos = sin(x + 3.14159265/2.0);
+ end
+endfunction
+
+
+function real tan;
+ input x;
+ real x;
+ begin
+ tan = sin(x)/cos(x);
+ end
+endfunction
+
+// The base-two exponential (antilogarithm) function, 2x, is computed by
+// examining the bits of the argument, and for those bits of the argument
+// that are 1, multiplying the result by the corresponding power of a base
+// very close to one. For example, if there were only two bits after
+// the radix point, the base would be the fourth root of two, 1.1892.
+// This number is squared on each iteration: 1.4142, 2.0, 4.0, 16.0.
+// So, if x is 101.112, the function computes 25.75 as 1.1892*1.4142*2.0*16.0 = 53.81.
+// In general, for k bits of precision, the base would be the 2k root of two.
+// Since we need about 23 bits of accuracy for our function, the base we use
+// is the 223 root of two, 1.000000082629586. This constant poses a problem
+// to some Verilog parsers, so we construct it in two parts. The following
+// function computes the appropriate root of two by repeatedly squaring this constant:
+
+function real rootof2;
+ input n;
+ integer n;
+ real power;
+ integer i;
+
+ begin
+ power = 0.82629586;
+ power = power / 10000000.0;
+ power = power + 1.0;
+ i = -23;
+
+ if (n >= 1)
+ begin
+ power = 2.0;
+ i = 0;
+ end
+
+ for (i=i; i< n; i=i+1)
+ begin
+ power = power * power;
+ end
+ rootof2 = power;
+ end
+endfunction // if
+
+// This function is used for computing both antilogarithms and logarithms.
+// This routine is never called with n less than -23, thus no validity check
+// need be performed. When n>0, the exponentiation begins with 2.0 in order to
+// improve accuracy.
+// For computing the antilogarithm, we make use of the identity ex = 2x/ln(2),
+// and then proceed as in the example above. The constant 1/ln(2) = 1.44269504.
+// Here is the natural exponential function:
+
+function real exp;
+ input x;
+ real x;
+ real x1,power,prod;
+ integer i;
+ begin
+ x1 = fabs(x)*1.44269504;
+ if (x1 > 255.0)
+ begin
+ exp = 0.0;
+ if (x>0.0)
+ begin
+ $display("exp illegal argument:",x);
+ $stop;
+ end
+ end
+ else
+ begin
+ prod = 1.0;
+ power = 128.0;
+ for (i=7; i>=-23; i=i-1)
+ begin
+ if (x1 > power)
+ begin
+ prod = prod * rootof2(i);
+ x1 = x1 - power;
+ end
+ power = power / 2.0;
+ end
+ if (x < 0)
+ exp = 1.0/prod;
+ else
+ exp = prod;
+ end
+ end
+endfunction // fabs
+
+// The function prints an error message if the argument is too large
+// (greater than about 180). All error messages in this package are
+// followed by $stop to allow the designer to use the debugging
+// features of Verilog to determine the cause of the error, and
+// possibly to resume the simulation. An argument of less than
+// about –180 simply returns zero with no error. The main loop
+// assumes a positive argument. A negative argument is computed as 1/e-x.
+// The logarithm function prints an error message for arguments less
+// than or equal to zero because the real-valued logarithm is not
+// defined for such arguments. The loop here requires an argument
+// greater than or equal to one. For arguments between zero and one,
+// this code uses the identity ln(1/x) = -ln(x).
+
+function real log;
+ input x;
+ real x;
+ real re,log2;
+ integer i;
+ begin
+ if (x <= 0.0)
+ begin
+ $display("log illegal argument:",x);
+ $stop;
+ log = 0;
+ end
+ else
+ begin
+ if (x<1.0)
+ re = 1.0/x;
+ else
+ re = x;
+ log2 = 0.0;
+ for (i=7; i>=-23; i=i-1)
+ begin
+ if (re > rootof2(i))
+ begin
+ re = re/rootof2(i);
+ log2 = 2.0*log2 + 1.0;
+ end
+ else
+ log2 = log2*2;
+ end
+ if (x < 1.0)
+ log = -log2/12102203.16;
+ else
+ log = log2/12102203.16;
+ end
+ end
+endfunction
+
+// The code only divides re by rootof2(i) when the re is larger
+// (so that the quotient will be greater than 1.0). Each time
+// such a division occurs, a bit that is 1 is recorded in the
+// whole number result (multiply by 2 and add 1). Otherwise,
+// a zero is recorded (multiply by 2). At the end of the loop,
+// log2 will contain 223 log2|x|. We divide by 223 and use the
+// identity ln(x) = log2(x)/log2(e). The constant 12102203.16 is 223 log2(e).
+// The log(x) and exp(x)functions are used to implement the pow(x,y) and sqrt(x) functions:
+
+function real pow;
+ input x,y;
+ real x,y;
+ begin
+ if (x<0.0)
+ begin
+ $display("pow illegal argument:",x);
+ $stop;
+ end
+ pow = exp(y*log(x));
+ end
+endfunction
+
+function real sqrt;
+ input x;
+ real x;
+ begin
+ if (x<0.0)
+ begin
+ $display("sqrt illegal argument:",x);
+ $stop;
+ end
+ sqrt = exp(0.5*log(x));
+ end
+endfunction
+
+// The arctangent [3,7] is computed as a continued fraction,
+// using the identities tan-1(x) = -tan-1(-x) and tan-1(x) = π/2 - tan-1(1/x)
+// to reduce the range to 0 < x < 1:
+
+function real atan;
+ input x;
+ real x;
+ real x1,x2,sign,bias;
+ real d3,s3;
+ begin
+ sign = 1.0;
+ bias = 0.0;
+ x1 = x;
+ if (x1 < 0.0)
+ begin
+ x1 = -x1;
+ sign = -1.0;
+ end
+ if (x1 > 1.0)
+ begin
+ x1 = 1.0/x1;
+ bias = sign*3.14159265/2.0;
+ sign = -1.0*sign;
+ end
+ x2 = x1*x1;
+ d3 = x2 + 1.44863154;
+ d3 = 0.26476862 / d3;
+ s3 = x2 + 3.3163354;
+ d3 = s3 - d3;
+ d3 = 7.10676 / d3;
+ s3 = 6.762139 + x2;
+ d3 = s3 - d3;
+ d3 = 3.7092563 / d3;
+ d3 = d3 + 0.17465544;
+ atan = sign*x1*d3+bias;
+ end
+endfunction
+
+// The other functions (asin(x) and acos(x)) are computed from the arctangent.
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