aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/utils/context_builder.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/utils/context_builder.v')
-rw-r--r--fpga/usrp3/lib/rfnoc/utils/context_builder.v392
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