230 lines
8.4 KiB
Verilog
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
|