diff options
20 files changed, 4195 insertions, 78 deletions
diff --git a/fpga/usrp3/lib/axi4s_sv/AxiStreamIf.sv b/fpga/usrp3/lib/axi4s_sv/AxiStreamIf.sv new file mode 100644 index 000000000..cfba6d2eb --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/AxiStreamIf.sv @@ -0,0 +1,594 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Interface: AxiStreamIf +// Description: +// AXI4-Stream is an ARM standard for representing streams or packets in +// a design. For more information on the spec see: +// https://static.docs.arm.com/ihi0051/a/IHI0051A_amba4_axi4_stream_v1_0_protocol_spec.pdf +// +// The interface contains methods for +// (1) Monitoring progress of a packet +// (2) Extracting fields from a packet +// (3) Overwriting fields in a packet +// +// Parameters: +// - DATA_WIDTH - Width of tdata on AXI4S bus +// - USER_WIDTH - Width of tuser on AXI4S bus +// - MAX_PACKET_BYTES - Maximum number of bytes between tlast. If 0, no +// word_count resources are added. +// - TDATA - use tData if 1 +// - TUSER - use tUser if 1 +// - TKEEP - use tKeep if 1 +// - TLAST - use tLast if 1 +// +// Some Historic USRP code uses tuser without tkeep frequently. +// The usage of tuser varies, here are some examples: +// (1) Instead of using tkeep, user can encode number of valid bytes in a word +// (2) The MSB can indicate an FCS failure from the MAC +// (3) Header information is passed from xport to chdr system +// + +//----------------------------------------------------------------------------- +// Unidirectional AXI4-Stream interface +//----------------------------------------------------------------------------- + +// Minimal - Interface Capable of continuous assignment +interface AxiStreamIf #( + int DATA_WIDTH = 64, + + int USER_WIDTH = 1, + int MAX_PACKET_BYTES = 0, + bit TDATA = 1, + bit TUSER = 1, + bit TKEEP = 1, + bit TLAST = 1 +) ( + input logic clk, + input logic rst = 1'b0 +); + +// We don't want to check this, but this is an example of how to +// check an input parameter +// initial begin +// assert (DATA_WIDTH % 8 == 0) else begin +// $display("DATA_WIDTH == %1d",DATA_WIDTH); +// $fatal(1,"Invalid data width on AxiStreamIf"); +// end +// end + + localparam BYTES_PER_WORD = DATA_WIDTH/8; + localparam DWIDTH = TDATA ? DATA_WIDTH : 0; + localparam UWIDTH = TUSER ? USER_WIDTH : 0; + localparam KWIDTH = TKEEP ? DATA_WIDTH/8 : 0; + localparam LWIDTH = TLAST ? 1 : 0; + localparam PACKED_WIDTH = DWIDTH + UWIDTH + KWIDTH + LWIDTH ; + + // local type defs + typedef logic [DATA_WIDTH-1:0] data_t; + typedef logic [USER_WIDTH-1:0] user_t; + typedef logic [DATA_WIDTH/8-1:0] keep_t; + + // Signals that make up a unidirectional AXI-Stream interface + logic tlast; + logic tvalid; + logic tready; + data_t tdata; + user_t tuser; + keep_t tkeep; + + //--------------------------------------- + // Trailing Bytes/Keep functions + // Trailing byte of 0 means full word else trailing bytes is the + // number of bytes in the last word. + //--------------------------------------- + // bits needed to represent trailing bytes + localparam TRAILING_WIDTH = $clog2(DATA_WIDTH/8); + typedef logic [TRAILING_WIDTH-1:0] trailing_bytes_t; + + // translate between bytes and tkeep + function automatic trailing_bytes_t keep2trailing(keep_t keep); + trailing_bytes_t bytes = '0; + if (tlast == 1) begin + // mux between values based on the high bit = 1 + for(int b = 0; b < DATA_WIDTH/8 ; b++) begin + if (keep[b]) bytes = b+1; + end + end + return bytes; + endfunction : keep2trailing + + function automatic keep_t trailing2keep(trailing_bytes_t bytes); + keep_t keep = '1; + if (tlast == 1 && bytes != 0) begin + foreach(keep[b]) begin + keep[b] = bytes > b; + end + end + return keep; + endfunction : trailing2keep + + // View from the master side + modport master ( + input clk, rst, + output tvalid, tdata, tuser, tkeep, tlast, + input tready, + import keep2trailing, + import trailing2keep + ); + + // View from the slave side + modport slave ( + input clk, rst, + input tvalid, tdata, tuser, tkeep, tlast, + output tready, + import keep2trailing, + import trailing2keep + ); + + +endinterface : AxiStreamIf + +// Full featured for packet modification. Must use procedural assignment. +interface AxiStreamPacketIf #( + int DATA_WIDTH = 64, + int USER_WIDTH = 1, + int MAX_PACKET_BYTES = 0, + bit TDATA = 1, + bit TUSER = 1, + bit TKEEP = 1, + bit TLAST = 1 +) ( + input logic clk, + input logic rst = 1'b0 +); + +// We don't want to check this, but this is an example of how to +// check an input parameter: +// initial begin +// assert (DATA_WIDTH % 8 == 0) else begin +// $display("DATA_WIDTH == %1d",DATA_WIDTH); +// $fatal(1,"Invalid data width on AxiStreamIf"); +// end +// end + + localparam BYTES_PER_WORD = DATA_WIDTH/8; + localparam MAX_PACKET_WORDS = MAX_PACKET_BYTES/BYTES_PER_WORD; + // local type defs + typedef logic [DATA_WIDTH-1:0] data_t; + typedef logic [USER_WIDTH-1:0] user_t; + typedef logic [DATA_WIDTH/8-1:0] keep_t; + typedef logic [$clog2(MAX_PACKET_WORDS)-1:0] word_count_t; + + // Signals that make up a unidirectional AXI-Stream interface + logic tlast; + logic tvalid; + logic tready; + data_t tdata; + user_t tuser; + keep_t tkeep; + word_count_t word_count = 0; + + if (MAX_PACKET_BYTES>0) begin + always_ff @(posedge clk) begin + if (rst) begin + word_count <= 0; + end else begin + // reset at last + if (tlast && tvalid && tready) begin + word_count <= 0; + // increment when valid and ready + end else if (tvalid && tready)begin + word_count <= word_count+1; + end + end + end + end + + //--------------------------------------- + // Packing functions + //--------------------------------------- + + localparam DWIDTH = TDATA ? DATA_WIDTH : 0; + localparam UWIDTH = TUSER ? USER_WIDTH : 0; + localparam KWIDTH = TKEEP ? DATA_WIDTH/8 : 0; + localparam LWIDTH = TLAST ? 1 : 0; + localparam PACKED_WIDTH = DWIDTH + UWIDTH + KWIDTH + LWIDTH ; + typedef logic [PACKED_WIDTH-1:0] packed_t; + + function automatic packed_t pack(INC_DATA=1,INC_USER=1,INC_KEEP=1,INC_LAST=1); + logic [PACKED_WIDTH-1:0] data = 'X; + + int USTART; + int KSTART; + int LSTART; + + USTART = INC_DATA ? DWIDTH :0; + KSTART = INC_USER ? USTART+UWIDTH :USTART; + LSTART = INC_KEEP ? KSTART+KWIDTH :KSTART; + + if (TDATA && INC_DATA) begin + // in the LSB + data[0+:DATA_WIDTH] = tdata; + end + if (TUSER && INC_USER) begin + // in the 1st MIDDLE + data[USTART+:USER_WIDTH] = tuser; + end + if (TKEEP && INC_KEEP) begin + // in the 2nd MIDDLE + data[KSTART+:DATA_WIDTH/8] = tkeep; + end + if (TLAST && INC_LAST) begin + // in the MSB + data[LSTART] = tlast; + end + return data; + endfunction : pack + + task automatic unpack (packed_t data,INC_DATA=1,INC_USER=1,INC_KEEP=1,INC_LAST=1); + + int USTART; + int KSTART; + int LSTART; + + USTART = INC_DATA ? DWIDTH :0; + KSTART = INC_USER ? USTART+UWIDTH :USTART; + LSTART = INC_KEEP ? KSTART+KWIDTH :KSTART; + + if (TDATA && INC_DATA) begin + // in the LSB + tdata = data[0+:DATA_WIDTH]; + end + if (TUSER && INC_USER) begin + // in the 1st MIDDLE + tuser = data[USTART+:USER_WIDTH]; + end + if (TKEEP && INC_KEEP) begin + // in the 2nd MIDDLE + tkeep = data[KSTART+:DATA_WIDTH/8]; + end + if (TLAST && INC_LAST) begin + // in the MSB + tlast = data[LSTART]; + end + endtask : unpack + + //--------------------------------------- + // Trailing Bytes/Keep functions + // Trailing byte of 0 means full word else trailing bytes is the + // number of bytes in the last word. + //--------------------------------------- + // bits needed to represent trailing bytes + localparam TRAILING_WIDTH = $clog2(DATA_WIDTH/8); + typedef logic [TRAILING_WIDTH-1:0] trailing_bytes_t; + + // translate between bytes and tkeep + function automatic trailing_bytes_t keep2trailing(keep_t keep); + trailing_bytes_t bytes = '0; + if (tlast == 1) begin + // mux between values based on the high bit = 1 + for(int b = 0; b < DATA_WIDTH/8 ; b++) begin + if (keep[b]) bytes = b+1; + end + end + return bytes; + endfunction : keep2trailing + + function automatic keep_t trailing2keep(trailing_bytes_t bytes); + keep_t keep = '1; + if (tlast == 1 && bytes != 0) begin + foreach(keep[b]) begin + keep[b] = bytes > b; + end + end + return keep; + endfunction : trailing2keep + + // do the translation directly to/from the ifc + function automatic keep_t get_trailing_bytes(); + localparam USER_TRAILING_WIDTH = + USER_WIDTH >= TRAILING_WIDTH ? TRAILING_WIDTH : USER_WIDTH; + assert (TUSER) else + $fatal("Can't get trailing if TUSER doesn't exist"); + assert (USER_WIDTH >= TRAILING_WIDTH) else + $fatal("USER_WIDTH is to narrow to contain trailing"); + return trailing2keep(tuser[USER_TRAILING_WIDTH-1:0]); + endfunction : get_trailing_bytes + + task automatic set_trailing_bytes(keep_t keep); + localparam USER_TRAILING_WIDTH = + USER_WIDTH >= TRAILING_WIDTH ? TRAILING_WIDTH : USER_WIDTH; + assert (TUSER) else + $fatal("Can't set trailing if TUSER doesn't exist"); + assert (USER_WIDTH >= TRAILING_WIDTH) else + $fatal("USER_WIDTH is to narrow to set trailing"); + tuser[USER_TRAILING_WIDTH-1:0] = keep2trailing(keep); + endtask : set_trailing_bytes + + + //--------------------------------------- + // Packet PROGRESS functions + //--------------------------------------- + // Notify when a byte is reached(VALID) + function automatic bit reached_packet_byte( + input int OFFSET + ); + // constant because they only depend on offset + int WORD_OFFSET; + assert (MAX_PACKET_BYTES > 0) else + $fatal(1,"checking packet on a non packet bus"); + // Constant + WORD_OFFSET = OFFSET / (DATA_WIDTH/8); + + return (word_count == WORD_OFFSET); + + endfunction : reached_packet_byte + + // Notify when a byte is being transmitted(VALID && READY) + function automatic bit xfering_packet_byte( + input int OFFSET + ); + + return reached_packet_byte(OFFSET) && tvalid && tready; + + endfunction : xfering_packet_byte + + // drive functions (These are not particularly useful + // but they guarantee the modules using the package don't + // drive the interface with a continuous assignment + task automatic drive_tlast(input logic last); + tlast = last; + endtask + task automatic drive_tvalid(input logic valid); + tvalid = valid; + endtask + task automatic drive_tready(input logic ready); + tready = ready; + endtask + task automatic drive_tdata(input logic data); + tdata = data; + endtask + task automatic drive_tuser(input logic user); + tuser = user; + endtask + task automatic drive_tkeep(input logic keep); + tkeep = keep; + endtask + function automatic word_count_t get_word_count(); + return word_count; + endfunction + + //--------------------------------------- + // Packet FIELD READ functions + // + // USAGE: + // + // // get the fields - don't use assign. assign will not activate with changes to in1. + // always_comb begin : get_fields + // eth_dst_addr_new = in1.get_packet_field48(eth_dst_addr_old,DST_MAC_BYTE,.NETWORK_ORDER(1)); + // reached_end_of_udp = in1.reached_packet_byte(DST_PORT_BYTE+3);// we have enough to decide + // end + // + // // use the fields + // always_comb begin : use_fields + // if (reached_end_of_udp) begin // Wait until you know the field is fully captured before using it! + // ** DO SOMETHING INTERESTING WITH est_dst_addr_new + // end + + + //--------------------------------------- + // Grab a byte from a passing stream + function automatic logic [7:0] get_packet_byte( + input logic [7:0] old_value, + input int OFFSET + ); + // constant because they only depend on offset + int BYTE_OFFSET; + + logic [7:0] new_value; + + // Constant + BYTE_OFFSET = OFFSET % (DATA_WIDTH/8); + + // default : Stay the same + new_value = old_value; + + if (reached_packet_byte(OFFSET)) begin + new_value = tdata[BYTE_OFFSET*8+:8]; + end + return new_value; + endfunction : get_packet_byte + + // Grab a 16 bit field from a passing stream + function automatic logic [15:0] get_packet_field16( + input logic [15:0] old_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + logic [15:0] new_value; + + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+i); + end + end + return new_value; + + endfunction : get_packet_field16 + + // Grab a 32 bit field from a passing stream + function automatic logic [31:0] get_packet_field32( + input logic [31:0] old_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + logic [31:0] new_value; + + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+i); + end + end + + return new_value; + + endfunction : get_packet_field32 + + // Grab a 48 bit field from a passing stream + function automatic logic [47:0] get_packet_field48( + input logic [47:0] old_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + logic [47:0] new_value; + + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + new_value[i*8+:8] = get_packet_byte(old_value[i*8+:8],OFFSET+i); + end + end + + return new_value; + + endfunction : get_packet_field48 + + //--------------------------------------- + // Packet FIELD OVERWRITE functions + // + // USAGE: + // + // // Each call muxes data as it goes by. This chains together muxing which synthesis will simplify + // always_comb begin : set_header_fields + // v2e5.tdata = v2e4.tdata; + // v2e5.put_packet_field48(mac_dst,DST_MAC_BYTE,.NETWORK_ORDER(1)); + // end + //--------------------------------------- + + // Overwrite a byte in a passing stream + task automatic put_packet_byte( + input logic [7:0] new_value, + input int OFFSET + ); + // constant because they only depend on offset + int BYTE_OFFSET; + + // Constant + BYTE_OFFSET = OFFSET % (DATA_WIDTH/8); + if (reached_packet_byte(OFFSET)) begin + tdata[BYTE_OFFSET*8+:8] = new_value; + end + endtask : put_packet_byte + + // Overwrite a 16 bit field in a passing stream + task automatic put_packet_field16( + input logic [15:0] new_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + put_packet_byte(new_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + put_packet_byte(new_value[i*8+:8],OFFSET+i); + end + end + endtask : put_packet_field16 + + // Overwrite a 32 bit field in a passing stream + task automatic put_packet_field32( + input logic [31:0] new_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + put_packet_byte(new_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + put_packet_byte(new_value[i*8+:8],OFFSET+i); + end + end + endtask : put_packet_field32 + + // Overwrite a 48 bit field in a passing stream + task automatic put_packet_field48( + input logic [47:0] new_value, + input int OFFSET, + input int NETWORK_ORDER=0 + ); + localparam BYTES=$size(new_value)/8; + for (int i=0;i < BYTES; i++) begin + if (NETWORK_ORDER==1) begin + put_packet_byte(new_value[i*8+:8],OFFSET+BYTES-1-i); + end else begin + put_packet_byte(new_value[i*8+:8],OFFSET+i); + end + end + endtask : put_packet_field48 + + + // View from the master side + modport master ( + input clk, rst, + output tvalid, tdata, tuser, tkeep, tlast, + input tready, + //import methods + import pack, + import unpack, + import keep2trailing, + import trailing2keep, + import get_trailing_bytes, + import set_trailing_bytes, + import get_word_count, + import reached_packet_byte, + import xfering_packet_byte, + import get_packet_byte, + import get_packet_field16, + import get_packet_field32, + import get_packet_field48, + import put_packet_byte, + import put_packet_field16, + import put_packet_field32, + import put_packet_field48 + ); + + // View from the slave side + modport slave ( + input clk, rst, + input tvalid, tdata, tuser, tkeep, tlast, + output tready, + //import methods + import pack, + import unpack, + import keep2trailing, + import trailing2keep, + import get_trailing_bytes, + import set_trailing_bytes, + import get_word_count, + import reached_packet_byte, + import xfering_packet_byte, + import get_packet_byte, + import get_packet_field16, + import get_packet_field32, + import get_packet_field48, + import put_packet_byte, + import put_packet_field16, + import put_packet_field32, + import put_packet_field48 + ); + + +endinterface : AxiStreamPacketIf diff --git a/fpga/usrp3/lib/axi4s_sv/Makefile.srcs b/fpga/usrp3/lib/axi4s_sv/Makefile.srcs new file mode 100644 index 000000000..b86de5b41 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/Makefile.srcs @@ -0,0 +1,18 @@ +# +# Copyright 2020 Ettus Research, A National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# RFNoC Utility Sources +################################################## +AXI4S_SV_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/axi4s_sv/, \ +AxiStreamIf.sv \ +axi4s_remove_bytes.sv \ +axi4s_remove_bytes_start.sv \ +axi4s_add_bytes.sv \ +axi4s_fifo.sv \ +axi4s_packet_gate.sv \ +axi4s_width_conv.sv \ +)) diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s.vh b/fpga/usrp3/lib/axi4s_sv/axi4s.vh new file mode 100644 index 000000000..1a0b80256 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s.vh @@ -0,0 +1,51 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Header File: axi4s +// Description: Macros for use with AXI4S +// + +//----------------------------------------------------------------------------- +// Unidirectional AXI4-Stream interface +//----------------------------------------------------------------------------- + +// Macro that drives o from i for all fields. Of course ready runs in the +// counter direction. + +`define AXI4S_ASSIGN(O,I) \ + if (``I.TDATA) ``O.tdata = ``I.tdata;\ + if (``I.TUSER) ``O.tuser = ``I.tuser;\ + else ``O.tuser = 0;\ + if (``I.TKEEP) ``O.tkeep = ``I.tkeep;\ + else ``O.tkeep = '1;\ + if (``I.TLAST) ``O.tlast = ``I.tlast;\ + ``O.tvalid = ``I.tvalid;\ + ``I.tready = ``O.tready; + +`define AXI4S_DEBUG_ASSIGN(O,I) \ + (* mark_debug = "true" *) logic [``I.DATA_WIDTH-1:0] ``I``_debug_tdata;\ + (* mark_debug = "true" *) logic [``I.USER_WIDTH-1:0] ``I``_debug_tuser;\ + (* mark_debug = "true" *) logic [``I.DATA_WIDTH/8-1:0] ``I``_debug_tkeep;\ + (* mark_debug = "true" *) logic ``I``_debug_tlast;\ + (* mark_debug = "true" *) logic ``I``_debug_tvalid;\ + (* mark_debug = "true" *) logic ``I``_debug_tready;\ + always_comb begin\ + if (``I.TDATA) ``I``_debug_tdata = ``I.tdata;\ + if (``I.TUSER) ``I``_debug_tuser = ``I.tuser;\ + if (``I.TKEEP) ``I``_debug_tkeep = ``I.tkeep;\ + if (``I.TLAST) ``I``_debug_tlast = ``I.tlast;\ + ``I``_debug_tvalid = ``I.tvalid;\ + ``I``_debug_tready = ``O.tready;\ + end\ + always_comb begin\ + if (``I.TDATA) ``O.tdata = ``I``_debug_tdata;\ + if (``I.TUSER) ``O.tuser = ``I``_debug_tuser;\ + else ``O.tuser = 0;\ + if (``I.TKEEP) ``O.tkeep = ``I``_debug_tkeep;\ + else ``O.tkeep = '1;\ + if (``I.TLAST) ``O.tlast = ``I``_debug_tlast;\ + ``O.tvalid = ``I``_debug_tvalid;\ + ``I.tready = ``I``_debug_tready;\ + end diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes.sv new file mode 100644 index 000000000..398942210 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes.sv @@ -0,0 +1,395 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_add_bytes +// +// Description: +// +// Add zero filled bytes to a packet. +// tUser = {error,trailing bytes}; +// +// LIMITATIONS +// The block only adds bytes to the beginning of a word. +// +// Parameters: +// ADD_START - Add bytes before this point (0 means start) +// 0 is the only supported value right now +// ADD_BYTES - Number of bytes to add +// SYNC - When 1 we wait for the start word to be +// valid before we start shifting. +// When 0 we aggressively pad 0 early, but +// it means the extra space may be added before +// we setup the values we want to overwrite onto +// that space. + +module axi4s_add_bytes #( + int ADD_START = 0, + int ADD_BYTES = 6, + bit SYNC = 1 +) ( + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o // AxiStreamIf or AxiStreamPacketIf +); + + localparam BYTES_PER_WORD = i.DATA_WIDTH/8; + // tUSER - always {error,numbytes} + localparam UWIDTH = $clog2(BYTES_PER_WORD+1); + + //packet position in bytes of the last removed byte. + localparam ADD_END = ADD_START + ADD_BYTES-1; + //packet position in bytes of the 1st byte after removal. + localparam ADD_RESTART = ADD_END+1; + + ////////////// Byte offsets in a word ///////////////// + localparam START_BYTE = ADD_START % BYTES_PER_WORD; + localparam END_BYTE = ADD_END % BYTES_PER_WORD; + localparam RESTART_BYTE = ADD_RESTART % BYTES_PER_WORD; + + // An Important shift offset + localparam BYTE_SHIFT = (BYTES_PER_WORD - RESTART_BYTE)%BYTES_PER_WORD; + // Subcase Recognition + // EXACT case - the removal expression is removing an entire word + localparam EXACT = BYTE_SHIFT == 0; + + `include "axi4s.vh" + // Parameter Checks + initial begin + assert (i.DATA_WIDTH == o.DATA_WIDTH) else + $fatal("DATA_WIDTH mismatch"); + assert (i.USER_WIDTH == o.USER_WIDTH) else + $fatal("USER_WIDTH mismatch"); + assert (i.USER_WIDTH >= UWIDTH) else + $fatal("i.USER_WIDTH is to small"); + assert (o.USER_WIDTH >= UWIDTH) else + $fatal("o.USER_WIDTH is to small"); + assert (ADD_START == 0) else + $fatal("Only tested for ADD_START = 0"); + end + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s1(i.clk,i.rst); + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + + logic reached_start; + logic reached_end; + logic byte_overflow; + logic [s0.DATA_WIDTH-1:0] zero_data; + logic [s0.DATA_WIDTH-1:0] last_tdata; + logic [s0.DATA_WIDTH-1:0] remaining_shift_data; + logic [s0.DATA_WIDTH-1:0] last_shift_data; + logic [s0.DATA_WIDTH-1:0] first_shifted_data; + + logic error_bit, error_bit_old; + + // Cache a couple of words from the bus + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + last_tdata <= 0; + end else if (s0.tvalid && s0.tready) begin + last_tdata <= s0.tdata; + end + end + + if (EXACT) begin + always_comb begin + // If END_BYTE=3 + zero_data = 'b0; + first_shifted_data = s0.tdata; + remaining_shift_data = s0.tdata; + last_shift_data = s0.tdata; + end + end else begin + always_comb begin + zero_data = 'b0; + // If END_BYTE=2 [7:0] [23:0] + // If END_BYTE=1 [15:0] [15:0] + // If END_BYTE=0 [23:0] [7:0] + first_shifted_data = {s0.tdata[BYTE_SHIFT*8-1:0],zero_data[END_BYTE*8+7:0]}; + // If END_BYTE=0 [23:0] [31:24] + remaining_shift_data = {s0.tdata[BYTE_SHIFT*8-1:0],last_tdata[s0.DATA_WIDTH-1:BYTE_SHIFT*8]}; + // If END_BYTE=0 [23:0] [31:24] + last_shift_data = {zero_data[BYTE_SHIFT*8-1:0],s0.tdata[s0.DATA_WIDTH-1:BYTE_SHIFT*8]}; + end + end + + //----------------------------------------------------------------------- + // user write function + // this module ASSUMES user includes error in the MSB and the rest is the + // number of bytes in the word + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] uwrite(error=0,[UWIDTH-2:0] bytes=0); + begin + return {error,bytes}; + end + endfunction + + //----------------------------------------------------------------------- + // get_error -extract error from tuser + //----------------------------------------------------------------------- + function automatic get_error([UWIDTH-1:0] tuser); + begin + return tuser[UWIDTH-1]; + end + endfunction + + //----------------------------------------------------------------------- + // get_bytes -extract num_bytes from tuser + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] get_bytes([UWIDTH-1:0] tuser); + logic [UWIDTH-1:0] bytes; + begin + if (tuser[UWIDTH-2:0] == 0) bytes = BYTES_PER_WORD; + else bytes = tuser[UWIDTH-2:0]; + return bytes; + end + endfunction + + //--------------------------------------- + // remove state machine + //--------------------------------------- + typedef enum {ST_PRE_ADD, ST_ADDING, ST_POST_ADD,ST_BONUS} add_state_t; + + add_state_t add_state = ST_PRE_ADD; + add_state_t next_add_state = ST_PRE_ADD; + + + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + error_bit_old <= 0; + end else begin + + // must hold until output completes + if (s1.tlast && s1.tvalid && s1.tready) begin + error_bit_old <= 0; + // but they set based on the input + end else if (s0.tvalid && s0.tready) begin + error_bit_old <= error_bit; + end + end + end + + // Find the landmark bytes + always_comb error_bit = get_error(s0.tuser) || error_bit_old; + + always_comb begin + reached_start = s1.reached_packet_byte(ADD_START); + reached_end = s1.reached_packet_byte(ADD_START+ADD_BYTES); + end + + if (EXACT) begin + always_comb byte_overflow = 0; + end else begin + always_comb byte_overflow = get_bytes(s0.tuser) > BYTE_SHIFT; + end + + // because s0.tready feeds back and generates a + // change event for the entire interface, + // it can trigger an infinite loop of assignment + // even when nothing is changing. This breaks + // the feedback loop. + logic s0_tready; + always_comb s0.tready = s0_tready; + + // ADD state machine + always_comb begin + + // default assignment of next_state + next_add_state = add_state; + s1.tuser = s0.tuser; + s1.tlast = s0.tlast; + s1.tvalid = s0.tvalid; + s1.tdata = first_shifted_data; + s0_tready = s1.tready; + + case (add_state) + // ***************************************************** + // PRE_ADD - wait till we reach ADD_START + // ***************************************************** + ST_PRE_ADD: begin + + if (!SYNC || s0.tvalid) begin + // reached start and end in same clock and end of word + if (reached_start && reached_end && s0.tlast) begin + + // if final word has more bytes than we can fit. + if (byte_overflow) begin + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = 0; // don't advance + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_BONUS; + end + // we can finish this clock because final word + // didn't overflow into an additional word. + end else begin + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,get_bytes(s0.tuser) + RESTART_BYTE); + // NO state advance + end + // reached start and end, and not the end of the packet + end else if (reached_start && reached_end && !s0.tlast) begin + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_POST_ADD; + end + + // reached start but not the end of byte insertion + end else if (reached_start && !reached_end) begin + s1.tlast = 0; + s1.tvalid = 1; + s0_tready = 0; // don't advance + s1.tdata = zero_data; + s1.tuser = uwrite(0,BYTES_PER_WORD); + + if (s1.tready) begin + next_add_state = ST_ADDING; + end + + end + end + end //ST_PRE_REMOVE + + + // ***************************************************** + // REMOVING - burn words until we have data to + // start sending again + // ***************************************************** + ST_ADDING: begin + //defaults + s1.tlast = 0; + s1.tvalid = 1; + s0_tready = 0; // don't advance + s1.tdata = zero_data; + s1.tuser = uwrite(0,BYTES_PER_WORD); + + // reached the end of incoming packet and data insertion + if (reached_end && s0.tlast) begin + // if final word has more bytes than we can fit. + if (byte_overflow) begin + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = 0; // don't advance + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_BONUS; + end + end else begin + // we can finish this clock because final word + // didn't overflow into an additional word. + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,get_bytes(s0.tuser) + RESTART_BYTE); + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_PRE_ADD; + end + end + + // reached the end of data insertion - not end of packet + end else if (reached_end && !s0.tlast) begin + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tdata = first_shifted_data; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_POST_ADD; + end + + end + end + // ***************************************************** + // POST_ADD waiting for end + // ***************************************************** + ST_POST_ADD: begin + //defaults + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tdata = remaining_shift_data; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + // reached the end, but we have extra bytes to send + if (s0.tlast && byte_overflow) begin + s1.tlast = 0; + s0_tready = 0; // don't let a advance + + if (s0.tvalid && s1.tready) begin + next_add_state = ST_BONUS; + end + + // reached the end, and don't need the bonus state + end else if (s0.tlast) begin + s1.tlast = 1; + s1.tuser = uwrite(error_bit,get_bytes(s0.tuser) + RESTART_BYTE); + + if (s1.tready && s0.tvalid) begin + next_add_state = ST_PRE_ADD; + end + + end + end + + // ***************************************************** + // BONUS write out any overflow words + // ***************************************************** + ST_BONUS: begin + //defaults + s1.tdata = last_shift_data; + s1.tuser = uwrite(error_bit,get_bytes(s0.tuser)+ RESTART_BYTE); + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + + if (s1.tready && s0.tvalid) begin + next_add_state = ST_PRE_ADD; + end + + end + + // We should never get here + default: begin + next_add_state = ST_PRE_ADD; + end + endcase + end + + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + add_state <= ST_PRE_ADD; + end else begin + add_state <= next_add_state; + end + end + + always_comb begin + `AXI4S_ASSIGN(o,s1) + end + + +endmodule : axi4s_add_bytes diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/Makefile b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/Makefile new file mode 100644 index 000000000..f1d36d7c2 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/Makefile @@ -0,0 +1,47 @@ +# +# Copyright 2020 Ettus Research, A National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir +BASE_DIR = $(abspath ../../../top) +# Include viv_sim_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- + +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/axi4s_sv/Makefile.srcs + +DESIGN_SRCS = $(abspath \ +$(AXI4S_SV_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +TB_TOP_MODULE = axi4s_add_bytes_all_tb +SIM_TOP = $(TB_TOP_MODULE) + +SIM_SRCS = \ +$(abspath $(TB_TOP_MODULE).sv )\ +$(abspath axi4s_add_bytes_tb.sv ) + +# supressing the following worthless reminder. +#* Warning: M:/usrp4-hw/oss-repo/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv(228): (vlog-2583) [SVCHK] - +# Extra checking for conflicts with always_comb and always_latch variables is done at vopt time +SVLOG_ARGS = -suppress 2583 +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_all_tb.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_all_tb.sv new file mode 100644 index 000000000..4fb0edda3 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_all_tb.sv @@ -0,0 +1,53 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_add_bytes_all_tb +// +// Description: Testbench for axi_add_bytes +// Exercise corner cases parameters for adding bytes to the front of a packet +// + +module axi4s_add_bytes_all_tb; + + // actual use cases + // Add Ethernet header from a packet includes a preamble + axi4s_add_bytes_tb #(.TEST_NAME("ENET_64_HADD"),.WIDTH(64),.ADD_START(0),.ADD_BYTES(48)) + ENET_64_HADD48 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_128_HADD"),.WIDTH(128),.ADD_START(0),.ADD_BYTES(48)) + ENET_128_HADD48 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_256_HADD"),.WIDTH(256),.ADD_START(0),.ADD_BYTES(48)) + ENET_256_HADD48 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_512_HADD"),.WIDTH(512),.ADD_START(0),.ADD_BYTES(48)) + ENET_512_HADD48 (); + + // Add Preamble header from a packet + axi4s_add_bytes_tb #(.TEST_NAME("ENET_64_PADD"),.WIDTH(64),.ADD_START(0),.ADD_BYTES(6)) + ENET_64_PADD6 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_128_PADD"),.WIDTH(128),.ADD_START(0),.ADD_BYTES(6)) + ENET_128_PADD6 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_256_PADD"),.WIDTH(256),.ADD_START(0),.ADD_BYTES(6)) + ENET_256_PADD6 (); + axi4s_add_bytes_tb #(.TEST_NAME("ENET_512_PADD"),.WIDTH(512),.ADD_START(0),.ADD_BYTES(6)) + ENET_512_PADD6 (); + + // START cases - removal starts at LSB of word + axi4s_add_bytes_tb #(.TEST_NAME("SSTART_32_0:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(1)) + SSTART_32_0to0 (); + axi4s_add_bytes_tb #(.TEST_NAME("SSTART_32_1:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(2)) + SSTART_32_0to1 (); + axi4s_add_bytes_tb #(.TEST_NAME("SSTART_32_2:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(3)) + SSTART_32_0to2 (); + axi4s_add_bytes_tb #(.TEST_NAME("EXACT_32_3:0"), .WIDTH(32),.ADD_START(0),.ADD_BYTES(4)) + EXACT_32_0to3 (); + axi4s_add_bytes_tb #(.TEST_NAME("MSTART_32_4:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(5)) + MSTART_32_0to4 (); + axi4s_add_bytes_tb #(.TEST_NAME("MSTART_32_5:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(6)) + MSTART_32_0to5 (); + axi4s_add_bytes_tb #(.TEST_NAME("MSTART_32_6:0"),.WIDTH(32),.ADD_START(0),.ADD_BYTES(7)) + MSTART_32_0to6 (); + axi4s_add_bytes_tb #(.TEST_NAME("EXACT_32_7:0"), .WIDTH(32),.ADD_START(0),.ADD_BYTES(8)) + EXACT_32_0to7 (); + +endmodule diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_tb.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_tb.sv new file mode 100644 index 000000000..5e96684ec --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_add_bytes_tb/axi4s_add_bytes_tb.sv @@ -0,0 +1,171 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_add_bytes_tb +// +// Description: Testbench for axi_add_bytes +// + +module axi4s_add_bytes_tb #( + parameter TEST_NAME = "axi_add_bytes", + WIDTH=32,ADD_START=0,ADD_BYTES=7 +); + // Include macros and time declarations for use with PkgTestExec + // 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". + `define TEST_EXEC_OBJ test + `include "test_exec.svh" + import PkgAxiStreamBfm::*; + import PkgTestExec::*; + import PkgEthernet::*; + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + localparam UWIDTH = $clog2((WIDTH/8)+1); + localparam MAX_PACKET_BYTES = 16*1024; + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit clk; + bit reset; + + sim_clock_gen #(.PERIOD(5.0), .AUTOSTART(1)) + clk_gen (.clk(clk), .rst(reset)); + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + TestExec test = new(); + AxiStreamIf #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + i (clk, reset); + AxiStreamIf #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + o (clk, reset); + + // Bus functional model for a axi_stream controller + AxiStreamBfm #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + axis = new(.master(i), .slave(o)); + + + //---------------------------------------------------- + // Instantiate DUT + //---------------------------------------------------- + axi4s_add_bytes #(.ADD_START(ADD_START),.ADD_BYTES(ADD_BYTES)) + DUT (.*); + + //--------------------------------------------------------------------------- + // Reset + //--------------------------------------------------------------------------- + + task test_reset(); + test.start_test("Wait for Reset", 10us); + clk_gen.reset(); + wait(!reset); + repeat (10) @(posedge clk); + test.end_test(); + endtask : test_reset + + //--------------------------------------------------------------------------- + // Ethernet to CPU test + //--------------------------------------------------------------------------- + typedef AxiStreamPacket #(WIDTH, UWIDTH) AxisPacket_t; + typedef XportStreamPacket #(WIDTH) XportPacket_t; + + task test_samples(int num_samples); + raw_pkt_t raw; + + localparam PKTS_TO_SEND = 10; + automatic XportPacket_t send[$]; + automatic XportPacket_t expected[$]; + + test.start_test({TEST_NAME,"::Remove Bytes"}, 10us); + + for (int i= 0 ; i < PKTS_TO_SEND; i++) begin + + expected[i] = new; + send[i] = new; + + get_ramp_raw_pkt(.num_samps(num_samples),.ramp_start((i*num_samples)%256),.ramp_inc(1),.pkt(raw),.SWIDTH(8)); + send[i].push_bytes(raw); + // Add the bytes to the raw packet to build expected. + for (int i = 0 ; i < ADD_BYTES; ++i) begin + raw.insert(ADD_START,0); + end + expected[i].push_bytes(raw); + send[i].tkeep_to_tuser(.ERROR_PROB(10)); + expected[i].tkeep_to_tuser(); + if (send[i].has_error()) expected[i].set_error(); + end + + fork + begin // Send Thread + foreach(send[i])begin + axis.put(send[i]); + end + end + begin // Expected Thread + foreach(expected[i]) begin + automatic string str; + automatic AxisPacket_t actual_a = new(); + automatic XportPacket_t actual = new(); + axis.get(actual_a); + actual.import_axis(actual_a); + actual.tuser_to_tkeep(); + str = $sformatf("ADD_START=%3d ADD_BYTES=%3d",ADD_START,ADD_BYTES); + `ASSERT_ERROR(!actual.compare_w_error(expected[i]),str); + end + end + join + + + test.end_test(); + + endtask : test_samples + + + //---------------------------------------------------- + // Main test loop + //---------------------------------------------------- + initial begin : tb_main + integer min_length; + + test.tb_name = TEST_NAME; + min_length = ADD_START+1; + + + axis.run(); + + test_reset(); + + // Test with default holdoff. + for (int i=1 ; i < (WIDTH/8)*3 ; ++i) begin + test_samples(min_length+i); + end + + // repeat back to back + axis.set_slave_stall_prob(0); + axis.set_master_stall_prob(0); + for (int i=1 ; i < (WIDTH/8)*3 ; ++i) begin + test_samples(min_length+i); + end + + + // End the TB, but don't $finish, since we don't want to kill other + // instances of this testbench that may be running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + clk_gen.kill(); + + end // initial begin + +endmodule +`undef TEST_EXEC_OBJ diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_fifo.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_fifo.sv new file mode 100644 index 000000000..0aa1daca0 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_fifo.sv @@ -0,0 +1,82 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_fifo +// +// Description: System Verilog wrapper for axi_fifo that accepts an +// AxiStreamIfc with slave_user/master_uer interface. +// +// Parameters: +// SIZE - 2**SIZE words are stored + +module axi4s_fifo #( + int SIZE = 1 // default size set to one to act as a pipe phase + ) ( + input logic clear=1'b0, + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o, // AxiStreamIf or AxiStreamPacketIf + output logic [15:0] space, + output logic [15:0] occupied +); + + `include "axi4s.vh" + + // Parameter Checks + initial begin + assert (i.DATA_WIDTH == o.DATA_WIDTH) else + $fatal("DATA_WIDTH mismatch"); + assert (i.USER_WIDTH == o.USER_WIDTH) else + $fatal("USER_WIDTH mismatch"); + assert (i.TDATA == o.TDATA) else + $fatal("TDATA present mismatch"); + assert (i.TUSER == o.TUSER) else + $fatal("TUSER present mismatch"); + assert (i.TKEEP == o.TKEEP) else + $fatal("TKEEP present mismatch"); + assert (i.TLAST == o.TLAST) else + $fatal("TLAST present mismatch"); + end + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TDATA(i.TDATA),.TKEEP(i.TKEEP),.TUSER(i.TUSER),.TLAST(i.TLAST), + .MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TDATA(i.TDATA),.TKEEP(i.TKEEP),.TUSER(i.TUSER),.TLAST(i.TLAST), + .MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s1(i.clk,i.rst); + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + // move from AxiStreamPacketIf to AxiStreamIfc + always_comb begin + `AXI4S_ASSIGN(o,s1) + end + + logic [s0.PACKED_WIDTH-1:0] s0_data; + logic [s1.PACKED_WIDTH-1:0] s1_data; + always_comb s0_data = s0.pack(); + always_comb s1.unpack(s1_data); + + logic s0_ready, s1_valid; + always_comb s0.tready = s0_ready; + always_comb s1.tvalid = s1_valid; + + axi_fifo #( + .WIDTH(s0.PACKED_WIDTH),.SIZE(SIZE) + ) axi_fifo_i ( + .clk(s0.clk), .reset(s0.rst), .clear(clear), + .i_tdata(s0_data), + .i_tvalid(s0.tvalid), + .i_tready(s0_ready), + .o_tdata(s1_data), + .o_tvalid(s1_valid), + .o_tready(s1.tready), + .space(space), .occupied(occupied) + ); + +endmodule : axi4s_fifo diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_packet_gate.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_packet_gate.sv new file mode 100644 index 000000000..a1b744adf --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_packet_gate.sv @@ -0,0 +1,98 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_packet_gate +// +// Description: +// System Verilog wrapper for axi_gate_packet that accepts +// a AxiStreamIfc with slave_user/master_uer interface. +// +// Parameters: +// TDATA - store tData if 1 +// TUSER - store tUser if 1 +// SIZE - 2**SIZE words are stored +// USE_AS_BUFF - Allow the packet gate to be used as a buffer (uses more RAM) +// MIN_PKT_SIZE - log2 of minimum valid packet size (rounded down, used to +// reduce addr fifo size) + +module axi4s_packet_gate #( + bit TDATA = 1, + bit TUSER = 1, + int SIZE = 10, + bit USE_AS_BUFF = 1, + int MIN_PKT_SIZE= 1 +) ( + input logic clear=1'b0, + input logic error=1'b0, + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o // AxiStreamIf or AxiStreamPacketIf +); + + `include "axi4s.vh" + + // Parameter Checks + initial begin + assert (i.DATA_WIDTH == o.DATA_WIDTH) else + $fatal("DATA_WIDTH mismatch"); + assert (i.USER_WIDTH == o.USER_WIDTH) else + $fatal("USER_WIDTH mismatch"); + assert (i.TDATA == o.TDATA) else + $fatal("TDATA present mismatch"); + assert (i.TUSER == o.TUSER) else + $fatal("TUSER present mismatch"); + assert (i.TKEEP == o.TKEEP) else + $fatal("TKEEP present mismatch"); + assert (i.TLAST == 1) else + $fatal("i.TLAST not present"); + assert (o.TLAST == 1) else + $fatal("o.TLAST not present"); + end + + localparam WIDTH = i.DWIDTH + i.UWIDTH + i.KWIDTH; + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TDATA(i.TDATA),.TKEEP(i.TKEEP),.TUSER(i.TUSER),.TLAST(i.TLAST), + .MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TDATA(i.TDATA),.TKEEP(i.TKEEP),.TUSER(i.TUSER),.TLAST(i.TLAST), + .MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s1(i.clk,i.rst); + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + // move from AxiStreamPacketIf to AxiStreamIfc + always_comb begin + `AXI4S_ASSIGN(o,s1) + end + + logic [WIDTH-1:0] s0_data; + logic [WIDTH-1:0] s1_data; + always_comb s0_data = s0.pack(.INC_LAST(0)); + always_comb s1.unpack(s1_data,.INC_LAST(0)); + + logic s0_ready, s1_valid, s1_last; + always_comb s0.tready = s0_ready; + always_comb s1.tvalid = s1_valid; + always_comb s1.tlast = s1_last; + + axi_packet_gate #( + .WIDTH(WIDTH),.SIZE(SIZE), .USE_AS_BUFF(USE_AS_BUFF), .MIN_PKT_SIZE(MIN_PKT_SIZE) + ) axi_packet_gate_i ( + .clk(s0.clk), .reset(s0.rst), .clear(clear), + .i_terror(error), + .i_tdata(s0_data), + .i_tvalid(s0.tvalid), + .i_tready(s0_ready), + .i_tlast(s0.tlast), + .o_tdata(s1_data), + .o_tvalid(s1_valid), + .o_tready(s1.tready), + .o_tlast(s1_last) + ); + +endmodule : axi4s_packet_gate diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv new file mode 100644 index 000000000..4321d87d4 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv @@ -0,0 +1,802 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module : axi4s_remove_bytes +// +// Description: +// Remove bytes from a packet. 1 removal can happen per +// packet. The removal is made by delaying the output +// by a clock, and then combining the new and old word +// and providing a combination of shifted words. +// This implementation requires that the user field +// holds the number of valid bytes in the word, and the MSB of the user field +// indicates if the MAC had an error. +// +// The block will hold off the input if it goes to the BONUS State. +// +// This block is intended to remove data from the beginning or middle +// of a packet. You can truncate a packet by setting REM_END to -1. +// +// LIMITATIONS +// The block will set the error bit if you put in a packet between +// REM_END and REM_START length, and it is unable to cleanly signal +// and end to the packet. (there is no way to send a zero byte valid +// packet using tuser protocol. +// Packets must be terminated with tlast. +// +// Parameters: +// REM_START - First byte to remove (0 means start) +// REM_END - Last byte to remove (-1 means truncate from REM START) +// + +module axi4s_remove_bytes #( + REM_START=0, + REM_END=8 +)( + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o // AxiStreamIf or AxiStreamPacketIf +); + + localparam BYTES_PER_WORD = i.DATA_WIDTH/8; + // tUSER - always {error,numbytes} + localparam UWIDTH = $clog2(BYTES_PER_WORD+1); + localparam ERROR = UWIDTH-1; // MSB is the error bit. + + localparam TRUNCATE = REM_END < 0; + // END is inclusive so +1 + localparam BYTES_REMOVED = TRUNCATE ? 1 : + REM_END-REM_START+1; + + // how many bytes into the word for start and end point + localparam START_BYTE = REM_START% BYTES_PER_WORD; + localparam START_WORD = REM_START/ BYTES_PER_WORD; + localparam END_BYTE = TRUNCATE ? BYTES_PER_WORD-1 : + REM_END % BYTES_PER_WORD; + localparam END_WORD = TRUNCATE ? 65535 : // max word counter value + REM_END / BYTES_PER_WORD; + + localparam FIRST_BYTE_AFTER = (END_BYTE+1) % BYTES_PER_WORD; + + localparam BYTE_SHIFT = BYTES_REMOVED % BYTES_PER_WORD; + localparam BYTE_CARRY = BYTES_PER_WORD - BYTE_SHIFT; + // CASE differentiators + localparam SINGLE = BYTES_REMOVED <= BYTES_PER_WORD; + localparam START_AT_LSB = START_BYTE == 0; + localparam END_AT_MSB = END_BYTE == BYTES_PER_WORD-1; + localparam EXACT = START_AT_LSB && END_AT_MSB; + localparam MIDDLE = END_BYTE >= START_BYTE; + + `include "axi4s.vh" + + // Parameter Checks + initial begin + assert (i.DATA_WIDTH == o.DATA_WIDTH) else + $fatal("DATA_WIDTH mismatch"); + end + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s1(i.clk,i.rst); + + + // implement specialized cases + if (REM_START == 0 && !EXACT) begin : start_not_exact + + // START at zero but still shifted + axi4s_remove_bytes_start #( + .REM_END(REM_END) + ) axi4s_remove_bytes_start_i ( + .i(i), .o(o) + ); + + end else begin : general + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + + typedef enum {MS_EXACT, MS_START_AT_LSB, MS_END_AT_MSB, + SINGLE_MIDDLE,MULTI_MIDDLE, MS_WRAP} case_t; + case_t MCASE; + + logic reached_start; + logic reached_end; + logic reached_end_plus; + // memory for holding old values + logic [s0.DATA_WIDTH-1:0] last_tdata; + logic [s0.DATA_WIDTH-1:0] first_tdata; + logic [UWIDTH-1:0] first_tuser; + + // various flavors of data shifting + logic [s0.DATA_WIDTH-1:0] trunc_data; + logic [s0.DATA_WIDTH-1:0] remaining_shift_data; + logic [s0.DATA_WIDTH-1:0] prefirst_shifted_data; + logic [s0.DATA_WIDTH-1:0] first_shifted_data; + logic [s0.DATA_WIDTH-1:0] one_word_data; + logic [s0.DATA_WIDTH-1:0] bonus_data; + + logic [15:0] word_count; // Oversized to 65536 words + logic error_bit, error_bit_old; + logic [UWIDTH-1:0] in_byte_count; + + //--------------------------------------- + // remove state machine + //--------------------------------------- + typedef enum {ST_PRE_REMOVE, ST_TRUNCATE, ST_REMOVING, + ST_POST_REMOVE, ST_BONUS} remove_state_t; + remove_state_t remove_state = ST_PRE_REMOVE; + remove_state_t next_remove_state = ST_PRE_REMOVE; + + always_comb in_byte_count = get_bytes(s0.tuser); + + // Cache a couple of words from the bus + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + last_tdata = 0; + first_tdata = 0; + first_tuser = 0; + end else + if (s0.tvalid && s0.tready && + (MCASE == MULTI_MIDDLE || MCASE==MS_START_AT_LSB)) + last_tdata = s0.tdata; + if (s0.tvalid && s0.tready && + (reached_start || next_remove_state==ST_POST_REMOVE || + (remove_state!=ST_REMOVING && remove_state!= ST_TRUNCATE))) begin + first_tdata = s0.tdata; + first_tuser = s0.tuser; + end + end + + //***************** DATA SHIFTING CASES ***********************/ + + //----------------------------------------------------------------------- + // user write function + // this module ASSUMES user includes error in the MSB and the rest is the + // number of bytes in the word + //----------------------------------------------------------------------- + function automatic logic [START_BYTE*8-1:0] start_part([s0.DATA_WIDTH-1:0] data); + begin + // workaround :: modelsim optimizer can fail if there is aposibility of a 0+:0 + localparam MY_START_BYTE = START_BYTE ? START_BYTE : 1; + return data[0+:MY_START_BYTE*8]; + end + endfunction + + + function automatic logic [s0.DATA_WIDTH-1-FIRST_BYTE_AFTER*8:0] end_part([s0.DATA_WIDTH-1:0] data); + begin + return data[s0.DATA_WIDTH-1:FIRST_BYTE_AFTER*8]; + end + endfunction + + function automatic logic [s0.DATA_WIDTH-1-BYTE_SHIFT*8:0] bs_part([s0.DATA_WIDTH-1:0] data); + begin + return data[s0.DATA_WIDTH-1:BYTE_SHIFT*8]; + end + endfunction + +// Examples +// +// ENDING CASE 1 +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 B0 A0 <- word 0 +// D1 XX XX XX <- R(6:4)) D0 C0 B0 A0 +// D2 C2 B2 A2 C2 B2 A2 D1 +// D0 C0 B0 A0 <- next packet D2 +// D0 C0 B0 A0 +// +// ENDING CASE2 +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 B0 A0 <- word 0 +// D1 XX XX XX <- R(6:4)) D0 C0 B0 A0 +// C2 B2 A2 C2 B2 A2 D1 +// D0 C0 B0 A0 <- next packet +// D0 C0 B0 A0 +// Middle of Word case +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 B0 A0 <- word 0 +// D1 XX XX A1 <- R(7:6) D0 C0 B0 A0 +// D2 C2 B2 A2 B2 A2 D1 A1 +// D3 C3 B3 A3 B3 A3 D2 C2 +// D0 C0 B0 A0 <- next packet D3 C3 +// +// Easy Truncation (can handle dynamically) +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 B0 A0 <- word 0 +// D1 C1 B1 A1 D0 C0 B0 A0 +// XX XX XX <- R(11:8)) D1 C1 B1 A1 <- TLAST HERE +// D0 C0 B0 A0 <- next packet +// D0 C0 B0 A0 +// Truncation case requiring REM_END=-1 +// because last word is to far away to see tlast. +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 B0 A0 <- word 0 +// XX XX XX XX <- R(-1:4)) D0 C0 B0 A0 <- TLAST HERE +// XX XX XX XX +// XX XX XX XX +// D0 C0 B0 A0 <- next packet +// D0 C0 B0 A0 +// Remove from Front +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// XX XX XX XX <- R(0:7) +// XX XX XX XX <- +// C2 B2 A2 +// D0 C0 B0 A0 <- next packet C2 B2 A2 +// D0 C0 B0 A0 +// +// Remove 1 byte on back to back 1 word packets +// Incoming packet outgoing packet +// /////////////////////////////////////////////// +// D0 C0 XX A0 <- R(1:1) +// D0 C0 XX A0 <- R(1:1) D0 C0 A0 +// D0 C0 XX A0 <- R(1:1) D0 C0 A0 +// D0 C0 A0 +// +// + + // Note these should all be static shifts. We don't want to infer a barrel shifter. + if (EXACT) begin // Remove whole words + always_comb begin + MCASE = MS_EXACT; + first_shifted_data = s0.tdata; + remaining_shift_data = s0.tdata; + one_word_data = s0.tdata; + trunc_data = first_tdata; + bonus_data = 'bX; + end + end else if (START_AT_LSB) begin // Remove start of word shift case + // EXAMPLE XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 with START BYTE = 0(A0) END_BYTE = 2(C0) BYTE_SHIFT=3 + // 1st word would be C1 B1 A1/H0 G0 F0 E0 D0 + // [23:0] C1 B1 A1 / [63:24] H0 G0 F0 E0 D0 + // same as remaining_shift_data above + // EXAMPLE XX XX XX XX XX XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 + // XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 with START BYTE = 0(A0) END_BYTE = 2(10)(C1) BYTE_SHIFT=3 + // 1st word would be C2 B2 A2/H1 G1 F1 E1 D1 + // [23:0] C2 B2 A2 / [63:24] H1 G1 F1 E1 D1 + // same as remaining_shift_data above + // NOTE: Entire words are thrown away at start, so no caching required + always_comb begin + MCASE = MS_START_AT_LSB; + first_shifted_data = {s0.tdata,bs_part(last_tdata)}; + if (BYTE_SHIFT==0) + remaining_shift_data = s0.tdata; + else + remaining_shift_data = {s0.tdata,bs_part(last_tdata)}; + bonus_data = 'bX; + one_word_data = end_part(s0.tdata); + trunc_data = first_tdata; + bonus_data = 'bX; + bonus_data = bs_part(s0.tdata); + end + end else if (END_AT_MSB) begin // Remove end of word shift case + // EXAMPLE XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 with START BYTE = 6(G0) END_BYTE = 7(H0) BYTE_SHIFT=2 + // 1st word would be B1 A1/F0 E0 D0 C0 B0 A0 + // [15:0] B1 A1 / [47:0] F0 E0 D0 C0 B0 A0 + // EXAMPLE XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 + // XX XX XX XX XX XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 with START BYTE = 6(G0) END_BYTE = 7(15)(H0) BYTE_SHIFT=2 + // 1st word would be B2 A2/F0 E0 D0 C0 B0 A0 + // NOTE: Uses 1st Data (from when we reach the first word + // [15:0] B2 A2 / [47:0] F0 E0 D0 C0 B0 A0 + always_comb begin + MCASE = MS_END_AT_MSB; + first_shifted_data = {s0.tdata,start_part(first_tdata)}; + if (BYTE_SHIFT==0) + remaining_shift_data = s0.tdata; + else + remaining_shift_data = {s0.tdata,bs_part(first_tdata)}; + one_word_data = s0.tdata; + trunc_data = first_tdata; + bonus_data = 'bX; + bonus_data = bs_part(s0.tdata); + end + end else if(MIDDLE) begin // Remove middle of word shift case + // EXAMPLE XX XX XX XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 with START BYTE = 1(B0) END_BYTE = 6(G0) BYTE_SHIFT=6 + // 1st word would be F1 E1 D1 C1 B1 A1/H0/A0 + // [47:0] F1 E1 D1 C1 B1 A1 [63:56] H0 [7:0] A0 + // EXAMPLE XX XX XX XX XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 + // XX XX XX XX XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 with START BYTE = 1(B0) END_BYTE = 6(14)(G0) BYTE_SHIFT=6 + // 1st word would be F2 E2 D2 C2 B2 A2/H1/A0 + // NOTE: Uses first Data from when we reach the first word. + // Also, must advance one clock beyond end for this case. + // [47:0] F2 E2 D2 C2 B2 A2 [63:56] H1 [7:0] A0 + // remaining words F2 E2 D2 C2 B2 A2/H1 G1 + // [47:0] F2 E2 D2 C2 B2 A2 [63:48] H1 G1 + // same as remaining_shift_data above + always_comb begin + if (SINGLE) begin + MCASE = SINGLE_MIDDLE; + first_shifted_data = {s0.tdata,end_part(first_tdata),start_part(first_tdata)}; + end else begin + MCASE = MULTI_MIDDLE; + prefirst_shifted_data = {end_part(s0.tdata),start_part(first_tdata)}; + first_shifted_data = {s0.tdata,end_part(last_tdata),start_part(first_tdata)}; + end + if (BYTE_SHIFT==0) + remaining_shift_data = s0.tdata; + else + remaining_shift_data = {s0.tdata,bs_part(first_tdata)}; + one_word_data = {end_part(s0.tdata),start_part(s0.tdata)}; + trunc_data = first_tdata; + bonus_data = 'bX; + bonus_data = bs_part(s0.tdata); + end + end else begin //wrapped case + // EXAMPLE XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 with START BYTE = 6(G0) END_BYTE = 2(10)(C1) BYTE_SHIFT=5 + // XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 + // + // 1st word would be E1 D1/F0 E0 D0 C0 B0 A0 + // [39:24] E1 D1 / [47:0] F0 E0 D0 C0 B0 A0 + // remaining words E2 D2 C2 B2 A2/H1 G1 F1 + // [39:0] E2 D2 C2 B2 A2 / H1 G1 F1 [63:40] + // same as remaining_shift_data_above // EXAMPLE XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 + // XX XX XX XX XX XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 + // XX XX XX + // H2 G2 F2 E2 D2 C2 B2 A2 with START BYTE = 6(G0) END_BYTE = 2(10)(C1) BYTE_SHIFT=5 + // + // 1st word would be E2 D2/F0 E0 D0 C0 B0 A0 + // NOTE: Uses 1st Data (from when we reach the first word ; + // [39:24] E2 D2 / [47:0] F0 E0 D0 C0 B0 A0 + always_comb begin + MCASE = MS_WRAP; + first_shifted_data = {end_part(s0.tdata),start_part(first_tdata)}; + if (BYTE_SHIFT==0) + remaining_shift_data = s0.tdata; + else + remaining_shift_data = {s0.tdata,bs_part(first_tdata)}; + one_word_data = s0.tdata; + trunc_data = first_tdata; + bonus_data = 'bX; + bonus_data = bs_part(s0.tdata); + + end + end + + + typedef enum {PASS_THRU,BONUS,REM_SHIFT_DATA,FIRST_SHIFT_DATA, + PREFIRST_SHIFT_DATA,TRUNCATE_DATA,ONE_WORD} data_mux_sel_t; + data_mux_sel_t data_mux_sel = PASS_THRU; + + always_comb begin : data_mux + s1.tdata = s0.tdata; + case (data_mux_sel) + PASS_THRU : s1.tdata = s0.tdata; + ONE_WORD : s1.tdata = one_word_data; + FIRST_SHIFT_DATA : s1.tdata = first_shifted_data; + PREFIRST_SHIFT_DATA : if (MCASE==MULTI_MIDDLE) + s1.tdata = prefirst_shifted_data; + else + s1.tdata = first_shifted_data; + REM_SHIFT_DATA : if (!TRUNCATE) + s1.tdata = remaining_shift_data; + TRUNCATE_DATA : if (TRUNCATE) + s1.tdata = trunc_data; + BONUS : if (!TRUNCATE && !EXACT) + s1.tdata = bonus_data; + default : s1.tdata = s0.tdata; + endcase + end + + //----------------------------------------------------------------------- + // user write function + // this module ASSUMES user includes error in the MSB and the rest is the + // number of bytes in the word + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] uwrite(error=0,[UWIDTH-2:0] bytes=0); + begin + return {error,bytes}; + end + endfunction + + //----------------------------------------------------------------------- + // get_error -extract error from tuser + //----------------------------------------------------------------------- + function automatic get_error([UWIDTH-1:0] tuser); + begin + return tuser[ERROR]; + end + endfunction + + //----------------------------------------------------------------------- + // get_bytes -extract num_bytes from tuser + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] get_bytes([UWIDTH-1:0] tuser); + logic [UWIDTH-1:0] bytes; + begin + if (tuser[UWIDTH-2:0] == 0) bytes = BYTES_PER_WORD; + else bytes = tuser[UWIDTH-2:0]; + return bytes; + end + endfunction + + // Debug state used to determine which sub-case is taken in simulation + typedef enum {D_IDLE, D_REACHED_START, D_TRUNCATE, D_LAST, D_NOT_LAST, + D_LAST_WO_END, D_LAST_W_END, D_LAST_W_END_BONUS, D_LAST_W_END_PLUS, + D_REACHED_END_PLUS} debug_t; + debug_t debug = D_IDLE; + + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + error_bit_old <= 0; + end else begin + // must hold until bonus completes + if (s1.tlast && s1.tvalid && s1.tready && remove_state==ST_BONUS) begin + error_bit_old <= 0; + // or clear if not going to bonus + end else if (s0.tlast && s0.tvalid && s0.tready && next_remove_state!=ST_BONUS) begin + error_bit_old <= 0; + // but they set based on the input + end else if (s0.tvalid && s0.tready) begin + error_bit_old <= error_bit; + end + end + end + + assign error_bit = get_error(s0.tuser) || error_bit_old; + + // When truncating we want to hold the last valid word until + // the end so we can accumulate any errors that might of occured + if (TRUNCATE && START_BYTE==0) begin + always_comb reached_start = s0.reached_packet_byte(REM_START-1); + end else begin + always_comb reached_start = s0.reached_packet_byte(REM_START); + end + + // the WRAP case leans forward one word since it bridges to + // the next word so it needs to reach end_plus early + // REMOVE statemachine + always_comb begin : reached_end_comb + if (MCASE==MS_WRAP) begin + reached_end = s0.reached_packet_byte(REM_END); + reached_end_plus = s0.reached_packet_byte(REM_END); + end else begin + reached_end = s0.reached_packet_byte(REM_END); + reached_end_plus = s0.reached_packet_byte(REM_END+BYTES_PER_WORD); + end + end + + // because s0.tready feeds back and generates a + // change event for the entire interface, + // it can trigger an infinite loop of assignment + // even when nothing is changing. This breaks + // the feedback loop. + logic s0_tready; + always_comb s0.tready = s0_tready; + + // Remove Statemachine + always_comb begin : remove_next_state + // default assignment of next_state + next_remove_state = remove_state; + debug = D_IDLE; + data_mux_sel = PASS_THRU; + s1.tuser = s0.tuser; + s1.tlast = s0.tlast; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + case (remove_state) + + // ***************************************************** + // PRE_REMOVE - wait till we reach REM_START + // ***************************************************** + ST_PRE_REMOVE: begin + //defaults + data_mux_sel = PASS_THRU; + s1.tuser = s0.tuser; + s1.tlast = s0.tlast; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + + if (reached_start) begin // based only on word count + debug = D_REACHED_START; + + // Truncating word end + if (TRUNCATE && s0.tlast) begin + s1.tlast = 1; + data_mux_sel = ONE_WORD; + debug = D_TRUNCATE; + + // get number of bytes based on if we had enough to surpass END_BYTE + if (START_BYTE == 0) // Exact case + s1.tuser = uwrite(error_bit,in_byte_count); + else if (in_byte_count < START_BYTE) + s1.tuser = uwrite(error_bit,in_byte_count); + else + s1.tuser = uwrite(error_bit,START_BYTE); + + end else if (TRUNCATE && !s0.tlast) begin + s1.tlast = 1; + data_mux_sel = ONE_WORD; + debug = D_TRUNCATE; + + if (s1.tready && s0.tvalid) begin + s1.tlast = 0; + s1.tvalid = 0; + next_remove_state = ST_TRUNCATE; + end + + // packet ends + end else if (s0.tlast) begin + s1.tlast = 1; + data_mux_sel = ONE_WORD; + debug = D_LAST; + // get number of bytes based on if we had enough to surpass END_BYTE + if (in_byte_count < START_BYTE) + s1.tuser = uwrite(error_bit,in_byte_count); + else if (START_WORD != END_WORD) + s1.tuser = uwrite(error_bit,START_BYTE); + else if (in_byte_count < END_BYTE+1) + s1.tuser = uwrite(error_bit,START_BYTE); + else + s1.tuser = uwrite(error_bit,in_byte_count - BYTES_REMOVED); + + // if we are on the first word of the removal and have no way to terminate the packet + // set error. + if ((START_WORD != END_WORD && START_BYTE == 0) || EXACT) + s1.tuser[ERROR] = 1; + + // if removal starts at the start of the packet, squelch the packet. + if ( (START_WORD != END_WORD && REM_START == 0) || + // also if we don't have enough data to publish 1 byte + ((in_byte_count <= END_BYTE+1) && REM_START == 0)) begin + s1.tlast = 0; + s1.tvalid = 0; + end + + end else begin // not the last word + debug = D_NOT_LAST; + + s1.tvalid = 0; + if (s0.tvalid) begin + // we will always need to wait for some more data before + // forming the next word if this was not the start of the packet + next_remove_state = ST_REMOVING; + end + end + end + end //ST_PRE_REMOVE + + // ***************************************************** + // TRUNCATE - wait for end of packet to put out the + // last word (so we can see if error bit asserts) + // ***************************************************** + ST_TRUNCATE: begin + + if (TRUNCATE) begin // Simplify synthesis + // get number of bytes based on if we had enough to surpass END_BYTE + if (get_bytes(first_tuser) < START_BYTE) + s1.tuser = uwrite(error_bit,get_bytes(first_tuser)); + else + s1.tuser = uwrite(error_bit,START_BYTE); + + data_mux_sel = TRUNCATE_DATA; + s1.tlast = s0.tlast; + s1.tvalid = s0.tlast && s0.tvalid; + s0_tready = 1; + if (s1.tready && s0.tvalid && s0.tlast) begin + next_remove_state = ST_PRE_REMOVE; + end + end + end + + // ***************************************************** + // REMOVING - burn words until we have data to + // start sending again + // ***************************************************** + ST_REMOVING: begin + //defaults + data_mux_sel = FIRST_SHIFT_DATA; + s1.tuser = 0; + s1.tlast = 0; + s1.tvalid = 0; + s0_tready = 1; + + // if we don't reach the end of the removal + // it is an error case because we don't + // have any valid data to send with the tlast. + if (s0.tlast && !reached_end && !reached_end_plus) begin + debug = D_LAST_WO_END; + s1.tuser = uwrite(1,0); + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + + // started from zero we have pushed + // zero data so we can just squelch the packet + if (REM_START==0) + s1.tvalid = 0; + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_PRE_REMOVE; + end + + // end of packet and we have some data to send + // but we didn't buffer the extra word of end data yet + end else if (s0.tlast && reached_end && !reached_end_plus) begin + debug = D_LAST_W_END; + + if (MCASE==MULTI_MIDDLE) + data_mux_sel = PREFIRST_SHIFT_DATA; + else if (MCASE==MS_START_AT_LSB) + data_mux_sel = ONE_WORD; + s1.tlast = 1; + + // if we are exact and started from zero we have pushed + // zero data so we can just squelch the packet + if (EXACT && REM_START==0) + s1.tvalid = 0; + else + s1.tvalid = s0.tvalid; + + s0_tready = s1.tready; + + if (MCASE==MULTI_MIDDLE) + if (in_byte_count <= FIRST_BYTE_AFTER) + s1.tuser = uwrite(1,0); // not enough data to avoid error + else + s1.tuser = uwrite(error_bit,in_byte_count + BYTE_CARRY); + else if (MCASE==MS_END_AT_MSB) + s1.tuser = uwrite(1,0); // not enough data to avoid error + else if (in_byte_count <= FIRST_BYTE_AFTER) + if (REM_START == 0) + s1.tvalid = 0; + else + s1.tuser = uwrite(1,0); // not enough data to avoid error + else + s1.tuser = uwrite(error_bit,in_byte_count - FIRST_BYTE_AFTER); + + // if we are exact and have already published some data + // that data is unterminated and we have no way to + // set a packet end. + if (EXACT && REM_START!=0) + s1.tuser[ERROR] = 1; + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_PRE_REMOVE; + end + + // end of packet and we have some some data to send + // and we have more data then we can fit in the + // the current word + end else if (s0.tlast && reached_end_plus && in_byte_count > BYTE_SHIFT + && BYTE_SHIFT != 0) begin + debug = D_LAST_W_END_BONUS; + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = 0; // don't let a advance + + if (s0.tvalid && s1.tready) begin + next_remove_state = ST_BONUS; + end + + // end of packet and we have some some data to send + // and we were ready to send data anyways + end else if(s0.tlast && reached_end_plus) begin + debug = D_LAST_W_END_PLUS; + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + + if (EXACT) + s1.tuser = uwrite(error_bit,in_byte_count); + else begin + s1.tuser = uwrite(error_bit,in_byte_count + BYTE_CARRY); + end + + if (MCASE==MS_WRAP && in_byte_count <= FIRST_BYTE_AFTER) + s1.tuser = uwrite(1,0); // not enough data to avoid error + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_PRE_REMOVE; + end + + // we are ready to send the first byte after the shift + end else if(!s0.tlast && reached_end_plus) begin + debug = D_REACHED_END_PLUS; + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_POST_REMOVE; + end + + end + end + + // ***************************************************** + // POST_REMOVAL waiting for end + // ***************************************************** + ST_POST_REMOVE: begin + //defaults + data_mux_sel = REM_SHIFT_DATA; + s1.tuser = uwrite(error_bit,BYTES_PER_WORD); + s1.tlast = 0; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + // reached the end, but we have extra bytes to send + if (s0.tlast && in_byte_count > BYTE_SHIFT + && BYTE_SHIFT != 0) begin + s1.tlast = 0; + s0_tready = 0; // don't let a advance + + if (s0.tvalid && s1.tready) begin + next_remove_state = ST_BONUS; + end + + // reached the end, and don't need the bonus state + end else if (s0.tlast) begin + s1.tlast = 1; + s1.tuser = uwrite(error_bit,in_byte_count + BYTES_PER_WORD-BYTE_SHIFT); + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_PRE_REMOVE; + end + + end + end + + // ***************************************************** + // BONUS write out any overflow words + // ***************************************************** + ST_BONUS: begin + //defaults + data_mux_sel = BONUS; + s1.tuser = uwrite(error_bit,in_byte_count-BYTE_SHIFT); + s1.tlast = 1; + s1.tvalid = s0.tvalid; + s0_tready = s1.tready; + + if (s1.tready && s0.tvalid) begin + next_remove_state = ST_PRE_REMOVE; + end + + end + + // We should never get here + default: begin + next_remove_state = ST_PRE_REMOVE; + end + endcase + end + + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + remove_state <= ST_PRE_REMOVE; + end else begin + remove_state <= next_remove_state; + end + end + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(o,s1) + end + + end + +endmodule : axi4s_remove_bytes diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_start.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_start.sv new file mode 100644 index 000000000..7c6950ae7 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_start.sv @@ -0,0 +1,333 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module : axi4s_remove_bytes_start +// Description: +// Specialized case of the Remove Bytes problem. This +// sub design is called when REM_START=0 and not EXACT +// +// This implementation requires that the user field +// holds the number of valid bytes in the word, and the MSB of the user field +// indicates if the MAC had an error. +// +// The block will NOT hold off the input if it goes to the BONUS State. +// +// Parameters: +// REM_END - Last byte to remove (Truncate case is invalid) +// + +module axi4s_remove_bytes_start #( + REM_END=3 +)( + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o // AxiStreamIf or AxiStreamPacketIf +); + + // tUSER - always {error,numbytes} + // +1 ,0 to numbytes-1 (0 is a full word) + localparam UWIDTH = $clog2(i.BYTES_PER_WORD)+1; + localparam ERROR = UWIDTH-1; // MSB is the error bit. + + // END is inclusive so +1 + localparam BYTES_REMOVED = REM_END+1; + + // how many bytes into the word for start and end point + localparam END_BYTE = REM_END % i.BYTES_PER_WORD; + localparam END_WORD = REM_END / i.BYTES_PER_WORD; + + // SHIFT_POINT + // BYTE_CARY | BYTE_SHIFT // + // EXAMPLES (32 bit) + // REM_END = 0 3 | 1 i[0] a[3:1] + // REM_END = 1 2 | 2 i[1:0] a[3:2] + // REM_END = 2 1 | 3 i[2:0] a[3] + // REM_END = 3 4 | 0 // EXACT - not valid here + // we use BYTE_CARY bytes from a + // we use BYTE_SHIFT bytes from i + localparam BYTE_SHIFT = BYTES_REMOVED % i.BYTES_PER_WORD; + localparam BYTE_CARRY = i.BYTES_PER_WORD - BYTE_SHIFT; + + initial begin + assert(END_BYTE != i.BYTES_PER_WORD-1 ) else + $fatal(1,"Required to be not EXACT"); + end + + `include "axi4s.vh" + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s1(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TKEEP(0),.MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s2(i.clk,i.rst); + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + + //--------------------------------------- + // remove state machine + //--------------------------------------- + typedef enum {ST_REMOVING, ST_POST_REMOVE} remove_state_t; + remove_state_t remove_state = ST_REMOVING; + remove_state_t next_remove_state = ST_REMOVING; + + + logic s0_wr,s1_rd; + always_comb s0_wr = s0.tvalid && s0.tready; + always_comb s1_rd = s1.tvalid && s1.tready; + // FIFO to buffer up 1 previous value. MUST be 1! + always_ff @(posedge s1.clk) begin + if (s1.rst) begin + // upstream + s1.tdata <= 0; + s1.tuser <= 0; + s1.tlast <= 0; + s1.tvalid <= 0; // fullness + end else begin + + // store data on write + if (s0_wr) begin + // check that we never attempt + // a write that will overflow + // Only 2 valid write times + // (1) Write while empty(tvalid=0) + // (2) Write while reading + assert (!s1.tvalid || s1_rd) else + $fatal(1,"Overflow write"); + + s1.tdata <= s0.tdata; + s1.tuser <= s0.tuser; + s1.tlast <= s0.tlast; + s1.tvalid <= 1; //full + + // if we read without a write + // declare it empty! + end else if (s1_rd) begin + s1.tdata <= 'X; + s1.tuser <= 'X; + s1.tlast <= 1'bX; + s1.tvalid <= 0; //empty + end + + end + end + + // ready to write when not full, or being read + // passes s1_ready back cominatorially + always_comb s0.tready = !s1.tvalid || s1_rd; + + //***************** DATA SHIFTING ***********************/ + function automatic logic [s0.DATA_WIDTH-1-BYTE_SHIFT*8:0] bs_part([s0.DATA_WIDTH-1:0] data); + begin + return data[s0.DATA_WIDTH-1:BYTE_SHIFT*8]; + end + endfunction + + // Note these should all be static shifts. We don't want to infer a barrel shifter. + + // EXAMPLE XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 with START BYTE = 0(A0) END_BYTE = 2(C0) BYTE_SHIFT=3 + // 1st word would be C1 B1 A1/H0 G0 F0 E0 D0 + // [23:0] C1 B1 A1 / [63:24] H0 G0 F0 E0 D0 + // same as shift_data above + // EXAMPLE XX XX XX XX XX XX XX XX + // for 8 byte word H0 G0 F0 E0 D0 C0 B0 A0 + // XX XX XX + // H1 G1 F1 E1 D1 C1 B1 A1 with START BYTE = 0(A0) END_BYTE = 2(10)(C1) BYTE_SHIFT=3 + // 1st word would be C2 B2 A2/H1 G1 F1 E1 D1 + // [23:0] C2 B2 A2 / [63:24] H1 G1 F1 E1 D1 + // same as shift_data above + // NOTE: Entire words are thrown away at start, so no caching required + logic [s0.DATA_WIDTH-1:0] shift_data; + always_comb shift_data = {s0.tdata,bs_part(s1.tdata)}; + + //----------------------------------------------------------------------- + // user write function + // this module ASSUMES user includes error in the MSB and the rest is the + // number of bytes in the word + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] uwrite(error=0,[UWIDTH-2:0] bytes=0); + begin + return {error,bytes}; + end + endfunction + + //----------------------------------------------------------------------- + // get_error -extract error from tuser + //----------------------------------------------------------------------- + function automatic get_error([UWIDTH-1:0] tuser); + begin + return tuser[ERROR]; + end + endfunction + + //----------------------------------------------------------------------- + // get_bytes -extract num_bytes from tuser + //----------------------------------------------------------------------- + function automatic [UWIDTH-1:0] get_bytes([UWIDTH-1:0] tuser); + logic [UWIDTH-1:0] bytes; + begin + if (tuser[UWIDTH-2:0] == 0) bytes = s0.BYTES_PER_WORD; + else bytes = tuser[UWIDTH-2:0]; + return bytes; + end + endfunction + + logic [UWIDTH-1:0] s0_byte_count, s1_byte_count; + always_comb s0_byte_count = get_bytes(s0.tuser); + always_comb s1_byte_count = get_bytes(s1.tuser); + + logic s1_error_bit, s1_error_bit_old; + logic s0_error_bit, s0_error_bit_old; + + always_ff @(posedge s0.clk) begin + if (s0.rst) begin + s0_error_bit_old <= 0; + s1_error_bit_old <= 0; + end else begin + // clear at the end of the packet + if (s1.tlast && s1.tvalid && s1.tready) begin + s1_error_bit_old <= 0; + // but they set based on the input + end else if (s1.tvalid && s1.tready) begin + s1_error_bit_old <= s1_error_bit; + end + // clear at the end of the packet + if (s0.tlast && s0.tvalid && s0.tready) begin + s0_error_bit_old <= 0; + // but they set based on the input + end else if (s1.tvalid && s1.tready) begin + s0_error_bit_old <= s0_error_bit; + end + + end + end + + assign s1_error_bit = get_error(s1.tuser) && s1.tvalid || s1_error_bit_old; + assign s0_error_bit = get_error(s0.tuser) && s0.tvalid || s0_error_bit_old; + + logic s1_reached_end; + always_comb s1_reached_end = s1.reached_packet_byte(REM_END); + + // Remove Statemachine + always_comb begin : remove_next_state + // default assignment of next_state + next_remove_state = remove_state; + // In REM_START=0 case, always shift the same ammount. + s2.tdata = shift_data; + s2.tuser = s1.tuser; + s2.tlast = s1.tlast; + s2.tvalid = s1.tvalid; + s1.tready = s2.tready; + case (remove_state) + // ***************************************************** + // REMOVING - burn words until we have data to + // start sending again + // ***************************************************** + ST_REMOVING: begin + //defaults + s2.tuser = 0; + s2.tlast = 0; + s2.tvalid = 0; + s1.tready = 1; + + //reached removal point + if (s1.tvalid && s1_reached_end) begin + + // if we didn't get enough bytes to fill the word + // squelch the packet + if (s1.tlast && s1_byte_count <= BYTE_SHIFT) begin + s2.tlast = 1; + s2.tvalid = 0; + s1.tready = 1; + // ending in top half of word + end else if (s0.tlast && s0_byte_count <= BYTE_SHIFT) begin + s2.tlast = 1; + s2.tuser = uwrite(s1_error_bit || s0_error_bit,s0_byte_count + BYTE_CARRY); + s2.tvalid = s0.tvalid && s1.tvalid; + s2.tlast = 1; + s1.tready = s2.tready && s0.tvalid; + // ending in bottom half of word + end else if (s1.tlast) begin + s2.tlast = 1; + s2.tuser = uwrite(s1_error_bit,s1_byte_count - BYTE_SHIFT); + s2.tvalid = s1.tvalid; + s1.tready = s2.tready; + // end of removal start sending data! + end else begin + s2.tuser = uwrite(s1_error_bit || s0_error_bit,s0.BYTES_PER_WORD); + s2.tvalid = s0.tvalid && s1.tvalid; + s2.tlast = 0; + s1.tready = s2.tready && s0.tvalid; + if (s2.tready && s0.tvalid && s1.tvalid) begin + next_remove_state = ST_POST_REMOVE; + end + end + end + + end + + // ***************************************************** + // POST_REMOVAL waiting for end + // ***************************************************** + ST_POST_REMOVE: begin + + //defaults + s2.tuser = uwrite(s1_error_bit || s0_error_bit,s0.BYTES_PER_WORD); + s2.tvalid = s0.tvalid && s1.tvalid; + s2.tlast = 0; + s1.tready = s2.tready && s0.tvalid; + + // word is {i[BS-1:0],a[MSB:BS]} + // 32 bit example + // BS1 i[0] : a[3:1] -> + // BS2 i[1:0]: a[3:2] + // BS3 i[2:0]: a[3] + // ending in top half of word + if (s0.tlast && s0_byte_count <= BYTE_SHIFT) begin + s2.tuser = uwrite(s1_error_bit || s0_error_bit,s0_byte_count + BYTE_CARRY); + s2.tvalid = s0.tvalid && s1.tvalid; + s2.tlast = 1; + if (s2.tready && s0.tvalid && s1.tvalid) begin + next_remove_state = ST_REMOVING; + end + // ending in bottom half of word + end else if (s1.tlast) begin + s2.tuser = uwrite(s1_error_bit,s1_byte_count - BYTE_SHIFT); + s2.tvalid = s1.tvalid; + s2.tlast = 1; + if (s2.tready && s1.tvalid) begin + next_remove_state = ST_REMOVING; + end + // ending in second half of shift word + end + end + + // We should never get here + default: begin + next_remove_state = ST_REMOVING; + end + endcase + end + + always_ff @(posedge s1.clk) begin + if (s1.rst) begin + remove_state <= ST_REMOVING; + end else begin + remove_state <= next_remove_state; + end + end + + // move from AxiStreamPacketIf to AxiStreamIfc + always_comb begin + `AXI4S_ASSIGN(o,s2) + end + +endmodule : axi4s_remove_bytes_start diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/Makefile b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/Makefile new file mode 100644 index 000000000..e017851ed --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/Makefile @@ -0,0 +1,48 @@ +# +# Copyright 2020 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +#------------------------------------------------- +# Top-of-Makefile +#------------------------------------------------- +# Define BASE_DIR to point to the "top" dir +BASE_DIR = $(abspath ../../../top) +# Include viv_sim_preamble after defining BASE_DIR +include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak + +#------------------------------------------------- +# Design Specific +#------------------------------------------------- + +# Include makefiles and sources for the DUT and its dependencies +include $(BASE_DIR)/../lib/axi4s_sv/Makefile.srcs + +DESIGN_SRCS = $(abspath \ +$(AXI4S_SV_SRCS) \ +) + +#------------------------------------------------- +# Testbench Specific +#------------------------------------------------- +# Define only one toplevel module +TB_TOP_MODULE = axi4s_remove_bytes_all_tb +SIM_TOP = $(TB_TOP_MODULE) + +SIM_SRCS = \ +$(abspath $(TB_TOP_MODULE).sv )\ +$(abspath axi4s_remove_bytes_tb.sv ) + +# supressing the following worthless reminder. +#* Warning: M:/usrp4-hw/oss-repo/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes.sv(228): (vlog-2583) [SVCHK] - +# Extra checking for conflicts with always_comb and always_latch variables is done at vopt time +SVLOG_ARGS = -suppress 2583 + +#------------------------------------------------- +# Bottom-of-Makefile +#------------------------------------------------- +# Include all simulator specific makefiles here +# Each should define a unique target to simulate +# e.g. xsim, vsim, etc and a common "clean" target +include $(BASE_DIR)/../tools/make/viv_simulator.mak diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_all_tb.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_all_tb.sv new file mode 100644 index 000000000..129c5e65e --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_all_tb.sv @@ -0,0 +1,139 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_remove_bytes_all_tb +// +// Description: Testbench for axi4s_remove_bytes_all_tb +// + +module axi4s_remove_bytes_all_tb #( + /* no PARAM */ +)( + /* no IO */ +); + + // actual use cases + // Strip Ethernet header from a packet includes a preamble + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_64_HREMOVE48"),.WIDTH(64),.REM_START(0),.REM_END(47)) + ENET_64_HREMOVE48 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_128_HREMOVE48"),.WIDTH(128),.REM_START(0),.REM_END(47)) + ENET_128_HREMOVE48 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_256_HREMOVE48"),.WIDTH(256),.REM_START(0),.REM_END(47)) + ENET_256_HREMOVE48 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_512_HREMOVE48"),.WIDTH(512),.REM_START(0),.REM_END(47)) + ENET_512_HREMOVE48 (); + + // Strip Ethernet header from a packet without a preamble + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_64_HREMOVE42"),.WIDTH(64),.REM_START(0),.REM_END(41)) + ENET_64_HREMOVE42 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_128_HREMOVE42"),.WIDTH(128),.REM_START(0),.REM_END(41)) + ENET_128_HREMOVE42 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_256_HREMOVE42"),.WIDTH(256),.REM_START(0),.REM_END(41)) + ENET_256_HREMOVE42 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_512_HREMOVE42"),.WIDTH(512),.REM_START(0),.REM_END(41)) + ENET_512_HREMOVE42 (); + + // Strip Preamble header from a packet + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_64_PREMOVE6"),.WIDTH(64),.REM_START(0),.REM_END(5)) + ENET_64_PREMOVE6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_128_PREMOVE6"),.WIDTH(128),.REM_START(0),.REM_END(5)) + ENET_128_PREMOVE6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_256_PREMOVE6"),.WIDTH(256),.REM_START(0),.REM_END(5)) + ENET_256_PREMOVE6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("ENET_512_PREMOVE6"),.WIDTH(512),.REM_START(0),.REM_END(5)) + ENET_512_PREMOVE6 (); + + + // TRUNCATE cases - + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_1:0"),.WIDTH(32),.REM_START(1),.REM_END(-1)) + TRUNCATE_32_0to1 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_2:0"),.WIDTH(32),.REM_START(2),.REM_END(-1)) + TRUNCATE_32_0to2 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_3:0"), .WIDTH(32),.REM_START(3),.REM_END(-1)) + TRUNCATE_32_0to3 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_4:0"),.WIDTH(32),.REM_START(4),.REM_END(-1)) + TRUNCATE_32_0to4 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_5:0"),.WIDTH(32),.REM_START(5),.REM_END(-1)) + TRUNCATE_32_0to5 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_6:0"),.WIDTH(32),.REM_START(6),.REM_END(-1)) + TRUNCATE_32_0to6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("TRUNCATE_32_7:0"), .WIDTH(32),.REM_START(7),.REM_END(-1)) + TRUNCATE_32_0to7 (); + + + // START cases - removal starts at LSB of word + axi4s_remove_bytes_tb #(.TEST_NAME("SSTART_32_0:0"),.WIDTH(32),.REM_START(0),.REM_END(0)) + SSTART_32_0to0 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SSTART_32_1:0"),.WIDTH(32),.REM_START(0),.REM_END(1)) + SSTART_32_0to1 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SSTART_32_2:0"),.WIDTH(32),.REM_START(0),.REM_END(2)) + SSTART_32_0to2 (); + axi4s_remove_bytes_tb #(.TEST_NAME("EXACT_32_3:0"), .WIDTH(32),.REM_START(0),.REM_END(3)) + EXACT_32_0to3 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MSTART_32_4:0"),.WIDTH(32),.REM_START(0),.REM_END(4)) + MSTART_32_0to4 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MSTART_32_5:0"),.WIDTH(32),.REM_START(0),.REM_END(5)) + MSTART_32_0to5 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MSTART_32_6:0"),.WIDTH(32),.REM_START(0),.REM_END(6)) + MSTART_32_0to6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("EXACT_32_7:0"), .WIDTH(32),.REM_START(0),.REM_END(7)) + EXACT_32_0to7 (); + + + // END cases - removal ends at MSB of word + axi4s_remove_bytes_tb #(.TEST_NAME("MEND_32_7:1"),.WIDTH(32),.REM_START(1),.REM_END(7)) + MEND_32_1to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MEND_32_7:2"),.WIDTH(32),.REM_START(2),.REM_END(7)) + MEND_32_2to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MEND_32_7:3"),.WIDTH(32),.REM_START(3),.REM_END(7)) + MEND_32_3to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("EXACT_32_7:4"),.WIDTH(32),.REM_START(4),.REM_END(7)) + EXACT_32_4to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_7:5"),.WIDTH(32),.REM_START(5),.REM_END(7)) + SEND_32_5to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_7:6"),.WIDTH(32),.REM_START(6),.REM_END(7)) + SEND_32_6to7 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_7:7"),.WIDTH(32),.REM_START(7),.REM_END(7)) + SEND_32_7to7 (); + + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_3:1"),.WIDTH(32),.REM_START(1),.REM_END(3)) + SEND_32_1to3 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_3:2"),.WIDTH(32),.REM_START(2),.REM_END(3)) + SEND_32_2to3 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_3:3"),.WIDTH(32),.REM_START(3),.REM_END(3)) + SEND_32_3to3 (); + + // MIDDLE cases - removal is in the middle of words + axi4s_remove_bytes_tb #(.TEST_NAME("SMID_32_1:1"),.WIDTH(32),.REM_START(1),.REM_END(1)) + SMID_32_1to1 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SMID_32_2:1"),.WIDTH(32),.REM_START(1),.REM_END(2)) + SMID_32_2to1 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SMID_32_2:2"),.WIDTH(32),.REM_START(2),.REM_END(2)) + SMID_32_2to2 (); + + axi4s_remove_bytes_tb #(.TEST_NAME("MMID_32_5:1"),.WIDTH(32),.REM_START(1),.REM_END(5)) + MMID_32_1to5 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MMID_32_6:1"),.WIDTH(32),.REM_START(1),.REM_END(6)) + MMID_32_1to6 (); + axi4s_remove_bytes_tb #(.TEST_NAME("MMID_32_6:2"),.WIDTH(32),.REM_START(2),.REM_END(6)) + MMID_32_2to6 (); + + // WRAP cases - removal is over word boundary + axi4s_remove_bytes_tb #(.TEST_NAME("SWRAP_32_5:2"),.WIDTH(32),.REM_START(2),.REM_END(5)) + SWRAP_32_2to5 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SWRAP_32_5:3"),.WIDTH(32),.REM_START(3),.REM_END(5)) + SWRAP_32_3to5 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SWRAP_32_6:3"),.WIDTH(32),.REM_START(3),.REM_END(6)) + SWRAP_32_3to6 (); + + axi4s_remove_bytes_tb #(.TEST_NAME("MMID_32_9:2"),.WIDTH(32),.REM_START(2),.REM_END(9)) + MWRAP_32_2to9 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_9:3"),.WIDTH(32),.REM_START(3),.REM_END(9)) + MWRAP_32_3to9 (); + axi4s_remove_bytes_tb #(.TEST_NAME("SEND_32_10:3"),.WIDTH(32),.REM_START(3),.REM_END(10)) + MWRAP_32_3to10(); + + +endmodule diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_tb.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_tb.sv new file mode 100644 index 000000000..c7b2587c7 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_remove_bytes_tb/axi4s_remove_bytes_tb.sv @@ -0,0 +1,233 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi4s_remove_bytes_tb +// +// Description: Testbench for axi_remove_bytes +// + +module axi4s_remove_bytes_tb #( + parameter TEST_NAME = "axi_remove_bytes", + WIDTH=32,REM_START=0,REM_END=7 +)( + /* no IO */ +); + // Include macros and time declarations for use with PkgTestExec + // 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". + `define TEST_EXEC_OBJ test + `include "test_exec.svh" + import PkgAxiStreamBfm::*; + import PkgTestExec::*; + import PkgEthernet::*; + + //--------------------------------------------------------------------------- + // Local Parameters + //--------------------------------------------------------------------------- + localparam UWIDTH = $clog2((WIDTH/8)+1); + localparam MAX_PACKET_BYTES = 16*1024; + + typedef AxiStreamPacket #(WIDTH, UWIDTH) AxisPacket_t; + typedef XportStreamPacket #(WIDTH) XportPacket_t; + + //--------------------------------------------------------------------------- + // Clocks + //--------------------------------------------------------------------------- + + bit clk; + bit reset; + + sim_clock_gen #(.PERIOD(5.0), .AUTOSTART(1)) + clk_gen (.clk(clk), .rst(reset)); + + //--------------------------------------------------------------------------- + // Bus Functional Models + //--------------------------------------------------------------------------- + TestExec test = new(); + AxiStreamIf #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + i (clk, reset); + AxiStreamIf #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + o (clk, reset); + + // Bus functional model for a axi_stream controller + AxiStreamBfm #(.DATA_WIDTH(WIDTH),.USER_WIDTH(UWIDTH),.TKEEP(0), + .MAX_PACKET_BYTES(MAX_PACKET_BYTES)) + axis = new(.master(i), .slave(o)); + + //---------------------------------------------------- + // Instantiate DUT + //---------------------------------------------------- + + axi4s_remove_bytes #(.REM_START(REM_START),.REM_END(REM_END)) + DUT (.*); + + //--------------------------------------------------------------------------- + // Reset + //--------------------------------------------------------------------------- + + task test_reset(); + test.start_test("Wait for Reset", 10us); + clk_gen.reset(); + wait(!reset); + repeat (10) @(posedge clk); + test.end_test(); + endtask : test_reset + + //--------------------------------------------------------------------------- + // Ethernet to CPU test + //--------------------------------------------------------------------------- + + task automatic test_samples(int num_samples); + localparam PKTS_TO_SEND = 10; + localparam LOW_LIM = DUT.EXACT?DUT.START_WORD-1:DUT.START_WORD; + localparam HIGH_LIM = REM_END; + + automatic string test_str; + automatic raw_pkt_t raw; + automatic XportPacket_t send[$]; + automatic XportPacket_t expected[$]; + automatic integer last_word_index; + test_str = $sformatf({TEST_NAME,"::Remove Bytes L=%3d"},num_samples); + test.start_test(test_str, 10us); + + for (int i= 0 ; i < PKTS_TO_SEND; i++) begin + + expected[i] = new; + send[i] = new; + + get_ramp_raw_pkt(.num_samps(num_samples),.ramp_start((i*num_samples)%256),.ramp_inc(1),.pkt(raw),.SWIDTH(8)); + send[i].push_bytes(raw); + // Remove the bytes from raw packet to build expected. + if (REM_END < 0) begin + raw = raw[0:REM_START-1]; + end else begin + for (int i = 0 ; i < REM_END-REM_START+1 ; ++i) begin + if (raw.size()-1 >= REM_START) begin + raw.delete(REM_START); + end + end + end + expected[i].push_bytes(raw); + send[i].tkeep_to_tuser(.ERROR_PROB(10)); + expected[i].tkeep_to_tuser(); + if (send[i].has_error()) + expected[i].set_error(); + + // if the incoming packet stops at a word + // where we have no where to mark an error + // then we set an error + last_word_index = send[i].data.size()-1; + if (last_word_index > LOW_LIM && + num_samples-1 <= HIGH_LIM) begin + if (!DUT.TRUNCATE) + if (DUT.START_WORD != DUT.END_WORD || DUT.EXACT) + expected[i].set_error(); + end + end + + for (int i= PKTS_TO_SEND-1 ; i >= 0; i--) begin + if (expected[i].data.size() == 0) expected.delete(i); + end + + fork + begin : send_thread + foreach(send[i])begin + axis.put(send[i]); + end + end + begin : expected_thread + + if (expected.size() == 0) begin : no_expected + automatic string str; + automatic AxisPacket_t actual_a = new(); + // I need a better way to flush the send queue. + #(2*WIDTH); // wait a bit for completion + o.tready = 1; + axis.wait_send(); // wait complete hung + #(2*WIDTH); // wait a bit for completion + str = $sformatf("UNEXPECTED_PACKET L=%3d\nREM_START=%3d:%1d REM_END=%3d:%1d SIZE =%3d WIDTH=%3d\nSEND\n%s", + num_samples,REM_START,DUT.START_WORD,REM_END,DUT.END_WORD,send[0].data.size(),WIDTH, + send[0].sprint()); + if (axis.try_get(actual_a)) begin + $display("ACTUAL"); + actual_a.print(); + `ASSERT_ERROR(actual_a.data.size() == 0,str); + end + $display("Wait completed"); + + end else begin : get_expected + automatic string str; + automatic AxisPacket_t actual_a = new(); + automatic XportPacket_t actual = new(); + foreach(expected[i]) begin + axis.get(actual_a); + actual.import_axis(actual_a); + actual.tuser_to_tkeep(); + str = $sformatf({"L= %3d REM =%2d:%1d WORD=%1d:%1d ERROR_LIM=%1d<%1d %1d<=%1d WIDTH=%3d","\nSEND\n%s"}, + num_samples, + REM_START,REM_END, + DUT.START_WORD,DUT.END_WORD, + LOW_LIM,last_word_index,num_samples-1,HIGH_LIM,WIDTH, + send[i].sprint()); + `ASSERT_ERROR(!actual.compare_w_error(expected[i],.COMPARE_ERROR_PACKETS(0)),str); + end + end + end + join + test.end_test(); + + endtask : test_samples + + + //---------------------------------------------------- + // Main test loop + //---------------------------------------------------- + initial begin : tb_main + automatic integer min_length; + + test.tb_name = TEST_NAME; + + if (REM_END == -1) + min_length = REM_START-4; + else + min_length = REM_END-4; + + if ( min_length < 1) min_length = 1; + + axis.run(); + + test_reset(); + // power of 2 from zero + for (int i=0 ; i < 6 ; ++i) begin + test_samples(2**i); + end + + for (int i=1 ; i < (WIDTH/8)*3 ; ++i) begin + test_samples(min_length+i); + end + + // repeat back to back + axis.set_slave_stall_prob(0); + axis.set_master_stall_prob(0); + + for (int i=1 ; i < (WIDTH/8)*3 ; ++i) begin + test_samples(min_length+i); + end + + // End the TB, but don't $finish, since we don't want to kill other + // instances of this testbench that may be running. + test.end_tb(0); + + // Kill the clocks to end this instance of the testbench + clk_gen.kill(); + + end // initial begin + +endmodule +`undef TEST_EXEC_OBJ diff --git a/fpga/usrp3/lib/axi4s_sv/axi4s_width_conv.sv b/fpga/usrp3/lib/axi4s_sv/axi4s_width_conv.sv new file mode 100644 index 000000000..f05f70438 --- /dev/null +++ b/fpga/usrp3/lib/axi4s_sv/axi4s_width_conv.sv @@ -0,0 +1,137 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// System Verilog wrapper for axis_width_conv that accepts a AxiStreamIfc +// with slave_user/master_user interface. This block requires that the +// bottom bits of user contain the number of bytes in the last word. +// +// If TKEEP - tkeep contains byte_enables +// If USER_TRAILING_BYTES - tuser contains byte_enables +// Else everything is a full word +// +// Parameters: +// PIPELINE - Add pipelining for timing. +// SYNC_CLKS - Input and output clock are the same. +// I_USER_TRAILING_BYTES - input byte_enable is set by user +// O_USER_TRAILING_BYTES - output byte_enable is set in user +// + +module axi4s_width_conv #( + PIPELINE = "NONE", + bit I_USER_TRAILING_BYTES = 0, + bit O_USER_TRAILING_BYTES = 0, + bit SYNC_CLKS = 1 +) ( + interface i, // AxiStreamIf or AxiStreamPacketIf + interface o // AxiStreamIf or AxiStreamPacketIf +); + + localparam IWIDTH =i.DATA_WIDTH; + localparam OWIDTH =o.DATA_WIDTH; + + `include "axi4s.vh" + // Parameter Checks + initial begin + if(i.TKEEP) begin + assert (!I_USER_TRAILING_BYTES) else + $fatal("I_USER_TRAILING_BYTE set at the same time as TKEEP"); + assert (!i.TUSER) else + $fatal("i.TUSER set- This module does not pass user"); + end else if(I_USER_TRAILING_BYTES) begin + assert (i.USER_WIDTH >= i.TRAILING_WIDTH ) else + $fatal("i.USER_WIDTH does not match TRAILING_WIDTH"); + end else begin + assert (!i.TUSER) else + $fatal("This module does not pass generic user_data"); + end + + if(o.TKEEP) begin + assert (!O_USER_TRAILING_BYTES) else + $fatal("O_USER_TRAILING_BYTE set at the same time as TKEEP"); + assert (!o.TUSER) else + $fatal("O.TUSER set- This module does not pass user"); + end else if(O_USER_TRAILING_BYTES) begin + assert (o.USER_WIDTH >= o.TRAILING_WIDTH) else + $fatal("o.USER_WIDTH does not match TRAILING_WIDTH"); + end else begin + assert (!o.TUSER) else + $fatal("This module does not pass generic user_data"); + end + + assert (i.TLAST == 1) else + $fatal("i.TLAST not present"); + assert (o.TLAST == 1) else + $fatal("o.TLAST not present"); + end + + AxiStreamPacketIf #(.DATA_WIDTH(i.DATA_WIDTH),.USER_WIDTH(i.USER_WIDTH), + .TDATA(i.TDATA),.TKEEP(i.TKEEP),.TUSER(i.TUSER),.TLAST(i.TLAST), + .MAX_PACKET_BYTES(i.MAX_PACKET_BYTES)) + s0(i.clk,i.rst); + AxiStreamPacketIf #(.DATA_WIDTH(o.DATA_WIDTH),.USER_WIDTH(o.USER_WIDTH), + .TDATA(o.TDATA),.TKEEP(o.TKEEP),.TUSER(o.TUSER),.TLAST(o.TLAST), + .MAX_PACKET_BYTES(o.MAX_PACKET_BYTES)) + s1(o.clk,o.rst); + + // move from AxiStreamIfc to AxiStreamPacketIf + always_comb begin + `AXI4S_ASSIGN(s0,i) + end + // move from AxiStreamPacketIf to AxiStreamIfc + always_comb begin + `AXI4S_ASSIGN(o,s1) + end + + logic [IWIDTH/8-1:0] s0_tkeep; + + if (s0.TKEEP) begin + always_comb s0_tkeep = s0.tkeep; + end else if (I_USER_TRAILING_BYTES) begin + always_comb s0_tkeep = s0.get_trailing_bytes(); + end else begin + always_comb s0_tkeep = '1; + end + + logic [OWIDTH/8-1:0] s1_tkeep; + logic [15:0] s1_bytes; + + if (s1.TKEEP) begin + always_comb s1.tkeep = s1_tkeep; + always_comb s1.tuser = 'X; + end else if (O_USER_TRAILING_BYTES) begin + always_comb s1.tkeep = 'X; + always_comb begin : assign_s1_tuser + s1.tuser = 0; + // MODELSIM_BUG - deleting the s1_bytes assignment causes modelsim failures. + s1_bytes = s1.keep2trailing(s1_tkeep); + s1.set_trailing_bytes(s1_tkeep); + end + end else begin + always_comb s1.tkeep = 'X; + always_comb s1.tuser = 'X; + end + + logic s0_ready, s1_valid, s1_last; + logic [s1.DATA_WIDTH-1:0] s1_data; + always_comb s0.tready = s0_ready; + always_comb s1.tvalid = s1_valid; + always_comb s1.tlast = s1_last; + always_comb s1.tdata = s1_data; + + axis_width_conv #( + .IN_WORDS(IWIDTH/8), .OUT_WORDS(OWIDTH/8), + .SYNC_CLKS(SYNC_CLKS), .PIPELINE(PIPELINE) + ) axis_width_conv ( + .s_axis_aclk(s0.clk), .s_axis_rst(s0.rst), + .s_axis_tdata(s0.tdata), .s_axis_tkeep(s0_tkeep), .s_axis_tlast(s0.tlast), + .s_axis_tvalid(s0.tvalid), .s_axis_tready(s0_ready), + + .m_axis_aclk(s1.clk), .m_axis_rst(s1.rst), + .m_axis_tdata(s1_data), .m_axis_tkeep(s1_tkeep), .m_axis_tlast(s1_last), + .m_axis_tvalid(s1_valid), .m_axis_tready(s1.tready) + ); + +endmodule : axi4s_width_conv diff --git a/fpga/usrp3/sim/rfnoc/Makefile.srcs b/fpga/usrp3/sim/rfnoc/Makefile.srcs index d1b59c8e2..31196678f 100644 --- a/fpga/usrp3/sim/rfnoc/Makefile.srcs +++ b/fpga/usrp3/sim/rfnoc/Makefile.srcs @@ -11,6 +11,7 @@ SIM_RFNOC_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/, \ axi4lite_sv/PkgAxiLite.sv \ axi4lite_sv/AxiLiteIf.sv \ +axi4s_sv/AxiStreamIf.sv \ )) ################################################## @@ -23,6 +24,7 @@ test_exec.svh \ sim_clock_gen.sv \ PkgAxiStreamBfm.sv \ PkgAxiLiteBfm.sv \ +PkgEthernet.sv \ PkgChdrData.sv \ PkgChdrUtils.sv \ PkgChdrBfm.sv \ diff --git a/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv b/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv index 9e2530350..fd23e501c 100644 --- a/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv +++ b/fpga/usrp3/sim/rfnoc/PkgAxiStreamBfm.sv @@ -1,61 +1,79 @@ // -// Copyright 2019 Ettus Research, A National Instruments Company +// Copyright 2020 Ettus Research, A National Instruments Brand // // SPDX-License-Identifier: LGPL-3.0-or-later // -// Module: PkgAxiStream +// Module: PkgAxiStreamBfm // -// Description: Package for a bi-directional AXI Stream bus functional model -// (BFM). This consists of the AxiStreamIf interface, and the -// AxiStreamPacket and AxiStreamBfm classes. +// Description: Package for a bi-directional AXI Stream bus functional model +// (BFM). This consists of the AxiStreamPacket and AxiStreamBfm classes. +// It's based on the AxiStreamIf in lib/axi4s_svPkgAXI4S.sv // - //----------------------------------------------------------------------------- -// Unidirectional AXI4-Stream interface +// AXI-Stream BFM Package //----------------------------------------------------------------------------- -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 +package PkgAxiStreamBfm; + //--------------------------------------------------------------------------- + // Raw packets - packet of just bytes + //--------------------------------------------------------------------------- + // Ethernet in particular defies normal word boundaries, so underneath treating + // it as the most fundamental quantity - Bytes. + typedef byte raw_pkt_t[$]; // Byte Queue + + // Push a packet with random data onto to the AXI Stream bus + // Args: + // - num_bytes: number of random bytes to add to the raw packet. + // - pkt: packet with rand data + task automatic get_rand_raw_pkt ( + input int num_bytes, + output raw_pkt_t pkt); + begin + repeat(num_bytes) begin + pkt.push_back($urandom); + end + end + endtask + + // Push a packet with a ramp on to the AXI Stream bus + // Args: + // - num_samps: Packet size in bytes *8 + // - ramp_start: Start value for the ramp + // - ramp_inc: Increment per clock cycle + task automatic get_ramp_raw_pkt ( + input int num_samps, + input logic [63:0] ramp_start, + input logic [63:0] ramp_inc, + input int SWIDTH=64, + output raw_pkt_t pkt); + begin + logic[63:0] word; + automatic integer counter = 0; + repeat(num_samps) begin + word = ramp_start+(counter*ramp_inc); + for (int i=0; i < SWIDTH ; i+=8) begin + pkt.push_back(word[i +: 8]); + end + counter = counter + 1; + end + end + endtask -//----------------------------------------------------------------------------- -// AXI-Stream BFM Package -//----------------------------------------------------------------------------- + // Comparison Functions + function automatic bit raw_pkt_compare(input raw_pkt_t a, input raw_pkt_t b); -package PkgAxiStreamBfm; + bit queue_match; + queue_match = 1; + // check each element of the queue and clear queue_match if they don't match. + // workaround for vivado bug - could be a==b + foreach(a[i]) queue_match = queue_match && a[i] == b[i]; + return ((a.size() == b.size()) && queue_match); + endfunction //--------------------------------------------------------------------------- // AXI Stream Packet Class @@ -73,7 +91,7 @@ package PkgAxiStreamBfm; typedef AxiStreamPacket #(DATA_WIDTH, USER_WIDTH) AxisPacket_t; - + bit verbose=0; //------------ // Properties //------------ @@ -105,6 +123,12 @@ package PkgAxiStreamBfm; keep = {}; endfunction; + // Delete a word from the current packet + function void delete(int i); + data.delete(i); + user.delete(i); + keep.delete(i); + endfunction; // Return true if this packet equals that of the argument virtual function bit equal(AxisPacket_t packet); @@ -117,21 +141,30 @@ package PkgAxiStreamBfm; foreach (data[i]) begin data_a = data[i]; data_b = packet.data[i]; - if (data_a !== data_b) return 0; + if (data_a !== data_b) begin + if (verbose) $display("AxisPacket data mismatch a[%2d]=%X b[%2d]=%X",i,data_a,i,data_b); + return 0; + end end if (user.size() != packet.user.size()) return 0; foreach (data[i]) begin user_a = user[i]; user_b = packet.user[i]; - if (user_a !== user_b) return 0; + if (user_a !== user_b) begin + if (verbose) $display("AxisPacket user mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b); + return 0; + end end if (keep.size() != packet.keep.size()) return 0; foreach (keep[i]) begin keep_a = keep[i]; keep_b = packet.keep[i]; - if (keep_a !== keep_b) return 0; + if (keep_a !== keep_b) begin + if (verbose) $display("AxisPacket keep mismatch a[%2d]=%X b[%2d]=%X",i,user_a,i,user_b); + return 0; + end end return 1; @@ -141,10 +174,22 @@ package PkgAxiStreamBfm; // Format the contents of the packet into a string function string sprint(); string str = ""; + string data_str = ""; if (data.size() == user.size() && data.size() == keep.size()) begin str = { str, "data, user, keep:\n" }; foreach (data[i]) begin - str = { str, $sformatf("%5d> %X %X %b\n", i, data[i], user[i], keep[i]) }; + data_str = ""; + if (DATA_WIDTH > 64) begin + for (int b=0; b < DATA_WIDTH; b +=64) begin + data_str = { data_str, $sformatf("%3d: %X",b,data[i][b+:64])}; + if (b+64 < DATA_WIDTH) begin + data_str = { data_str, $sformatf("\n ")}; + end + end + end else begin + data_str = { data_str, $sformatf("%X",data[i]) }; + end + str = { str, $sformatf("%5d> %s %X %X \n", i, data_str, user[i], keep[i]) }; end end else begin str = { str, "data:\n" }; @@ -169,6 +214,45 @@ package PkgAxiStreamBfm; $display(sprint()); endfunction : print + // Add an array of bytes (little endian) + function void push_bytes(raw_pkt_t raw, input user_t user = '0); + data_t word; + keep_t my_keep; + while (raw.size() > 0) begin + // fill tkeep + // SIZE = TKEEP / 0 = 0000, 1 = 0001, 2 = 0011, etc + my_keep = '1; + if (raw.size <= DATA_WIDTH/8) begin + foreach (my_keep[i]) my_keep[i] = i < (raw.size); + end + // fill the word with raw data from bottom up + word = '0; + for (int i = 0; i < DATA_WIDTH/8 ; i++) begin + if (my_keep[i]) word[i*8 +: 8] = raw.pop_front(); + end + this.data.push_back(word); + this.keep.push_back(my_keep); + this.user.push_back(user); + end + endfunction + + // Dump data contents as an array of bytes. (little endian) + function raw_pkt_t dump_bytes(); + data_t word; + keep_t my_keep; + raw_pkt_t raw; + assert (data.size == keep.size) else + $fatal("data and keep have different sizes!"); + foreach (data[i]) begin + my_keep = this.keep[i]; + word = this.data[i]; + for (int j = 0; j < DATA_WIDTH/8 ; j++) begin + if (my_keep[j]) raw.push_back(word[j*8 +: 8]); + end; + end + return raw; + endfunction + endclass : AxiStreamPacket; @@ -178,8 +262,13 @@ package PkgAxiStreamBfm; //--------------------------------------------------------------------------- class AxiStreamBfm #( - parameter int DATA_WIDTH = 64, - parameter int USER_WIDTH = 1 + int DATA_WIDTH = 64, + int USER_WIDTH = 1, + int MAX_PACKET_BYTES = 0, + bit TDATA = 1, + bit TUSER = 1, + bit TKEEP = 1, + bit TLAST = 1 ); //------------------ @@ -205,13 +294,16 @@ package PkgAxiStreamBfm; local const AxisPacket_t::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; + local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, + TDATA,TUSER,TKEEP,TLAST).master master; + local virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, + TDATA,TUSER,TKEEP,TLAST).slave slave; // NOTE: We should not need these flags if Vivado would be OK with null check // without throwing unnecessary null-ptr deref exceptions. local bit master_en; local bit slave_en; + bit slave_tready_init = 0; // Queues to store the bus transactions mailbox #(AxisPacket_t) tx_packets; mailbox #(AxisPacket_t) rx_packets; @@ -220,6 +312,8 @@ package PkgAxiStreamBfm; protected int master_stall_prob = DEF_STALL_PROB; protected int slave_stall_prob = DEF_STALL_PROB; + // Number of clocks betwen packets + int inter_packet_gap = 0; //--------- // Methods @@ -231,11 +325,13 @@ package PkgAxiStreamBfm; endfunction : packets_equal - // Class constructor. This must be given an interface for the master + // 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 + virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, + TDATA,TUSER,TKEEP,TLAST).master master, + virtual AxiStreamIf #(DATA_WIDTH,USER_WIDTH,MAX_PACKET_BYTES, + TDATA,TUSER,TKEEP,TLAST).slave slave ); this.master_en = (master != null); this.slave_en = (slave != null); @@ -253,7 +349,7 @@ package PkgAxiStreamBfm; endtask : put - // Attempt to queue the provided packet for transmission. Return 1 if + // Attempt to queue the provided packet for transmission. Return 1 if // successful, return 0 if the queue is full. function bit try_put(AxisPacket_t packet); assert (master_en) else $fatal(1, "Cannot use TX operations for a null master"); @@ -268,7 +364,7 @@ package PkgAxiStreamBfm; endtask : get - // Get the next packet if there's one available and return 1. Return 0 if + // Get the next packet if there's one available and return 1. Return 0 if // there's no packet available. function bit try_get(output AxisPacket_t packet); assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); @@ -276,7 +372,7 @@ package PkgAxiStreamBfm; endfunction : try_get - // Get the next packet when it becomes available (wait if necessary), but + // Get the next packet when it becomes available (wait if necessary), but // don't remove it from the receive queue. task peek(output AxisPacket_t packet); assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); @@ -284,8 +380,8 @@ package PkgAxiStreamBfm; 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 + // Get the next packet if there's one available and return 1, but don't + // remove it from the receive queue. Return 0 if there's no packet // available. function bit try_peek(output AxisPacket_t packet); assert (slave_en) else $fatal(1, "Cannot use RX operations for a null slave"); @@ -300,8 +396,8 @@ package PkgAxiStreamBfm; endfunction - // Wait until num packets have started transmission (i.e., until num - // packets have been dequeued). Set num = -1 to wait until all currently + // 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; @@ -318,7 +414,7 @@ package PkgAxiStreamBfm; endtask : wait_send - // Wait until num packets have completed transmission. Set num = -1 to wait + // 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; @@ -340,7 +436,7 @@ package PkgAxiStreamBfm; endtask : wait_complete - // Set the probability (as a percentage, 0 to 100) of the master interface + // 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 @@ -350,7 +446,7 @@ package PkgAxiStreamBfm; endfunction - // Set the probability (as a percentage, 0 to 100) of the slave interface + // 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 @@ -360,14 +456,14 @@ package PkgAxiStreamBfm; endfunction - // Get the probability (as a percentage, 0 to 100) of the master interface + // 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 + // 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; @@ -397,8 +493,10 @@ package PkgAxiStreamBfm; master.tlast <= 0; forever begin - @(posedge master.clk); - if (master.rst) continue; + repeat(inter_packet_gap) begin + @(posedge master.clk); + if (master.rst) continue; + end if (tx_packets.try_get(packet)) begin foreach (packet.data[i]) begin @@ -433,6 +531,9 @@ package PkgAxiStreamBfm; master.tuser <= IDLE_USER; master.tkeep <= IDLE_KEEP; master.tlast <= 0; + end else begin + @(posedge master.clk); + if (master.rst) continue; end end endtask : master_body @@ -445,7 +546,7 @@ package PkgAxiStreamBfm; local task slave_body(); AxisPacket_t packet = new(); - slave.tready <= 0; + slave.tready <= slave_tready_init; forever begin @(posedge slave.clk); diff --git a/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv b/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv index 148ef5488..cc4c9ac18 100644 --- a/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv +++ b/fpga/usrp3/sim/rfnoc/PkgAxisCtrlBfm.sv @@ -5,7 +5,7 @@ // // Module: PkgAxisCtrlBfm // -// Description: Package for an AXIS-Ctrl bus functional model (BFM), which +// Description: Package for an AXIS-Ctrl bus functional model (BFM), which // consists primarily of the AxisCtrlPacket and AxisCtrlBfm classes. // @@ -129,7 +129,7 @@ package PkgAxisCtrlBfm; // AXIS-Ctrl BFM Methods //--------------------------------------------------------------------------- - // Class constructor. This must be given an interface for the master + // 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, @@ -147,7 +147,7 @@ package PkgAxisCtrlBfm; endtask : put_ctrl - // Attempt to queue the provided packet for transmission. Return 1 if + // 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_t axis_packet; @@ -163,7 +163,7 @@ package PkgAxisCtrlBfm; endtask : get_ctrl - // Get the next packet if there's one available and return 1. Return 0 if + // 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_t axis_packet; @@ -173,7 +173,7 @@ package PkgAxisCtrlBfm; endfunction : try_get_ctrl - // Convert an AXIS-Ctrl packet data structure to an AXI-Stream packet data + // Convert an AXIS-Ctrl packet data structure to an AXI-Stream packet data // structure. function AxisCtrlBfm::AxisPacket_t AxisCtrlBfm::axis_ctrl_to_axis(AxisCtrlPacket ctrl_packet); AxisPacket_t axis_packet = new(); @@ -184,7 +184,7 @@ package PkgAxisCtrlBfm; 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 + 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 @@ -201,12 +201,12 @@ package PkgAxisCtrlBfm; endfunction : axis_ctrl_to_axis - // Convert an AXI-Stream packet data structure to an AXIS-Ctrl packet data + // Convert an AXI-Stream packet data structure to an AXIS-Ctrl packet data // structure. function AxisCtrlPacket AxisCtrlBfm::axis_to_axis_ctrl(AxisPacket_t 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]; @@ -224,7 +224,7 @@ package PkgAxisCtrlBfm; // Grab data ctrl_packet.data = axis_packet.data[(i+1):$]; - + return ctrl_packet; endfunction : axis_to_axis_ctrl diff --git a/fpga/usrp3/sim/rfnoc/PkgEthernet.sv b/fpga/usrp3/sim/rfnoc/PkgEthernet.sv new file mode 100644 index 000000000..a971b5c7e --- /dev/null +++ b/fpga/usrp3/sim/rfnoc/PkgEthernet.sv @@ -0,0 +1,809 @@ +// +// Copyright 2020 Ettus Research, A National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: PkgEthernet +// +// Description: This package defines the data types used to represent ETHER, +// IPV4, and UDP. It's based on a queue of bytes representation named +// raw_pkt_t. +// + +package PkgEthernet; + + import PkgAxiStreamBfm::*; + export PkgAxiStreamBfm::*; + + //************************************************************// + //////////////////// ETHER PACKETS ///////////////////////////// + //************************************************************// + + // Ether type - subset of possibilities. Add more as needed. + typedef enum logic [15:0] { + // Byte order 1 0 + IPV4=16'h0800, IPV6=16'h86_DD, VLAN_TAGGED=16'h8100, + DOUBLE_VLAN_TAGGED=16'h9100, ROCE=16'h8915 + } ether_type_t; + + // Some MAC addresses + // Byte order 5 4 3 2 1 0 + localparam logic [47:0] DEF_DEST_MAC_ADDR = 48'h5E_35_EB_71_46_F7; + localparam logic [47:0] DEF_SRC_MAC_ADDR = 48'h98_03_9B_8E_09_B9; + localparam logic [47:0] DEF_BRIDGE_MAC_ADDR = 48'hBB_BB_BB_BB_BB_BB; + + // Ether Header + typedef struct { + logic [47:0] dest_mac = DEF_DEST_MAC_ADDR; + logic [47:0] src_mac = DEF_SRC_MAC_ADDR; + ether_type_t ether_type = IPV4; + } eth_hdr_t; + + // Ethernet Packet, Header + Payload + typedef struct { + eth_hdr_t hdr; + raw_pkt_t payload; + logic [31:0] fcs; + int ipg = 12; // interpacket gap + } eth_pkt_t; + + // Break an eth_pkt into a queue of bytes + function automatic raw_pkt_t flatten_eth_pkt(input eth_pkt_t pkt); + raw_pkt_t pay; + + pay.push_back(pkt.hdr.dest_mac[47:40]); + pay.push_back(pkt.hdr.dest_mac[39:32]); + pay.push_back(pkt.hdr.dest_mac[31:24]); + pay.push_back(pkt.hdr.dest_mac[23:16]); + pay.push_back(pkt.hdr.dest_mac[15:8]); + pay.push_back(pkt.hdr.dest_mac[7:0]); + pay.push_back(pkt.hdr.src_mac[47:40]); + pay.push_back(pkt.hdr.src_mac[39:32]); + pay.push_back(pkt.hdr.src_mac[31:24]); + pay.push_back(pkt.hdr.src_mac[23:16]); + pay.push_back(pkt.hdr.src_mac[15:8]); + pay.push_back(pkt.hdr.src_mac[7:0]); + pay.push_back(pkt.hdr.ether_type[15:8]); + pay.push_back(pkt.hdr.ether_type[7:0]); + pay = {pay,pkt.payload}; + + return pay; + + endfunction + + // Break a queue of bytes into a eth_pkt + function automatic eth_pkt_t unflatten_eth_pkt(input raw_pkt_t pay); + eth_pkt_t pkt; + + pkt.hdr.dest_mac[47:40] = pay.pop_front(); + pkt.hdr.dest_mac[39:32] = pay.pop_front(); + pkt.hdr.dest_mac[31:24] = pay.pop_front(); + pkt.hdr.dest_mac[23:16] = pay.pop_front(); + pkt.hdr.dest_mac[15:8] = pay.pop_front(); + pkt.hdr.dest_mac[7:0] = pay.pop_front(); + pkt.hdr.src_mac[47:40] = pay.pop_front(); + pkt.hdr.src_mac[39:32] = pay.pop_front(); + pkt.hdr.src_mac[31:24] = pay.pop_front(); + pkt.hdr.src_mac[23:16] = pay.pop_front(); + pkt.hdr.src_mac[15:8] = pay.pop_front(); + pkt.hdr.src_mac[7:0] = pay.pop_front(); + pkt.hdr.ether_type[15:8] = pay.pop_front(); + pkt.hdr.ether_type[7:0] = pay.pop_front(); + pkt.payload = pay; + + return pkt; + + endfunction + + function automatic logic eth_pkt_compare(input eth_pkt_t a, input eth_pkt_t b); + + return ((a.hdr.dest_mac == b.hdr.dest_mac) && + (a.hdr.src_mac == b.hdr.src_mac) && + (a.hdr.ether_type == b.hdr.ether_type) && + raw_pkt_compare(a.payload,b.payload)); + + endfunction + + //************************************************************// + //////////////////// IPV4 PACKETS ////////////////////////////// + //************************************************************// + + // IP Protocol - subset of possibilities. add more as needed + typedef enum logic [7:0] { + UDP=8'd17, TCP=8'd6, ICMP=8'd1, IGMP=8'd2, ENCAP=8'd41 + } ip_protocol_t; + + // follow normal convention of an IP address + function automatic logic [31:0] ip(logic [7:0] a,b,c,d); + return {a,b,c,d}; + endfunction + + + localparam logic [31:0] DEF_DEST_IP_ADDR = ip(192,168,10,2); + localparam logic [31:0] DEF_SRC_IP_ADDR = ip(192,168,10,1); + localparam logic [31:0] DEF_BRIDGE_IP_ADDR = 32'h33_33_33_33; + + // IPv4 Header + typedef struct { + logic [3:0] header_length = 4'd5; + logic [3:0] version = 4'd4; + logic [5:0] dscp = 6'b0000_00; + logic [1:0] ecn = 2'b00; + logic [15:0] length = 16'hXXXX; //flag for (fill it in please) + logic [15:0] identification = 16'h462E; + logic rsv_zero = 1'b0; + logic dont_frag = 1'b1; + logic more_frag = 1'b0; + logic [12:0] frag_offset = 16'd0; + logic [7:0] time_to_live = 16'd64; + ip_protocol_t protocol = UDP; + logic [15:0] checksum = 16'hXXXX; //flag for (fill it in please) + logic [31:0] src_ip = DEF_SRC_IP_ADDR; + logic [31:0] dest_ip = DEF_DEST_IP_ADDR; + } ipv4_hdr_t; + + // IP Packet, Header + Payload + typedef struct { + ipv4_hdr_t hdr; + raw_pkt_t payload; + } ipv4_pkt_t; + + // The checksum for an IP header is the sum of all the 16 bit words that + // make up the header with the checksum set to zero. Add back the carry over + // from bits [31:16] then invert. + // See https://en.wikipedia.org/wiki/IPv4_header_checksum + function automatic logic [15:0] calc_ipv4_checksum(input raw_pkt_t pkt); + + // This is a bit oversized, but it's not costing anything. + // 10 max sized words can at most add logbase2 of 10 bits. + logic [31:0] checksum; + + checksum = 0; + // Iterate over 16 bit chunks reading from a byte addressed memory. + // There are 20 bytes in an ipv4 header. + for (int i = 0 ; i < 20 ; i+=2 ) begin + // BIG endian network ordering... so weird + checksum += {pkt[i],pkt[i+1]}; + end + checksum += checksum[31:16]; + checksum = ~checksum; + + return checksum[15:0]; + + endfunction + + // Break an eth_pkt into a queue of bytes + function automatic raw_pkt_t flatten_ipv4_pkt(input ipv4_pkt_t pkt); + raw_pkt_t pay; + + logic [15:0] length; + logic [15:0] checksum; + logic [2:0] flags; + + // If header or length is not set to default value then use the value in + // the packet. + if ($isunknown(pkt.hdr.length)) + length = pkt.payload.size()+20; // 20 because length includes IP header length. + else + length = pkt.hdr.length; + + flags = {pkt.hdr.more_frag,pkt.hdr.dont_frag,pkt.hdr.rsv_zero}; + // Start off with checksum as 0 + checksum = 0; + + // 20 byte IP header + pay.push_back({pkt.hdr.version,pkt.hdr.header_length}); // byte 0 + pay.push_back({pkt.hdr.dscp,pkt.hdr.ecn}); // byte 1 + pay.push_back(length[15:8]); // byte 2 + pay.push_back(length[7:0]); // byte 3 + pay.push_back(pkt.hdr.identification[15:8]); // byte 4 + pay.push_back(pkt.hdr.identification[7:0]); // byte 5 + pay.push_back({flags,pkt.hdr.frag_offset[12:8]}); // byte 6 + pay.push_back(pkt.hdr.frag_offset[7:0]); // byte 7 + pay.push_back(pkt.hdr.time_to_live); // byte 8 + pay.push_back(pkt.hdr.protocol); // byte 9 + pay.push_back(checksum[15:8]); // byte 10 + pay.push_back(checksum[7:0]); // byte 11 + pay.push_back(pkt.hdr.src_ip[31:24]); // byte 12 + pay.push_back(pkt.hdr.src_ip[23:16]); // byte 13 + pay.push_back(pkt.hdr.src_ip[15:8]); // byte 14 + pay.push_back(pkt.hdr.src_ip[7:0]); // byte 15 + pay.push_back(pkt.hdr.dest_ip[31:24]); // byte 16 + pay.push_back(pkt.hdr.dest_ip[23:16]); // byte 17 + pay.push_back(pkt.hdr.dest_ip[15:8]); // byte 18 + pay.push_back(pkt.hdr.dest_ip[7:0]); // byte 19 + pay = {pay,pkt.payload}; + + if ($isunknown(pkt.hdr.checksum)) + checksum = calc_ipv4_checksum(pay); + else + checksum = pkt.hdr.checksum; + // replace the checksum (bytes 11:10 + pay[10] = checksum[15:8]; + pay[11] = checksum[7:0]; + + return pay; + endfunction + + // Break a queue of bytes into a ip_pkt + function automatic ipv4_pkt_t unflatten_ipv4_pkt(input raw_pkt_t pay); + ipv4_pkt_t pkt; + + // 20 byte IP header + {pkt.hdr.version, + pkt.hdr.header_length} = pay.pop_front(); // byte 0 + {pkt.hdr.dscp,pkt.hdr.ecn} = pay.pop_front(); // byte 1 + pkt.hdr.length[15:8] = pay.pop_front(); // byte 2 + pkt.hdr.length[7:0] = pay.pop_front(); // byte 3 + pkt.hdr.identification[15:8] = pay.pop_front(); // byte 4 + pkt.hdr.identification[7:0] = pay.pop_front(); // byte 5 + {pkt.hdr.more_frag, + pkt.hdr.dont_frag, + pkt.hdr.rsv_zero, + pkt.hdr.frag_offset[12:8]} = pay.pop_front(); // byte 6 + pkt.hdr.frag_offset[7:0] = pay.pop_front(); // byte 7 + pkt.hdr.time_to_live = pay.pop_front(); // byte 8 + pkt.hdr.protocol = ip_protocol_t'(pay.pop_front()); // byte 9 + pkt.hdr.checksum[15:8] = pay.pop_front(); // byte 10 + pkt.hdr.checksum[7:0] = pay.pop_front(); // byte 11 + pkt.hdr.src_ip[31:24] = pay.pop_front(); // byte 12 + pkt.hdr.src_ip[23:16] = pay.pop_front(); // byte 13 + pkt.hdr.src_ip[15:8] = pay.pop_front(); // byte 14 + pkt.hdr.src_ip[7:0] = pay.pop_front(); // byte 15 + pkt.hdr.dest_ip[31:24] = pay.pop_front(); // byte 16 + pkt.hdr.dest_ip[23:16] = pay.pop_front(); // byte 17 + pkt.hdr.dest_ip[15:8] = pay.pop_front(); // byte 18 + pkt.hdr.dest_ip[7:0] = pay.pop_front(); // byte 19 + pkt.payload = pay; + + return pkt; + + endfunction + + function automatic logic ipv4_pkt_compare(input ipv4_pkt_t a, input ipv4_pkt_t b); + + return ((a.hdr.header_length == b.hdr.header_length) && + (a.hdr.version == b.hdr.version) && + (a.hdr.dscp == b.hdr.dscp) && + (a.hdr.ecn == b.hdr.ecn) && + (a.hdr.length == b.hdr.length) && + (a.hdr.identification == b.hdr.identification) && + (a.hdr.rsv_zero == b.hdr.rsv_zero) && + (a.hdr.dont_frag == b.hdr.dont_frag) && + (a.hdr.more_frag == b.hdr.more_frag) && + (a.hdr.frag_offset == b.hdr.frag_offset) && + (a.hdr.time_to_live == b.hdr.time_to_live) && + (a.hdr.protocol == b.hdr.protocol) && + (a.hdr.checksum == b.hdr.checksum) && + (a.hdr.src_ip == b.hdr.src_ip) && + (a.hdr.dest_ip == b.hdr.dest_ip) && + raw_pkt_compare(a.payload,b.payload)); + + endfunction + + //************************************************************// + //////////////////// UDP PACKETS /////////////////////////////// + //************************************************************// + + localparam logic [15:0] DEF_SRC_UDP_PORT = 16'd49748; + localparam logic [15:0] DEF_DEST_UDP_PORT = 16'd49153; + localparam logic [15:0] DEF_BRIDGE_UDP_PORT = 16'h66_55; + + // UDP Header + typedef struct { + logic [15:0] src_port = DEF_SRC_UDP_PORT; + logic [15:0] dest_port = DEF_DEST_UDP_PORT; + logic [15:0] length = 16'hXXXX; //flag for (fill it in please) + logic [15:0] checksum = 16'hXXXX; //flag for (fill it in please) + } udp_hdr_t; + + // UDP Packet, Header + Payload + typedef struct { + udp_hdr_t hdr; + raw_pkt_t payload; + } udp_pkt_t; + + function automatic logic [15:0] calc_udp_checksum( + input logic [31:0] src_ip, + input logic [31:0] dest_ip, + input raw_pkt_t pkt); + + logic [31:0] checksum; + raw_pkt_t virtual_header; + + // UDP checksum is calculated over a virtual header that is added to + // the front of the packet. + virtual_header.push_back(src_ip[31:24]); // byte 0 + virtual_header.push_back(src_ip[23:16]); // byte 1 + virtual_header.push_back(src_ip[15:8]); // byte 2 + virtual_header.push_back(src_ip[7:0]); // byte 3 + virtual_header.push_back(dest_ip[31:24]); // byte 4 + virtual_header.push_back(dest_ip[23:16]); // byte 5 + virtual_header.push_back(dest_ip[15:8]); // byte 6 + virtual_header.push_back(dest_ip[7:0]); // byte 7 + virtual_header.push_back(0); // byte 8 + virtual_header.push_back(UDP); // byte 9 UDP (Protocol enum) x11 + virtual_header.push_back(0); // byte 10 + virtual_header.push_back(pkt[6]); // byte 11 Length + virtual_header.push_back(pkt[7]); // byte 12 Length + + pkt = {virtual_header,pkt}; // add virtual header in front + + checksum = 0; + // Iterate over 16 bit chunks reading from an array of bytes + // need to traverse the virtual header / udp header / udp data + for (int i = 0 ; i < pkt.size ; i+=2 ) begin + // BIG endian network ordering... so weird + checksum += {pkt[i],pkt[i+1]}; + end + checksum += checksum[31:16]; + checksum = ~checksum; + + return checksum[15:0]; + + endfunction + + // Break a udp_pkt into a queue of bytes + function automatic raw_pkt_t flatten_udp_pkt( + input logic [31:0] src_ip, + input logic [31:0] dest_ip, + input udp_pkt_t pkt); + raw_pkt_t pay; + + logic [15:0] length; + logic [15:0] checksum; + + // If header or length is not set to default value then use the value in + // the packet. + if ($isunknown(pkt.hdr.length)) + length = pkt.payload.size()+8; // 8 because length includes UDP header length. + else + length = pkt.hdr.length; + + //temporary checksum + checksum = 0; + + pay.push_back(pkt.hdr.src_port[15:8]); // byte 0 + pay.push_back(pkt.hdr.src_port[7:0]); // byte 1 + pay.push_back(pkt.hdr.dest_port[15:8]); // byte 2 + pay.push_back(pkt.hdr.dest_port[7:0]); // byte 3 + pay.push_back(length[15:8]); // byte 4 + pay.push_back(length[7:0]); // byte 5 + pay.push_back(checksum[15:8]); // byte 6 + pay.push_back(checksum[7:0]); // byte 7 + pay = {pay,pkt.payload}; + + if ($isunknown(pkt.hdr.checksum)) + checksum = calc_udp_checksum(src_ip,dest_ip,pay); + else + checksum = pkt.hdr.checksum; + + pay[6] = checksum[15:8]; + pay[7] = checksum[7:0]; + + return pay; + + endfunction + + // Break a queue of bytes into a udp_pkt + function automatic udp_pkt_t unflatten_udp_pkt(input raw_pkt_t pay); + udp_pkt_t pkt; + + pkt.hdr.src_port[15:8] = pay.pop_front(); + pkt.hdr.src_port[7:0] = pay.pop_front(); + pkt.hdr.dest_port[15:8] = pay.pop_front(); + pkt.hdr.dest_port[7:0] = pay.pop_front(); + pkt.hdr.length[15:8] = pay.pop_front(); + pkt.hdr.length[7:0] = pay.pop_front(); + pkt.hdr.checksum[15:8] = pay.pop_front(); + pkt.hdr.checksum[7:0] = pay.pop_front(); + pkt.payload = pay; + + return pkt; + + endfunction + + function automatic logic udp_pkt_compare(input udp_pkt_t a, input udp_pkt_t b); + + return ((a.hdr.src_port == b.hdr.src_port) && + (a.hdr.dest_port == b.hdr.dest_port) && + (a.hdr.length == b.hdr.length) && + raw_pkt_compare(a.payload,b.payload)); + + endfunction + + typedef enum int { + NO_PREAMBLE=0, NORMAL_PREAMBLE=1, ZERO_PREAMBLE=2 + } preamble_t; + + // Build up a raw UDP packet. + // Args: + // - pkt: Packet data (queue) + // - stream: Stream to use (Optional) + function automatic raw_pkt_t build_udp_pkt ( + input eth_hdr_t eth_hdr, + input ipv4_hdr_t ipv4_hdr, + input udp_hdr_t udp_hdr, + input raw_pkt_t pay, + input int preamble = NO_PREAMBLE); + + automatic udp_pkt_t udp_pkt; + automatic ipv4_pkt_t ipv4_pkt; + automatic eth_pkt_t eth_pkt; + automatic raw_pkt_t raw_pkt; + + udp_pkt.hdr = udp_hdr; + udp_pkt.payload = pay; + ipv4_pkt.hdr = ipv4_hdr; + ipv4_pkt.payload = flatten_udp_pkt(ipv4_hdr.src_ip,ipv4_hdr.dest_ip,udp_pkt); + eth_pkt.hdr = eth_hdr; + eth_pkt.payload = flatten_ipv4_pkt(ipv4_pkt); + raw_pkt = flatten_eth_pkt(eth_pkt); + if (preamble==NORMAL_PREAMBLE) begin + raw_pkt.push_front(8'hAB); + raw_pkt.push_front(8'hAA); + raw_pkt.push_front(8'hAA); + raw_pkt.push_front(8'hAA); + raw_pkt.push_front(8'hAA); + raw_pkt.push_front(8'hAA); + end else if (preamble==ZERO_PREAMBLE) begin + raw_pkt.push_front(8'h00); + raw_pkt.push_front(8'h00); + raw_pkt.push_front(8'h00); + raw_pkt.push_front(8'h00); + raw_pkt.push_front(8'h00); + raw_pkt.push_front(8'h00); + end + return raw_pkt; + + endfunction + + // Wait for a packet to finish on the bus + // and decode it + task automatic decode_udp_pkt ( + input raw_pkt_t raw_pkt, + output eth_hdr_t eth_hdr, + output ipv4_hdr_t ipv4_hdr, + output udp_hdr_t udp_hdr, + output raw_pkt_t payload); + + eth_pkt_t eth_pkt; + ipv4_pkt_t ipv4_pkt; + udp_pkt_t udp_pkt; + + eth_pkt = unflatten_eth_pkt(raw_pkt); + ipv4_pkt = unflatten_ipv4_pkt(eth_pkt.payload); + udp_pkt = unflatten_udp_pkt(ipv4_pkt.payload); + + eth_hdr = eth_pkt.hdr; + ipv4_hdr = ipv4_pkt.hdr; + udp_hdr = udp_pkt.hdr; + payload = udp_pkt.payload; + + endtask + + //--------------------------------------------------------------------------- + // XPORT Stream Packet Class + //--------------------------------------------------------------------------- + // Extensions to the AxiStreamPacket used in the XPORT code + class XportStreamPacket #( + int DATA_WIDTH = 64 + ) extends AxiStreamPacket #(DATA_WIDTH, $clog2((DATA_WIDTH/8)+1)); + + typedef XportStreamPacket #(DATA_WIDTH) XportPacket_t; + localparam UWIDTH = $clog2((DATA_WIDTH/8)+1); + // Class constructor. + function new (); + super.new(); + endfunction : new + + // Return a handle to a copy of this transaction + function XportPacket_t copy(); + XportPacket_t temp; + temp = new(); + temp.data = this.data; + temp.user = this.user; + temp.keep = this.keep; + return temp; + endfunction + + // bring in data from an AxisPacket + function void import_axis(AxisPacket_t axi_pkt); + this.data = axi_pkt.data; + this.user = axi_pkt.user; + this.keep = axi_pkt.keep; + endfunction : import_axis + + // take the tuser signal's and use them to set + // tkeep signals + task automatic tuser_to_tkeep(logic PRESERVE_TUSER=1); + keep_t last_tkeep; + user_t last_tuser; + logic [$clog2(DATA_WIDTH/8)-1:0] last_bytes; + last_tuser = this.user[$]; + last_bytes = last_tuser[$clog2(DATA_WIDTH/8)-1:0]; + // set all the tuser values to zero + // set all the tuser values to zero + foreach (this.user[i]) begin + this.keep[i] = '1; + if(!PRESERVE_TUSER) + this.user[i] = 0; + end + // figure out the value of tkeep for the last word + if (last_bytes == 0) last_tkeep = '1; + // check if there is an X + else if ($isunknown(last_tuser)) last_tkeep = '1; + else begin + last_tkeep = 0; + foreach (last_tkeep[i]) begin + last_tkeep[i] = i < last_bytes; + end + end + this.keep[$] = last_tkeep; + // set data bytes where the value isn't used to zero + foreach (last_tkeep[i]) begin + if (last_tkeep[i] == 0) this.data[$][i*8 +: 8] = 0; + end + endtask : tuser_to_tkeep + + // take the tkeep signal's and use them to set + // width in the user signals + task automatic tkeep_to_tuser(int ERROR_PROB=0); + keep_t last_tkeep; + user_t last_tuser; + + last_tkeep = this.keep[$]; + // set all the tuser values to zero + foreach (this.user[i]) begin + this.user[i] = 0; + this.user[i][UWIDTH-1] = $urandom_range(99) < ERROR_PROB; + end + // check if there is an X + if ($isunknown(last_tkeep)) last_tuser = '0; + else begin + last_tuser = 0; + foreach (last_tkeep[i]) begin + if (last_tkeep[i]==1'b1) begin + last_tuser = last_tuser+1; + end + end + end + // full word is 0. MSB set is error + if (last_tuser == DATA_WIDTH/8)last_tuser = 0; + this.user[$] = last_tuser; + this.user[$][UWIDTH-1] = $urandom_range(99) < ERROR_PROB; + + // set data bytes where the value isn't used to zero + foreach (last_tkeep[i]) begin + if (last_tkeep[i] == 0) this.data[$][i*8 +: 8] = 0; + end + + endtask : tkeep_to_tuser + + function automatic logic has_error(); + logic error; + + error = 0; + foreach (this.user[i]) begin + //catch if error was set + error = error || this.user[i][UWIDTH-1]; + end + return error; + endfunction : has_error + + function automatic int byte_length(); + int bytes; + int last_bytes; + bytes = (this.data.size()-1)*DATA_WIDTH/8; + last_bytes = this.user[$][UWIDTH-2:0]; + if (last_bytes == 0) + bytes+=DATA_WIDTH/8; + else + bytes+=last_bytes; + + return bytes; + + endfunction : byte_length + + function automatic void clear_error(); + + foreach (this.user[i]) begin + this.user[i][UWIDTH-1] = 0; + end + + endfunction : clear_error + + function automatic void clear_keep(); + + foreach (this.keep[i]) begin + this.keep[i] = 0; + end + + endfunction : clear_keep + + function automatic void clear_user(); + + foreach (this.user[i]) begin + this.user[i] = 0; + end + + endfunction : clear_user + + task automatic set_error(); + foreach (this.user[i]) begin + this.user[i][UWIDTH-1] = 1; + end + endtask : set_error + + ///// compare_w_error + // Check that this packet has expected error bit in tuser + // Keep is not compared + // If COMPARE_ERROR_PACKETS is 0 + // Don't check packet contents + // If COMPARE_ERROR_PACKETS is 1 + // Check that this packet matches the expected packet + function automatic logic compare_w_error( + XportPacket_t expected, + int COMPARE_ERROR_PACKETS=1 + ); + + automatic XportPacket_t actual_copy = this.copy(); + automatic XportPacket_t expected_copy = expected.copy(); + + logic exp_error=0; + logic act_error=0; + logic error_condition; + + exp_error = expected.has_error(); + act_error = this.has_error(); + + actual_copy.clear_error(); + expected_copy.clear_error(); + actual_copy.clear_keep(); + expected_copy.clear_keep(); + + error_condition = (!expected_copy.equal(actual_copy) && + (!exp_error || COMPARE_ERROR_PACKETS)) || + act_error != exp_error; + if (error_condition) begin + $display("Expected"); + expected.print(); + $display("Actual"); + this.print(); + if (!expected_copy.equal(actual_copy)) + $display("ERROR :: packet mismatch"); + if (act_error != exp_error) + $display("ERROR :: error mismatch"); + end + + return error_condition; + + endfunction : compare_w_error + + ///// compare_w_sof + // Check that this packet has expected sof bit in tuser + // Keep is not compared + // Check that this packet matches the expected packet + function automatic logic compare_w_sof(XportPacket_t expected); + + automatic XportPacket_t actual_copy = this.copy(); + automatic XportPacket_t expected_copy = expected.copy(); + + logic sof_error=0; + foreach (this.user[i]) begin + if (i==0) begin + // set if top bit of user isn't set on the first word. + sof_error = !this.user[i][UWIDTH-1]; + end else begin + // set if top bit of user is set on any other word. + sof_error = this.user[i][UWIDTH-1] || sof_error; + end + end + + // error bit doubles for SOF + actual_copy.clear_error(); + expected_copy.clear_error(); + actual_copy.clear_keep(); + expected_copy.clear_keep(); + + // set SOF in expected + expected_copy.user[0][UWIDTH-1] = 0; + + if (!expected_copy.equal(actual_copy) || + sof_error) begin + $display("Expected"); + expected_copy.print(); + $display("Actual"); + this.print(); + if (!expected_copy.equal(actual_copy)) + $display("ERROR :: packet mismatch"); + if (sof_error) + $display("ERROR :: sof mismatch"); + end + + return !expected_copy.equal(actual_copy) || + sof_error; + + endfunction : compare_w_sof + + ///// compare_w_pad + // Check that this packet has expected sof bit in tuser + // Keep is not compared + // Check that this packet matches the expected packet + // if DUMB_ORIGINAL_WAY + // User is not compared + // else + // Check that this packets tuser matches the expected packet + function automatic logic compare_w_pad( + XportPacket_t expected, + logic DUMB_ORIGINAL_WAY=0 + ); + + automatic XportPacket_t actual_copy = this.copy(); + automatic XportPacket_t expected_copy = expected.copy(); + + // not using MSB as error here. + actual_copy.clear_error(); + expected_copy.clear_error(); + actual_copy.clear_keep(); + expected_copy.clear_keep(); + // Add pad bytes to user + if (DUMB_ORIGINAL_WAY) begin + // I can't figure out how to calculate the expected on the + // original so I'm just copying the actual + foreach (expected_copy.user[i]) begin + expected_copy.user[i] = actual_copy.user[i]; + end + end + + if (!expected_copy.equal(actual_copy)) begin + $display("Expected"); + expected_copy.print(); + $display("Actual"); + this.print(); + if (!expected_copy.equal(actual_copy)) + $display("ERROR :: packet mismatch"); + end + + return !expected_copy.equal(actual_copy); + + endfunction : compare_w_pad + + ///// compare_no_user + // Check that this packet has expected sof bit in tuser + // Keep is not compared + // Check that this packet matches the expected packet + // User is not compared + function automatic logic compare_no_user(XportPacket_t expected); + + automatic XportPacket_t actual_copy = this.copy(); + automatic XportPacket_t expected_copy = expected.copy(); + + // not using MSB as error here. + actual_copy.clear_error(); + expected_copy.clear_error(); + actual_copy.clear_keep(); + expected_copy.clear_keep(); + + // Add pad bytes to user + foreach (expected_copy.user[i]) begin + expected_copy.user[i] = 0; + actual_copy.user[i] = 0; + end + + if (!expected_copy.equal(actual_copy)) begin + $display("Expected"); + expected.print(); + $display("Actual"); + this.print(); + if (!expected_copy.equal(actual_copy)) + $display("ERROR :: packet mismatch"); + end + + return !expected_copy.equal(actual_copy); + + endfunction : compare_no_user + + endclass : XportStreamPacket; + +endpackage : PkgEthernet diff --git a/fpga/usrp3/tools/utils/testbenches.excludes b/fpga/usrp3/tools/utils/testbenches.excludes index 7ac5b134f..79c3bb557 100644 --- a/fpga/usrp3/tools/utils/testbenches.excludes +++ b/fpga/usrp3/tools/utils/testbenches.excludes @@ -13,3 +13,7 @@ top/n3xx/sim/ten_gig_eth_loopback top/x300/sim/x300_pcie_int top/n3xx/dboards/eiscat/radio/noc_block_ddc_eiscat_tb top/n3xx/dboards/eiscat/radio/noc_block_radio_core_eiscat_tb + +# These testbenches only work in ModelSim +lib/axi4s_sv/axi4s_remove_bytes_tb +lib/axi4s_sv/axi4s_add_bytes_tb |