// // Copyright 2019 Ettus Research, A National Instruments Company // // SPDX-License-Identifier: LGPL-3.0-or-later // // Module: context_builder // // Description: // // This module builds the payload and context data streams necessary for RFnoC // communication through an AXI-Stream Raw Data (Simple Interface). It takes as // input an AXI-Stream data bus and sideband buses containing the timestamp and // packet flags. // // For each AXI-Stream raw data packet that is input, the same data packet will // be output in the payload stream along with the context stream that's // necessary to create a CHDR packet for this data packet. // // The timestamp and flags must be input coincident with the AXI-Stream data // input. The timestamp and flag inputs will be sampled coincident with the // last word of data in the packet (i.e., when tlast is asserted). // // In order to determine the length of the packet, the entire packet is // buffered before the header in the context stream is generated. Therefore, // the internal FIFO size (configured by MTU) must be large enough to buffer // the maximum packet size. // // The maximum number of packets that can be simultaneously buffered in this // block is limited by INFO_FIFO_SIZE, where the maximum number of packets is // 2**INFO_FIFO_SIZE. This must be large enough to handle the expected worse // case, or data flow will stall. // // Parameters: // // CHDR_W : Width of the CHDR interface (width of context words) // ITEM_W : Number of samples/items per data word // NIPC : Number of samples/items per clock cycle // MTU : Log2 of maximum transfer unit (maximum packet size) in CHDR_W sized words. // INFO_FIFO_SIZE : Size of the internal packet info FIFO is 2**INFO_FIFO_SIZE // module context_builder #( parameter CHDR_W = 64, parameter ITEM_W = 32, parameter NIPC = 2, parameter MTU = 10, parameter INFO_FIFO_SIZE = 5 ) ( input axis_data_clk, input axis_data_rst, // Data stream in (AXI-Stream) input wire [(ITEM_W*NIPC)-1:0] s_axis_tdata, input wire [ NIPC-1:0] s_axis_tkeep, input wire s_axis_tlast, input wire s_axis_tvalid, output wire s_axis_tready, // Sideband info (sampled on the first cycle of the packet) input wire [ 63:0] s_axis_ttimestamp, input wire s_axis_thas_time, input wire s_axis_teov, input wire s_axis_teob, // Data stream out (AXI-Stream Payload) output wire [(ITEM_W*NIPC)-1:0] m_axis_payload_tdata, output wire [ NIPC-1:0] m_axis_payload_tkeep, output wire m_axis_payload_tlast, output wire m_axis_payload_tvalid, input wire m_axis_payload_tready, // Data stream out (AXI-Stream Context) output reg [CHDR_W-1:0] m_axis_context_tdata, output reg [ 3:0] m_axis_context_tuser, output reg m_axis_context_tlast, output reg m_axis_context_tvalid = 1'b0, input wire m_axis_context_tready ); `include "../core/rfnoc_chdr_utils.vh" reg packet_info_fifo_full; //--------------------------------------------------------------------------- // Data FIFO //--------------------------------------------------------------------------- // // This FIFO buffers packet data while we calculate each packet's length. // //--------------------------------------------------------------------------- wire s_axis_tvalid_df; wire s_axis_tready_df; // Compute MTU (maximum packet) size in data words from the CHDR word MTU. localparam DATA_FIFO_SIZE = MTU + $clog2(CHDR_W) - $clog2(ITEM_W*NIPC); axi_fifo #( .WIDTH (NIPC + 1 + ITEM_W*NIPC), .SIZE (DATA_FIFO_SIZE) ) data_fifo ( .clk (axis_data_clk), .reset (axis_data_rst), .clear (1'b0), .i_tdata ({s_axis_tkeep, s_axis_tlast, s_axis_tdata}), .i_tvalid (s_axis_tvalid_df), .i_tready (s_axis_tready_df), .o_tdata ({m_axis_payload_tkeep, m_axis_payload_tlast, m_axis_payload_tdata}), .o_tvalid (m_axis_payload_tvalid), .o_tready (m_axis_payload_tready), .space (), .occupied () ); // To prevent the packet info FIFO from overflowing, we block the input of // new packets to the data FIFO whenever the packet info FIFO fills up. assign s_axis_tready = s_axis_tready_df & ~packet_info_fifo_full; assign s_axis_tvalid_df = s_axis_tvalid & ~packet_info_fifo_full; //--------------------------------------------------------------------------- // Timestamp and Flags Capture //--------------------------------------------------------------------------- // // The timestamp and flags that we use for each packet is that of the last // data word. This maintains compatibility with how tuser was used on old // RFnoC. Here, we capture this information at the start of the packet. At // the end of the packet, when the length is known, this value will be // inserted into the packet info FIFO. // //--------------------------------------------------------------------------- reg [63:0] packet_timestamp; reg packet_has_time; reg packet_eov; reg packet_eob; always @(posedge axis_data_clk) begin if (s_axis_tvalid & s_axis_tready & s_axis_tlast) begin packet_timestamp <= s_axis_ttimestamp; packet_has_time <= s_axis_thas_time; packet_eov <= s_axis_teov; packet_eob <= s_axis_teob; end end //--------------------------------------------------------------------------- // Length Counter //--------------------------------------------------------------------------- // // Here We track the state of the incoming packet to determine its length. // //--------------------------------------------------------------------------- reg [15:0] packet_length, length_count; reg packet_length_valid; always @(posedge axis_data_clk) begin : length_counter if (axis_data_rst) begin length_count <= 0; packet_length <= 0; packet_length_valid <= 1'b0; end else begin : length_counter_main // Calculate the length of this word in bytes, taking tkeep into account integer i; integer num_bytes; num_bytes = 0; for (i = 0; i < NIPC; i = i + 1) begin num_bytes = num_bytes + (s_axis_tkeep[i]*(ITEM_W/8)); end // Update the packet length if the word is accepted packet_length_valid <= 1'b0; if (s_axis_tvalid & s_axis_tready) begin length_count <= length_count + num_bytes; if (s_axis_tlast) begin length_count <= 0; packet_length <= length_count + num_bytes; packet_length_valid <= 1'b1; end end end end //--------------------------------------------------------------------------- // Packet Info FIFO //--------------------------------------------------------------------------- // // This FIFO stores the packet info (length, timestamp, flags) for each fully // received packet. Due to AXI-Stream flow control, we may end up with // multiple packets being buffered in the data_fifo. The packet_info_fifo // here stores each packet's info until the packet is ready to go out. // //--------------------------------------------------------------------------- wire [63:0] next_packet_timestamp; wire next_packet_has_time; wire next_packet_eob; wire next_packet_eov; wire [15:0] next_packet_length; wire [15:0] packet_info_space; wire packet_info_valid; reg packet_info_ready = 1'b0; axi_fifo #( .WIDTH (3 + 64 + 16), .SIZE (INFO_FIFO_SIZE) ) packet_info_fifo ( .clk (axis_data_clk), .reset (axis_data_rst), .clear (1'b0), .i_tdata ({packet_eov, packet_eob, packet_has_time, packet_timestamp, packet_length}), .i_tvalid (packet_length_valid), .i_tready (), .o_tdata ({next_packet_eov, next_packet_eob, next_packet_has_time, next_packet_timestamp, next_packet_length}), .o_tvalid (packet_info_valid), .o_tready (packet_info_ready), .space (packet_info_space), .occupied () ); // Create a register to indicate when the FIFO is (almost) full. We leave // some space so that we can accept a new packet during the delay before data // transfer gets blocked. always @(posedge axis_data_clk) begin if (axis_data_rst) begin packet_info_fifo_full <= 1'b0; end else begin if (packet_info_space < 4) begin packet_info_fifo_full <= 1'b1; end else begin packet_info_fifo_full <= 1'b0; end end end //--------------------------------------------------------------------------- // Context State Machine //--------------------------------------------------------------------------- // // This state machine controls generation of the context packets (containing // the header and timestamp) that are output on m_axis_context, which will be // needed to create the CHDR packet. // //--------------------------------------------------------------------------- localparam ST_IDLE = 0; localparam ST_HEADER = 1; localparam ST_TIMESTAMP = 2; reg [ 1:0] state = ST_IDLE; // Current context FSM state reg [15:0] seq_num = 0; // CHDR sequence number reg [15:0] chdr_length; reg [ 2:0] chdr_pkt_type; reg [63:0] chdr_header; always @(*) begin : calc_chdr_header // Calculate byte length of the CHDR packet by adding the header and // timestamp length to the length of the payload. if (CHDR_W == 64) begin // If CHDR_W is 64-bit, timestamp is in a separate word if (next_packet_has_time) begin chdr_length = next_packet_length + 16; // Add two 64-bit CHDR words end else begin chdr_length = next_packet_length + 8; // Add one 64-bit CHDR word end end else begin // If CHDR_W is 128-bit or larger, timestamp is in the same word as the header chdr_length = next_packet_length + CHDR_W/8; // Add one CHDR word end // Determine the packet type if (next_packet_has_time) begin chdr_pkt_type = CHDR_PKT_TYPE_DATA_TS; end else begin chdr_pkt_type = CHDR_PKT_TYPE_DATA; end // Build up header chdr_header = chdr_build_header( 6'b0, // vc next_packet_eob, // eob next_packet_eov, // eov chdr_pkt_type, // pkt_type 0, // num_mdata seq_num, // seq_num chdr_length, // length of CHDR packet in bytes 0 // dst_epid ); end always @(posedge axis_data_clk) begin if (axis_data_rst) begin state <= ST_IDLE; seq_num <= 'd0; packet_info_ready <= 1'b0; m_axis_context_tvalid <= 1'b0; end else begin packet_info_ready <= 1'b0; if (CHDR_W == 64) begin : gen_ctx_fsm_64 // For 64-bit CHDR_W, we require two words, one for the header and one // for the timestamp. case (state) ST_IDLE: begin m_axis_context_tdata <= chdr_header; m_axis_context_tuser <= CONTEXT_FIELD_HDR; m_axis_context_tlast <= !next_packet_has_time; if (packet_info_valid && !packet_info_ready) begin m_axis_context_tvalid <= 1'b1; seq_num <= seq_num + 1; state <= ST_HEADER; end end ST_HEADER : begin // Wait for header to be accepted if (m_axis_context_tready) begin packet_info_ready <= 1'b1; m_axis_context_tdata <= next_packet_timestamp; if (next_packet_has_time) begin m_axis_context_tlast <= 1'b1; m_axis_context_tuser <= CONTEXT_FIELD_TS; state <= ST_TIMESTAMP; end else begin m_axis_context_tlast <= 1'b0; m_axis_context_tvalid <= 1'b0; state <= ST_IDLE; end end end ST_TIMESTAMP : begin // Wait for timestamp to be accepted if (m_axis_context_tready) begin m_axis_context_tvalid <= 1'b0; state <= ST_IDLE; end end default: state <= ST_IDLE; endcase end else begin : gen_ctx_fsm_128 // For 128-bit and larger CHDR_W, we need the header and timestamp in // the same word. case (state) ST_IDLE: begin m_axis_context_tdata <= { next_packet_timestamp, chdr_header }; m_axis_context_tuser <= next_packet_has_time ? CONTEXT_FIELD_HDR_TS : CONTEXT_FIELD_HDR; m_axis_context_tlast <= 1'b1; if (packet_info_valid) begin m_axis_context_tvalid <= 1'b1; seq_num <= seq_num + 1; packet_info_ready <= 1'b1; state <= ST_HEADER; end end ST_HEADER : begin // Wait for header to be accepted if (m_axis_context_tready) begin m_axis_context_tvalid <= 1'b0; state <= ST_IDLE; end end default : state <= ST_IDLE; endcase end end end endmodule