diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/utils/context_builder.v')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/utils/context_builder.v | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/utils/context_builder.v b/fpga/usrp3/lib/rfnoc/utils/context_builder.v new file mode 100644 index 000000000..83171e831 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/utils/context_builder.v @@ -0,0 +1,392 @@ +// +// 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 |