Files
b210-k7-fpga/lib/fifo/axi_packet_gate.v
T
Wade Fife 9e0f859bb6 fpga: lib: Fix axi_packet_gate RAM dib width
Original-commit: ed1b58053c53bfbd3f3b667a0ebe0aa8066e2b0e
2020-06-29 13:41:15 -05:00

230 lines
8.4 KiB
Verilog

//
// 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{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