diff options
Diffstat (limited to 'fpga/usrp3/lib/fifo/axi_packet_gate.v')
-rw-r--r-- | fpga/usrp3/lib/fifo/axi_packet_gate.v | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/fifo/axi_packet_gate.v b/fpga/usrp3/lib/fifo/axi_packet_gate.v new file mode 100644 index 000000000..771fb2200 --- /dev/null +++ b/fpga/usrp3/lib/fifo/axi_packet_gate.v @@ -0,0 +1,229 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description: +// Holds packets in a FIFO until they are complete. This allows buffering +// slowly-built packets so they don't clog up downstream logic. If o_tready +// is held high, this module guarantees that o_tvalid will not be deasserted +// until a full packet is transferred. This module can also optionally drop +// a packet if the i_terror bit is asserted along with i_tlast. This allows +// discarding packet, say, if a CRC check fails. +// NOTE: +// - The maximum size of a packet that can pass through this module is +// 2^SIZE lines. If a larger packet is sent, this module will lock up. +// - Assuming that upstream is valid and downstream is ready, the maximum +// in to out latency per packet is (2^SIZE + 2) clock cycles. +// 2^SIZE because this module gates a packet, 1 cycle for the RAM read and +// 1 more cycle for the output register. This is not guaranteed behavior though. +// - The USE_AS_BUFF parameter can be used to treat this packet gate as +// a multi-packet buffer. When USE_AS_BUFF=0, the max number of packets +// (regardless of size) that the module can store is 2. When USE_AS_BUFF=1, +// the entire storage of this module can be used to buffer packets but at +// the cost of some additional RAM. Beware the sequence of (big packet, +// small packet, small packet), as some outside buffering may be needed +// to handle this case if USE_AS_BUFF=0. + +module axi_packet_gate #( + parameter WIDTH = 64, // Width of datapath + parameter SIZE = 10, // log2 of the buffer size (must be >= MTU of packet) + parameter USE_AS_BUFF = 0, // Allow the packet gate to be used as a buffer (uses more RAM) + parameter MIN_PKT_SIZE= 0 // log2 of minimum valid packet size (rounded down, used to reduce addr fifo size) +) ( + input wire clk, + input wire reset, + input wire clear, + input wire [WIDTH-1:0] i_tdata, + input wire i_tlast, + input wire i_terror, + input wire i_tvalid, + output wire i_tready, + output reg [WIDTH-1:0] o_tdata = {WIDTH{1'b0}}, + output reg o_tlast = 1'b0, + output reg o_tvalid = 1'b0, + input wire o_tready +); + + localparam [SIZE-1:0] ADDR_ZERO = {SIZE{1'b0}}; + localparam [SIZE-1:0] ADDR_ONE = {{(SIZE-1){1'b0}}, 1'b1}; + + // ------------------------------------------- + // RAM block that will hold pkts + // ------------------------------------------- + wire wr_en, rd_en; + wire [WIDTH:0] wr_data, rd_data; + reg [SIZE-1:0] wr_addr = ADDR_ZERO, rd_addr = ADDR_ZERO; + + // Threshold to explicitly instantiate LUTRAM + localparam LUTRAM_THRESH = 5; + + // We need to instantiate a simple dual-port RAM here so + // we use the ram_2port module with one read port and one + // write port and "NO-CHANGE" mode. + ram_2port #( + .DWIDTH (WIDTH+1), .AWIDTH(SIZE), + .RW_MODE("NO-CHANGE"), .OUT_REG(0), + .RAM_TYPE(SIZE <= LUTRAM_THRESH ? "LUTRAM" : "AUTOMATIC") + ) ram_i ( + .clka (clk), .ena(1'b1), .wea(wr_en), + .addra(wr_addr), .dia(wr_data), .doa(), + .clkb (clk), .enb(rd_en), .web(1'b0), + .addrb(rd_addr), .dib({WIDTH{1'b0}}), .dob(rd_data) + ); + + // FIFO empty/full logic. The condition for both + // empty and full is when rd_addr == wr_addr. However, + // it matters if we approach that case from the low side + // or the high side. So keep track of the almost empty/full + // state for determine if the next transaction will cause + // the FIFO to be truly empty or full. + reg ram_full = 1'b0, ram_empty = 1'b1; + wire almost_full = (wr_addr == rd_addr - ADDR_ONE); + wire almost_empty = (wr_addr == rd_addr + ADDR_ONE); + + always @(posedge clk) begin + if (reset | clear) begin + ram_full <= 1'b0; + end else begin + if (almost_full) begin + if (wr_en & ~rd_en) + ram_full <= 1'b1; + end else begin + if (~wr_en & rd_en) + ram_full <= 1'b0; + end + end + end + + always @(posedge clk) begin + if (reset | clear) begin + ram_empty <= 1'b1; + end else begin + if (almost_empty) begin + if (rd_en & ~wr_en) + ram_empty <= 1'b1; + end else begin + if (~rd_en & wr_en) + ram_empty <= 1'b0; + end + end + end + + // ------------------------------------------- + // Address FIFO + // ------------------------------------------- + // The address FIFO will hold the write address + // for the last line in a non-errant packet + + wire [SIZE-1:0] afifo_i_tdata, afifo_o_tdata, afifo_p_tdata; + wire afifo_i_tvalid, afifo_i_tready; + wire afifo_o_tvalid, afifo_o_tready; + wire afifo_p_tvalid, afifo_p_tready; + + axi_fifo #(.WIDTH(SIZE), .SIZE(USE_AS_BUFF==1 ? SIZE-MIN_PKT_SIZE : 1)) addr_fifo_i ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata(afifo_i_tdata), .i_tvalid(afifo_i_tvalid), .i_tready(afifo_i_tready), + .o_tdata(afifo_p_tdata), .o_tvalid(afifo_p_tvalid), .o_tready(afifo_p_tready), + .space(), .occupied() + ); + + axi_fifo #(.WIDTH(SIZE), .SIZE(1)) addr_fifo_pipe_i ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata(afifo_p_tdata), .i_tvalid(afifo_p_tvalid), .i_tready(afifo_p_tready), + .o_tdata(afifo_o_tdata), .o_tvalid(afifo_o_tvalid), .o_tready(afifo_o_tready), + .space(), .occupied() + ); + + // ------------------------------------------- + // Write state machine + // ------------------------------------------- + reg [SIZE-1:0] wr_head_addr = ADDR_ZERO; + + assign i_tready = ~ram_full & afifo_i_tready; + assign wr_en = i_tvalid & i_tready; + assign wr_data = {i_tlast, i_tdata}; + + always @(posedge clk) begin + if (reset | clear) begin + wr_addr <= ADDR_ZERO; + wr_head_addr <= ADDR_ZERO; + end else begin + if (wr_en) begin + if (i_tlast) begin + if (i_terror) begin + // Incoming packet had an error. Rewind the write + // pointer and pretend that a packet never came in. + wr_addr <= wr_head_addr; + end else begin + // Incoming packet had no error, advance wr_addr and + // wr_head_addr for the next packet. + wr_addr <= wr_addr + ADDR_ONE; + wr_head_addr <= wr_addr + ADDR_ONE; + end + end else begin + // Packet is still in progress, only update wr_addr + wr_addr <= wr_addr + ADDR_ONE; + end + end + end + end + + // Push the write address to the address FIFO if + // - It is the last one in the packet + // - The packet has no errors + assign afifo_i_tdata = wr_addr; + assign afifo_i_tvalid = ~ram_full & i_tvalid & i_tlast & ~i_terror; + + // ------------------------------------------- + // Read state machine + // ------------------------------------------- + reg rd_data_valid = 1'b0; + wire update_out_reg; + // Data can be read if there is a valid last address in the + // address FIFO (signifying the end of an input packet) and + // if there is data available in RAM + wire ready_to_read = (~ram_empty) & afifo_o_tvalid; + // Pop from address FIFO once we have see the end of the pkt + assign afifo_o_tready = rd_en & (afifo_o_tdata == rd_addr); + + // Read from RAM if + // - A full packet has been written AND + // - Output data is not valid OR is currently being transferred + assign rd_en = ready_to_read & (update_out_reg | ~rd_data_valid); + + always @(posedge clk) begin + if (reset | clear) begin + rd_data_valid <= 1'b0; + rd_addr <= ADDR_ZERO; + end else begin + if (update_out_reg | ~rd_data_valid) begin + // Output data is not valid OR is currently being transferred + if (ready_to_read) begin + rd_data_valid <= 1'b1; + rd_addr <= rd_addr + ADDR_ONE; + end else begin + rd_data_valid <= 1'b0; // Don't read + end + end + end + end + + // Instantiate an output register to break critical paths starting + // at the RAM module. When ram_2port is inferred as BRAM, the tools + // should absorb this register into the BRAM block without using + // SLICE resources. + always @(posedge clk) begin + if (reset | clear) begin + o_tvalid <= 1'b0; + end else if (update_out_reg) begin + o_tvalid <= rd_data_valid; + {o_tlast, o_tdata} <= rd_data; + end + end + // Update the output reg only *after* the downstream + // block has consumed the current value + assign update_out_reg = o_tready | ~o_tvalid; + +endmodule |