diff options
Diffstat (limited to 'fpga/usrp3/sim')
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 |