// // 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