aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/axi_rate_change.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/axi_rate_change.v')
-rw-r--r--fpga/usrp3/lib/rfnoc/axi_rate_change.v491
1 files changed, 491 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/axi_rate_change.v b/fpga/usrp3/lib/rfnoc/axi_rate_change.v
new file mode 100644
index 000000000..166e03c46
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/axi_rate_change.v
@@ -0,0 +1,491 @@
+//
+// Copyright 2016 Ettus Research
+// Copyright 2018 Ettus Research, a National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// - Implements rate change of N:M (a.k.a. M/N), handles headers automatically
+// - Note: N should always be written before M in software to prevent false rate changes
+// while the block is active
+// - User code is responsible for generating correct number of outputs per input
+// > Example: When set 1/N, after N input samples block should output 1 sample. If
+// user code's pipelining requires additional samples to "push" the 1
+// sample out, it is the user's responsibility to make the mechanism
+// (such as injecting extra samples) to do so.
+// - Will always send an integer multiple of N samples to user logic. This ensures
+// the user will not need to manually clear a "partial output sample" stuck in their
+// pipeline due to an uneven (in respect to decimation rate) number of input samples.
+// - Can optionally strobe clear_user after receiving packet with EOB
+// > enable_clear_user must be enabled via CONFIG settings register
+// > Warning: Input will be throttled until last packet has completely passed through
+// user code to prevent clearing valid data. In certain conditions, this throttling
+// can have a significant impact on throughput.
+// - Output packet size will be identical to input packet size. The only exception is
+// the final output packet, which may be shorter due to a partial input packet.
+// Limitations:
+// - Rate changes are ignored while active. Block must be cleared or packet with EOB
+// (and enable_clear_user is set) will cause new rates to be loaded.
+// - Can potentially use large amounts of block RAM when using large decimation rates
+// (greater than 2K). This occurs due to the feature that the block always sends a multiple
+// of N samples to the user. Implementing this feature requires N samples to be buffered.
+// - User code with long pipelines may need to increase HEADER_FIFOSIZE. The debug signal
+// warning_header_fifo_full is useful in determining this case.
+//
+// Settings Registers:
+// sr_n: Number of input samples per M output samples (Always write N before M)
+// sr_m: Number of output samples per N input samples
+// sr_config: 0: Enable clear_user signal.
+
+module axi_rate_change #(
+ parameter WIDTH = 32, // Input bit width, must be a power of 2 and greater than or equal to 8.
+ parameter MAX_N = 2**16,
+ parameter MAX_M = 2**16,
+ parameter MAXIMIZE_OUTPUT_PKT_LEN = 1,
+ // Settings registers
+ parameter SR_N_ADDR = 0,
+ parameter SR_M_ADDR = 1,
+ parameter SR_CONFIG_ADDR = 2
+)(
+ input clk, input reset, input clear,
+ output clear_user, // Strobed after end of burst. Throttles input. Useful for resetting user code between bursts.
+ input [15:0] src_sid, input [15:0] dst_sid,
+ input set_stb, input [7:0] set_addr, input [31:0] set_data,
+ input [WIDTH-1:0] i_tdata, input i_tlast, input i_tvalid, output i_tready, input [127:0] i_tuser,
+ output [WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready, output [127:0] o_tuser,
+ output [WIDTH-1:0] m_axis_data_tdata, output m_axis_data_tlast, output m_axis_data_tvalid, input m_axis_data_tready,
+ input [WIDTH-1:0] s_axis_data_tdata, input s_axis_data_tlast, input s_axis_data_tvalid, output s_axis_data_tready,
+ // Debugging signals:
+ // - Warnings indicate there may be an issue with user code.
+ // - Errors mean the user code has violated a rule.
+ // - Signals latch once set and block must be reset to clear.
+ output reg warning_long_throttle, // In the throttle state for a "long" time.
+ output reg error_extra_outputs, // User code generated extra outputs, i.e. received more than the expected M outputs.
+ output reg error_drop_pkt_lockup // Drop partial packet module is not accepting data even though user code is ready.
+);
+
+ reg [$clog2(MAX_N+1)-1:0] n = 1;
+ reg [$clog2(MAX_M+1)-1:0] m = 1;
+
+ wire [WIDTH-1:0] i_reg_tdata;
+ wire i_reg_tvalid, i_reg_tready, i_reg_tlast;
+ wire i_reg_tvalid_int, i_reg_tready_int, i_reg_tlast_int;
+
+ reg throttle = 1'b1, first_header, partial_first_word;
+ reg [15:0] word_cnt_div_n;
+ reg [$clog2(MAX_N+1)-1:0] word_cnt_div_n_frac = 1;
+ reg [$clog2(MAX_N+1)-1:0] in_pkt_cnt = 1;
+
+ reg send_done;
+ reg rate_changed;
+
+ /********************************************************
+ ** Settings Registers
+ ********************************************************/
+ wire [$clog2(MAX_N+1)-1:0] sr_n;
+ wire n_changed;
+ setting_reg #(.my_addr(SR_N_ADDR), .width($clog2(MAX_N+1)), .at_reset(1)) set_n (
+ .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
+ .out(sr_n), .changed(n_changed));
+
+ wire [$clog2(MAX_M+1)-1:0] sr_m;
+ wire m_changed;
+ setting_reg #(.my_addr(SR_M_ADDR), .width($clog2(MAX_M+1)), .at_reset(1)) set_m (
+ .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
+ .out(sr_m), .changed(m_changed));
+
+ wire sr_config;
+ wire enable_clear_user; // Enable strobing clear_user between bursts.
+ setting_reg #(.my_addr(SR_CONFIG_ADDR), .width(1), .at_reset(1'b1)) set_config (
+ .clk(clk), .rst(reset), .strobe(set_stb), .addr(set_addr), .in(set_data),
+ .out(sr_config), .changed());
+ assign enable_clear_user = sr_config;
+
+ /********************************************************
+ ** Header, word count FIFOs
+ ** - Header provides VITA Time and payload length for
+ ** output packets
+ ** - Word count provides a normalized count for the
+ ** output state machine to know when it has consumed
+ ** the final input sample in a burst.
+ ********************************************************/
+ // Decode input header
+ wire [127:0] i_reg_tuser;
+ wire has_time_in, eob_in, eob_in_header;
+ wire [15:0] payload_length_in;
+ reg [15:0] payload_length_out;
+ wire [63:0] vita_time_in;
+ cvita_hdr_decoder cvita_hdr_decoder_in_header (
+ .header(i_reg_tuser), .pkt_type(), .eob(eob_in_header),
+ .has_time(has_time_in), .seqnum(), .length(), .payload_length(payload_length_in),
+ .src_sid(), .dst_sid(), .vita_time(vita_time_in));
+
+ assign eob_in = eob_in_header | rate_changed;
+
+ reg [15:0] word_cnt_div_n_tdata;
+ wire [15:0] word_cnt_div_n_fifo_tdata;
+ reg word_cnt_div_n_tvalid;
+ wire word_cnt_div_n_tready, word_cnt_div_n_fifo_tvalid, word_cnt_div_n_fifo_tready;
+ axi_fifo #(.WIDTH(16), .SIZE(0)) axi_fifo_word_cnt (
+ .clk(clk), .reset(reset), .clear(clear),
+ .i_tdata(word_cnt_div_n_tdata), .i_tvalid(word_cnt_div_n_tvalid), .i_tready(word_cnt_div_n_tready),
+ .o_tdata(word_cnt_div_n_fifo_tdata), .o_tvalid(word_cnt_div_n_fifo_tvalid), .o_tready(word_cnt_div_n_fifo_tready),
+ .space(), .occupied());
+
+ /********************************************************
+ ** Register input stream
+ ** - Upsteam will be throttled when clearing user logic
+ ********************************************************/
+ // Input register with header
+ axi_fifo_flop #(.WIDTH(WIDTH+1+128)) axi_fifo_flop_input (
+ .clk(clk), .reset(reset), .clear(clear),
+ .i_tdata({i_tlast,i_tdata,i_tuser}), .i_tvalid(i_tvalid), .i_tready(i_tready),
+ .o_tdata({i_reg_tlast,i_reg_tdata,i_reg_tuser}), .o_tvalid(i_reg_tvalid_int), .o_tready(i_reg_tready),
+ .space(), .occupied());
+
+ assign i_reg_tready = i_reg_tready_int & word_cnt_div_n_tready & ~throttle;
+ assign i_reg_tvalid = i_reg_tvalid_int & word_cnt_div_n_tready & ~throttle;
+ // Assert AXI Drop Partial Packet's i_tlast every N samples, which is used to detect and drop
+ // partial output samples.
+ assign i_reg_tlast_int = (word_cnt_div_n_frac == n) | (eob_in & i_reg_tlast);
+
+ /********************************************************
+ ** Input state machine
+ ********************************************************/
+ reg [1:0] input_state;
+ localparam RECV_INIT = 0;
+ localparam RECV = 1;
+ localparam RECV_WAIT_FOR_SEND_DONE = 2;
+
+ always @(posedge clk) begin
+ if (reset | clear) begin
+ n <= 1;
+ m <= 1;
+ rate_changed <= 1'b0;
+ first_header <= 1'b1;
+ partial_first_word <= 1'b1;
+ payload_length_out <= 'd0;
+ word_cnt_div_n <= 0;
+ word_cnt_div_n_frac <= 1;
+ throttle <= 1'b1;
+ word_cnt_div_n_tvalid <= 1'b0;
+ word_cnt_div_n_tdata <= 'd0;
+ input_state <= RECV_INIT;
+ end else begin
+ if (word_cnt_div_n_tvalid & word_cnt_div_n_tready) begin
+ word_cnt_div_n_tvalid <= 1'b0;
+ end
+ // Input state machine
+ case (input_state)
+ RECV_INIT : begin
+ n <= sr_n;
+ m <= sr_m;
+ rate_changed <= 1'b0;
+ first_header <= 1'b1;
+ partial_first_word <= 1'b1;
+ payload_length_out <= 'd0;
+ word_cnt_div_n <= 0;
+ word_cnt_div_n_frac <= 1;
+ if (i_reg_tvalid_int & word_cnt_div_n_tready) begin
+ throttle <= 1'b0;
+ input_state <= RECV;
+ end
+ end
+ // Logic used by the RECV state to track several variables:
+ // word_cnt_div_n: Number of words received divided by n.
+ // Needed for tracking final sample in a burst.
+ // word_cnt_div_n_frac: Used to increment word_cnt_div_n. Can be
+ // thought of as the fractional part of
+ // word_cnt_div_n.
+ // in_pkt_cnt: Similar to in_word_cnt, but for packets. Used
+ // to determine when a group of N packets has been
+ // received to store the next header.
+ // first_header: We only use the header from the first packet in
+ // a group of N packets (this greatly reduces
+ // the header FIFO size).
+ RECV : begin
+ // If rate changed, force a EOB.
+ if (m_changed) begin
+ rate_changed <= 1'b1;
+ end
+ if (i_reg_tvalid & i_reg_tready) begin
+ // Track the number of words sent to the user divided by N.
+ // At the end of a burst, this value is forwarded to the output
+ // state machine and used to determine when the final sample has
+ // arrived from the user code.
+ if (word_cnt_div_n_frac == n) begin
+ word_cnt_div_n <= word_cnt_div_n + 1;
+ word_cnt_div_n_frac <= 1;
+ end else begin
+ word_cnt_div_n_frac <= word_cnt_div_n_frac + 1;
+ end
+ // Use payload length from first packet
+ first_header <= 1'b0;
+ if (first_header) begin
+ payload_length_out <= payload_length_in;
+ end else if (MAXIMIZE_OUTPUT_PKT_LEN) begin
+ if (payload_length_out < payload_length_in) begin
+ payload_length_out <= payload_length_in;
+ end
+ end
+ // Track when at least N input samples have been received in this burst
+ if (partial_first_word & (word_cnt_div_n_frac == n)) begin
+ partial_first_word <= 1'b0;
+ end
+ // Burst ended before we received enough samples to form
+ // at least one full output sample.
+ // Note: axi_drop_partial_packet automatically handles
+ // dropping the partial sample.
+ if (i_reg_tlast & eob_in & partial_first_word) begin
+ input_state <= RECV_INIT;
+ end else begin
+ if (i_reg_tlast) begin
+ // At the end of a burst, forward the number of words divided by N to
+ // the output state machine via a FIFO. This allows the output state
+ // machine to know when it has received the final output word.
+ // We use a FIFO in case the bursts are very small and we
+ // need to store several of these values.
+ if (eob_in) begin
+ word_cnt_div_n_tdata <= word_cnt_div_n + (word_cnt_div_n_frac == n);
+ word_cnt_div_n_tvalid <= 1'b1;
+ throttle <= 1'b1;
+ if (enable_clear_user) begin
+ input_state <= RECV_WAIT_FOR_SEND_DONE;
+ end else begin
+ input_state <= RECV_INIT;
+ end
+ end
+ end
+ end
+ end
+ end
+ // Wait until last sample has been output and user logic is cleared
+ // WARNING: This can be a huge bubble state! However, since it only happens with
+ // EOBs, it should be infrequent.
+ RECV_WAIT_FOR_SEND_DONE : begin
+ if (send_done) begin
+ input_state <= RECV_INIT;
+ end
+ end
+ default : begin
+ input_state <= RECV_INIT;
+ end
+ endcase
+ end
+ end
+
+ assign clear_user = send_done & enable_clear_user;
+
+ /********************************************************
+ ** AXI Drop Partial Packet (to user)
+ ** - Enforces sending integer multiple of N samples
+ ** to user
+ ********************************************************/
+ axi_drop_partial_packet #(
+ .WIDTH(WIDTH+1),
+ .HOLD_LAST_WORD(1),
+ .MAX_PKT_SIZE(MAX_N),
+ .SR_PKT_SIZE_ADDR(SR_N_ADDR))
+ axi_drop_partial_packet (
+ .clk(clk), .reset(reset), .clear(clear | send_done),
+ .flush(word_cnt_div_n_tvalid & word_cnt_div_n_tready), // Flush on EOB
+ .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
+ .i_tdata({i_reg_tlast,i_reg_tdata}), .i_tvalid(i_reg_tvalid), .i_tlast(i_reg_tlast_int), .i_tready(i_reg_tready_int),
+ .o_tdata({m_axis_data_tlast,m_axis_data_tdata}), .o_tvalid(m_axis_data_tvalid), .o_tlast(/* Unused */), .o_tready(m_axis_data_tready));
+
+ /********************************************************
+ ** Output state machine
+ ********************************************************/
+ reg [1:0] output_state;
+ localparam SEND_INIT = 0;
+ localparam SEND = 1;
+
+ wire [WIDTH-1:0] o_reg_tdata;
+ wire [127:0] o_reg_tuser;
+ wire o_reg_tvalid, o_reg_tready, o_reg_tlast, o_reg_tlast_int;
+
+ reg [15:0] out_payload_cnt = (WIDTH/8);
+ reg [15:0] word_cnt_div_m;
+ reg [$clog2(MAX_M+1)-1:0] word_cnt_div_m_frac = 1;
+ reg [$clog2(MAX_M+1)-1:0] out_pkt_cnt = 1;
+
+ // End of burst tracking. Compare the number of words sent to the user divided by N
+ // to the number of words received from the user divided by M. When they equal each other
+ // then we have received the last word from the user in this burst.
+ // Note: Using word_cnt_div_n_fifo_tdata to make sure the last word is identified before
+ // it has been consumed.
+ wire last_word_in_burst = word_cnt_div_n_fifo_tvalid &
+ (word_cnt_div_m == word_cnt_div_n_fifo_tdata) &
+ (word_cnt_div_m_frac == m);
+
+ always @(posedge clk) begin
+ if (reset | clear) begin
+ word_cnt_div_m <= 1;
+ word_cnt_div_m_frac <= 1;
+ out_payload_cnt <= (WIDTH/8);
+ send_done <= 1'b0;
+ output_state <= SEND_INIT;
+ end else begin
+ // Track
+ case (output_state)
+ SEND_INIT : begin
+ word_cnt_div_m <= 1;
+ word_cnt_div_m_frac <= 1;
+ out_payload_cnt <= (WIDTH/8);
+ send_done <= 1'b0;
+ output_state <= SEND;
+ end
+ SEND : begin
+ if (o_reg_tvalid & o_reg_tready) begin
+ if (o_reg_tlast) begin
+ // Track number of samples from user to set tlast
+ out_payload_cnt <= (WIDTH/8);
+ end else begin
+ out_payload_cnt <= out_payload_cnt + (WIDTH/8);
+ end
+ // Track number of words consumed divided by M. This is used
+ // in conjunction with word_cnt_div_n to determine when we have received
+ // the last word in a burst from the user.
+ if (word_cnt_div_m_frac == m) begin
+ word_cnt_div_m <= word_cnt_div_m + 1;
+ word_cnt_div_m_frac <= 1;
+ end else begin
+ word_cnt_div_m_frac <= word_cnt_div_m_frac + 1;
+ end
+ if (last_word_in_burst) begin
+ send_done <= 1'b1;
+ output_state <= SEND_INIT;
+ end
+ end
+ end
+ default : begin
+ output_state <= SEND_INIT;
+ end
+ endcase
+ end
+ end
+
+ // Only pop this FIFO at EOB.
+ assign word_cnt_div_n_fifo_tready = o_reg_tvalid & o_reg_tready & last_word_in_burst;
+
+ /********************************************************
+ ** Adjust VITA time
+ ********************************************************/
+ localparam VT_INIT = 0;
+ localparam VT_INCREMENT = 1;
+ reg vt_state;
+
+ reg has_time_out, has_time_clear;
+ reg [63:0] vita_time_out, vita_time_accum;
+
+ always @(posedge clk) begin
+ if (reset | clear) begin
+ vt_state <= VT_INIT;
+ end else begin
+ case (vt_state)
+ VT_INIT : begin
+ has_time_clear <= 1'b0;
+ if (i_reg_tvalid & i_reg_tready & first_header) begin
+ vita_time_out <= vita_time_in;
+ vita_time_accum <= vita_time_in;
+ has_time_out <= has_time_in;
+ vt_state <= VT_INCREMENT;
+ end
+ end
+ VT_INCREMENT : begin
+ // Stop sending vita time if user does not send vita time
+ if (i_reg_tvalid & ~has_time_in) begin
+ has_time_clear <= 1'b1;
+ end
+ if (o_reg_tvalid & o_reg_tready) begin
+ if (o_reg_tlast) begin
+ if (has_time_clear) begin
+ has_time_out <= 1'b0;
+ end
+ vita_time_out <= vita_time_accum + n;
+ end
+ vita_time_accum <= vita_time_accum + n;
+ if (last_word_in_burst) begin
+ vt_state <= VT_INIT;
+ end
+ end
+ end
+ default : begin
+ vt_state <= VT_INIT;
+ end
+ endcase
+ end
+ end
+
+ // Create output header
+ cvita_hdr_encoder cvita_hdr_encoder (
+ .pkt_type(2'd0), .eob(last_word_in_burst), .has_time(has_time_out),
+ .seqnum(12'd0), .payload_length(16'd0), // Not needed, handled by AXI Wrapper
+ .src_sid(src_sid), .dst_sid(dst_sid),
+ .vita_time(vita_time_out),
+ .header(o_reg_tuser));
+
+ /********************************************************
+ ** Register input stream from user and output stream
+ ********************************************************/
+ assign o_reg_tlast = o_reg_tlast_int |
+ // End of packet
+ (out_payload_cnt == payload_length_out) |
+ // EOB, could be a partial packet
+ last_word_in_burst;
+
+ axi_fifo_flop #(.WIDTH(WIDTH+1)) axi_fifo_flop_from_user_0 (
+ .clk(clk), .reset(reset), .clear(clear),
+ // FIXME: If user asserts tlast at the wrong time, it likely causes a deadlock. For now ignore tlast.
+ //.i_tdata({s_axis_data_tlast,s_axis_data_tdata}), .i_tvalid(s_axis_data_tvalid), .i_tready(s_axis_data_tready),
+ .i_tdata({1'b0,s_axis_data_tdata}), .i_tvalid(s_axis_data_tvalid), .i_tready(s_axis_data_tready),
+ .o_tdata({o_reg_tlast_int,o_reg_tdata}), .o_tvalid(o_reg_tvalid), .o_tready(o_reg_tready),
+ .space(), .occupied());
+
+ // Output register with header
+ axi_fifo_flop #(.WIDTH(WIDTH+1+128)) axi_fifo_flop_output (
+ .clk(clk), .reset(reset), .clear(clear),
+ .i_tdata({o_reg_tlast,o_reg_tdata,o_reg_tuser}), .i_tvalid(o_reg_tvalid), .i_tready(o_reg_tready),
+ .o_tdata({o_tlast,o_tdata,o_tuser}), .o_tvalid(o_tvalid), .o_tready(o_tready),
+ .space(), .occupied());
+
+ /********************************************************
+ ** Error / warning signals
+ ********************************************************/
+ reg [23:0] counter_header_fifo_full, counter_throttle, counter_drop_pkt_lockup;
+ reg [2:0] counter_header_fifo_empty;
+ always @(posedge clk) begin
+ if (reset) begin
+ warning_long_throttle <= 1'b0;
+ error_extra_outputs <= 1'b0;
+ error_drop_pkt_lockup <= 1'b0;
+ counter_throttle <= 0;
+ counter_header_fifo_full <= 0;
+ counter_drop_pkt_lockup <= 0;
+ counter_header_fifo_empty <= 0;
+ end else begin
+ // In throttle state for a "long" time
+ if (throttle) begin
+ counter_throttle <= counter_throttle + 1;
+ if (counter_throttle == 2**24-1) begin
+ warning_long_throttle <= 1'b1;
+ end
+ end else begin
+ counter_throttle <= 0;
+ end
+ // More than M outputs per N inputs
+ if (word_cnt_div_n_fifo_tvalid & (word_cnt_div_m > word_cnt_div_n_fifo_tdata)) begin
+ error_extra_outputs <= 1'b1;
+ end
+ // Bad internal state. AXI drop partial packet is in a lockup condition.
+ if (~i_reg_tready_int & m_axis_data_tready) begin
+ counter_drop_pkt_lockup <= counter_drop_pkt_lockup + 1;
+ if (counter_drop_pkt_lockup == 2**24-1) begin
+ error_drop_pkt_lockup <= 1'b1;
+ end
+ end else begin
+ counter_drop_pkt_lockup <= 0;
+ end
+ end
+ end
+
+endmodule