mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
fpga: lib: create new URAM and large AXI FIFO
This commit is contained in:
parent
3d93412dec
commit
2eb2ac20d3
@ -18,6 +18,8 @@ axi_fifo64_to_fifo32.v \
|
||||
axi_fifo32_to_fifo16.v \
|
||||
axi_fifo16_to_fifo32.v \
|
||||
axi_fifo_bram.v \
|
||||
axi_fifo_uram.sv \
|
||||
axi_fifo_large.sv \
|
||||
axi_fifo_cascade.v \
|
||||
axi_fifo_flop2.v \
|
||||
axi_fifo_flop.v \
|
||||
|
||||
242
fpga/usrp3/lib/fifo/axi_fifo_large.sv
Normal file
242
fpga/usrp3/lib/fifo/axi_fifo_large.sv
Normal file
@ -0,0 +1,242 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axi_fifo_large
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Buffers incoming AXI-Stream data using a cascade of FIFOs whose depth is
|
||||
// controlled by the DEPTH parameter (number of words). For small depths the
|
||||
// module instantiates a single axi_fifo with input and output flop2 stages.
|
||||
// On UltraScale devices, when the requested depth exceeds a threshold,
|
||||
// URAM-based storage is used for the bulk of the capacity with an BRAM
|
||||
// cascade for the remaining depth. On other devices a pure BRAM cascade is
|
||||
// generated. A register-based pipeline flop is placed at the input and output
|
||||
// to break timing paths.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// WIDTH : Data bus width in bits.
|
||||
// DEPTH : FIFO depth in number of words. Actual depth is rounded
|
||||
// up to the next multiple of 512 (min BRAM depth).
|
||||
// DEVICE : Target device string ("ULTRASCALE", "7SERIES",
|
||||
// "VIRTEX6", "SPARTAN6").
|
||||
// MAX_NUM_URAM_BLOCKS: Maximum number of URAM primitives to use.
|
||||
// Set to -1 (default) for no limit.
|
||||
//
|
||||
|
||||
module axi_fifo_large #(
|
||||
int WIDTH = 64,
|
||||
int DEPTH = 4096,
|
||||
string DEVICE = "7SERIES",
|
||||
int MAX_NUM_URAM_BLOCKS = -1
|
||||
) (
|
||||
input logic clk,
|
||||
input logic reset,
|
||||
input logic clear,
|
||||
|
||||
input logic [WIDTH-1:0] i_tdata,
|
||||
input logic i_tvalid,
|
||||
output logic i_tready,
|
||||
|
||||
output logic [WIDTH-1:0] o_tdata,
|
||||
output logic o_tvalid,
|
||||
input logic o_tready,
|
||||
|
||||
output logic [19:0] space,
|
||||
output logic [19:0] occupied
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Parameter checks
|
||||
// ---------------------------------------------------------------------------
|
||||
// DEPTH limited by space and occupied output width
|
||||
if (DEPTH >= 2**20) begin
|
||||
$error("axi_fifo_large: DEPTH (%0d) cannot exceed 20 bits of address space.", DEPTH);
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Size threshold for single FIFO vs cascade.
|
||||
// ---------------------------------------------------------------------------
|
||||
localparam int SIZE_THRESHOLD = (
|
||||
(DEVICE == "ULTRASCALE") ? 12 : // Set to 12 to allow a single URAM block
|
||||
(DEVICE == "7SERIES") ? 14 : (
|
||||
(DEVICE == "VIRTEX6") ? 14 : (
|
||||
(DEVICE == "SPARTAN6") ? 12 : (
|
||||
12
|
||||
))));
|
||||
|
||||
// create a single FIFO instance with input and output flop2 stage
|
||||
if (DEPTH <= 2**SIZE_THRESHOLD) begin
|
||||
axi_fifo_cascade #(.WIDTH(WIDTH), .SIZE($clog2(DEPTH))) single_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(i_tdata), .i_tvalid(i_tvalid), .i_tready(i_tready),
|
||||
.o_tdata(o_tdata), .o_tvalid(o_tvalid), .o_tready(o_tready),
|
||||
.space(space[15:0]), .occupied(occupied[15:0])
|
||||
);
|
||||
assign space[19:16] = '0;
|
||||
assign occupied[19:16] = '0;
|
||||
|
||||
// create a FIFO cascade
|
||||
end else begin
|
||||
// -------------------------------------------------------------------------
|
||||
// Register based 2 element FIFO to break timing paths.
|
||||
// -------------------------------------------------------------------------
|
||||
logic [WIDTH-1:0] i_tdata_pre;
|
||||
logic i_tvalid_pre, i_tready_pre;
|
||||
|
||||
axi_fifo_flop2 #(.WIDTH(WIDTH)) pre_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(i_tdata), .i_tvalid(i_tvalid), .i_tready(i_tready),
|
||||
.o_tdata(i_tdata_pre), .o_tvalid(i_tvalid_pre), .o_tready(i_tready_pre),
|
||||
.space(), .occupied()
|
||||
);
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// URAM stage
|
||||
// -------------------------------------------------------------------------
|
||||
// Static information about URAM primitives
|
||||
localparam int URAM_BLOCK_WIDTH = 72; // Number of bits storable in a single URAM block
|
||||
localparam int URAM_BLOCK_DEPTH = 4096; // Number of words storable in a single URAM block
|
||||
|
||||
// Number of URAM primitives needed per storage line (72 bits wide each).
|
||||
localparam int URAM_PER_LINE = (WIDTH + URAM_BLOCK_WIDTH - 1) / URAM_BLOCK_WIDTH;
|
||||
|
||||
// Determine whether to use URAMs at all. URAMs are only available on
|
||||
// ULTRASCALE, and we need at least URAM_PER_LINE blocks to store a
|
||||
// single line. When MAX_NUM_URAM_BLOCKS is positive but too small to
|
||||
// fit even one line, we skip the URAM path entirely and fall through
|
||||
// to the BRAM cascade.
|
||||
localparam bit USE_URAM = (DEVICE == "ULTRASCALE") &&
|
||||
(MAX_NUM_URAM_BLOCKS < 0 || MAX_NUM_URAM_BLOCKS >= URAM_PER_LINE);
|
||||
|
||||
// Compute how much of the FIFO depth can be covered by URAMs
|
||||
// This will always return at least 1 as smaller FIFOs are handled by the single FIFO case above.
|
||||
localparam int URAM_COUNT_VAL1 = DEPTH / URAM_BLOCK_DEPTH;
|
||||
// Cap the URAM depth by the maximum number of URAM blocks if specified.
|
||||
localparam int URAM_COUNT_VAL2 = (MAX_NUM_URAM_BLOCKS < 0) ? URAM_COUNT_VAL1 : (MAX_NUM_URAM_BLOCKS / URAM_PER_LINE);
|
||||
// return the minimum of the two URAM depth calculations to determine the actual URAM depth to use.
|
||||
localparam int URAM_COUNT = (URAM_COUNT_VAL1 < URAM_COUNT_VAL2) ? URAM_COUNT_VAL1 : URAM_COUNT_VAL2;
|
||||
localparam int URAM_DEPTH = URAM_COUNT * URAM_BLOCK_DEPTH;
|
||||
|
||||
// --- URAM stage ---
|
||||
logic [WIDTH-1:0] uram_out_tdata;
|
||||
logic uram_out_tvalid, uram_out_tready;
|
||||
|
||||
if (USE_URAM) begin : gen_uram_fifo
|
||||
axi_fifo_uram #(.WIDTH(WIDTH), .DEPTH(URAM_DEPTH)) uram_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(i_tdata_pre),.i_tvalid(i_tvalid_pre), .i_tready(i_tready_pre),
|
||||
.o_tdata(uram_out_tdata), .o_tvalid(uram_out_tvalid), .o_tready(uram_out_tready),
|
||||
.space(), .occupied()
|
||||
);
|
||||
end else begin
|
||||
// If not using URAM, connect the URAM interface signals to the pre_fifo output.
|
||||
assign uram_out_tdata = i_tdata_pre;
|
||||
assign uram_out_tvalid = i_tvalid_pre;
|
||||
assign i_tready_pre = uram_out_tready;
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// BRAM stage
|
||||
// -------------------------------------------------------------------------
|
||||
// BRAM stages in the URAM path reuse the DEPTH of 13 as with a maximum BRAM
|
||||
// size of 1024 words the default max cascade height of 8 is reached at a
|
||||
// size of 2**13.
|
||||
localparam int BRAM_MAX_SIZE = (DEVICE == "ULTRASCALE") ? 13 : SIZE_THRESHOLD;
|
||||
localparam int BRAM_MAX_DEPTH = 2**BRAM_MAX_SIZE;
|
||||
// Creating a BRAM instance will at least use 512 words for the supported architectures
|
||||
localparam int BRAM_MIN_SIZE = 9;
|
||||
localparam int BRAM_MIN_DEPTH = 2**BRAM_MIN_SIZE;
|
||||
|
||||
// calculate remaining depth after URAM stage
|
||||
localparam int REMAINING_DEPTH = (USE_URAM) ? (DEPTH - URAM_DEPTH) : DEPTH;
|
||||
|
||||
// calculate the number of BRAM stages needed to cover the remaining depth
|
||||
// determine depth rounded up to the next multiple of the minimum BRAM depth
|
||||
localparam int REMAINING_DEPTH_CEIL = REMAINING_DEPTH + BRAM_MIN_DEPTH - 1;
|
||||
localparam int BRAM_DEPTH = (REMAINING_DEPTH_CEIL / BRAM_MIN_DEPTH) * BRAM_MIN_DEPTH;
|
||||
// determine the number of max size BRAM stages
|
||||
localparam int NUM_MAX_BRAM = REMAINING_DEPTH_CEIL / BRAM_MAX_DEPTH;
|
||||
|
||||
// generate the BRAM stages for the remaining depth
|
||||
localparam int BRAM_COUNT = NUM_MAX_BRAM + BRAM_MAX_SIZE - BRAM_MIN_SIZE;
|
||||
logic [WIDTH-1:0] c_tdata [BRAM_COUNT:0];
|
||||
logic c_tvalid [BRAM_COUNT:0], c_tready [BRAM_COUNT:0];
|
||||
|
||||
// connect the URAM output to the first BRAM stage (or to the output if no BRAM stages)
|
||||
assign c_tdata[0] = uram_out_tdata;
|
||||
assign c_tvalid[0] = uram_out_tvalid;
|
||||
assign uram_out_tready = c_tready[0];
|
||||
|
||||
// generate the max sized BRAM stages
|
||||
for (genvar j = 0; j < NUM_MAX_BRAM; j++) begin
|
||||
axi_fifo #(.WIDTH(WIDTH), .SIZE(BRAM_MAX_SIZE)) max_size_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(c_tdata[j]), .i_tvalid(c_tvalid[j]), .i_tready(c_tready[j]),
|
||||
.o_tdata(c_tdata[j+1]), .o_tvalid(c_tvalid[j+1]), .o_tready(c_tready[j+1]),
|
||||
.space(), .occupied()
|
||||
);
|
||||
end
|
||||
|
||||
// generate the smaller BRAM stages if needed (use SIZE of -1 to generate a bypass)
|
||||
localparam int NUM_SMALL_BRAM = BRAM_MAX_SIZE - BRAM_MIN_SIZE;
|
||||
// use bits in REMAINING_DEPTH_CEIL to determine which FIFOs to use and
|
||||
// which to bypass
|
||||
localparam logic [NUM_SMALL_BRAM-1:0] BRAM_STAGE_SELECT = REMAINING_DEPTH_CEIL[BRAM_MAX_SIZE-1:BRAM_MIN_SIZE];
|
||||
for (genvar k = 0; k < NUM_SMALL_BRAM; k++) begin
|
||||
localparam int CURRENT_SIZE = BRAM_MAX_SIZE - k - 1;
|
||||
localparam int BRAM_SIZE = (BRAM_STAGE_SELECT[NUM_SMALL_BRAM - k - 1]) ? CURRENT_SIZE : -1;
|
||||
localparam int IDX = NUM_MAX_BRAM + k;
|
||||
axi_fifo #(.WIDTH(WIDTH), .SIZE(BRAM_SIZE)) smaller_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(c_tdata[IDX]), .i_tvalid(c_tvalid[IDX]), .i_tready(c_tready[IDX]),
|
||||
.o_tdata(c_tdata[IDX + 1]), .o_tvalid(c_tvalid[IDX + 1]), .o_tready(c_tready[IDX + 1]),
|
||||
.space(), .occupied()
|
||||
);
|
||||
end
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Flop 2 output stage
|
||||
// -------------------------------------------------------------------------
|
||||
axi_fifo_flop2 #(.WIDTH(WIDTH)) post_fifo (
|
||||
.clk(clk), .reset(reset), .clear(clear),
|
||||
.i_tdata(c_tdata[BRAM_COUNT]), .i_tvalid(c_tvalid[BRAM_COUNT]), .i_tready(c_tready[BRAM_COUNT]),
|
||||
.o_tdata(o_tdata), .o_tvalid(o_tvalid), .o_tready(o_tready),
|
||||
.space(), .occupied()
|
||||
);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Space and occupied (diagnostics, not exact)
|
||||
// -------------------------------------------------------------------------
|
||||
localparam int TOTAL_DEPTH = (USE_URAM ? URAM_DEPTH : 0) + BRAM_DEPTH;
|
||||
logic read, write;
|
||||
|
||||
always_comb begin
|
||||
write = i_tvalid & i_tready;
|
||||
read = o_tvalid & o_tready;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear)
|
||||
space <= TOTAL_DEPTH;
|
||||
else if (read & ~write)
|
||||
space <= space + 1'd1;
|
||||
else if (write & ~read)
|
||||
space <= space - 1'd1;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear)
|
||||
occupied <= '0;
|
||||
else if (read & ~write)
|
||||
occupied <= occupied - 1'd1;
|
||||
else if (write & ~read)
|
||||
occupied <= occupied + 1'd1;
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
253
fpga/usrp3/lib/fifo/axi_fifo_uram.sv
Normal file
253
fpga/usrp3/lib/fifo/axi_fifo_uram.sv
Normal file
@ -0,0 +1,253 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axi_fifo_uram
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// UltraRAM-based AXI-Stream FIFO for Xilinx UltraScale+ devices.
|
||||
// Since UltraRAM requires multiple pipeline stages on the read path,
|
||||
// an axi_fifo_short is placed on the output to buffer data and
|
||||
// provide standard AXI-Stream backpressure. Reads from the UltraRAM
|
||||
// are only issued when the output FIFO has enough space to absorb all
|
||||
// in-flight data through the pipeline.
|
||||
//
|
||||
// Simple Dual Port: Port A writes, Port B reads. Follows Xilinx UG901
|
||||
// coding style for UltraRAM pipeline inference.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// WIDTH : Data width in bits. UltraRAM native width is 72 bits; Vivado
|
||||
// handles wider or narrower widths automatically.
|
||||
// DEPTH : FIFO depth (number of entries). Native UltraRAM depth is
|
||||
// 4096 entries. The actual depth is rounded up to next multiple of
|
||||
// 4096.
|
||||
//
|
||||
|
||||
module axi_fifo_uram #(
|
||||
int WIDTH = 72,
|
||||
int DEPTH = 4096
|
||||
) (
|
||||
input logic clk,
|
||||
input logic reset,
|
||||
input logic clear,
|
||||
input logic [WIDTH-1:0] i_tdata,
|
||||
input logic i_tvalid,
|
||||
output logic i_tready,
|
||||
output logic [WIDTH-1:0] o_tdata,
|
||||
output logic o_tvalid,
|
||||
input logic o_tready,
|
||||
output logic [15:0] space,
|
||||
output logic [15:0] occupied
|
||||
);
|
||||
|
||||
// Number of pipeline registers absorbable into the URAM cascade.
|
||||
// Each URAM block in the cascade contributes one pipeline stage.
|
||||
// cascade_depth = 2^max(0, SIZE-12), with a minimum of 1.
|
||||
localparam int URAM_BLOCK_DEPTH = 4096;
|
||||
localparam int CASCADE_DEPTH = (DEPTH + URAM_BLOCK_DEPTH - 1) / URAM_BLOCK_DEPTH;
|
||||
localparam int ACTUAL_DEPTH = CASCADE_DEPTH * URAM_BLOCK_DEPTH;
|
||||
localparam int SIZE = $clog2(ACTUAL_DEPTH);
|
||||
|
||||
// Total read latency through UltraRAM pipeline:
|
||||
// 1 (memreg) + NBPIPE (mem_pipe_reg stages) + 1 (dout)
|
||||
// = NBPIPE + 2 clock cycles
|
||||
localparam int NBPIPE = CASCADE_DEPTH;
|
||||
localparam int READ_LATENCY = NBPIPE + 2;
|
||||
|
||||
if (READ_LATENCY >= 32) begin
|
||||
$error("axi_fifo_uram: READ_LATENCY (%0d) must be less than 32 to fit in space check.", READ_LATENCY);
|
||||
end
|
||||
|
||||
//-------------------------------------------------
|
||||
// Write / Read address pointers
|
||||
//-------------------------------------------------
|
||||
// Extra MSB for full/empty disambiguation
|
||||
logic unsigned [SIZE:0] wr_addr, rd_addr;
|
||||
logic unsigned [SIZE-1:0] wr_ptr, rd_ptr;
|
||||
|
||||
always_comb begin
|
||||
wr_ptr = wr_addr[SIZE-1:0];
|
||||
rd_ptr = rd_addr[SIZE-1:0];
|
||||
end
|
||||
|
||||
logic empty, full, write, read;
|
||||
|
||||
always_comb begin
|
||||
empty = (wr_addr == rd_addr);
|
||||
full = (wr_addr[SIZE] != rd_addr[SIZE]) && (wr_ptr == rd_ptr);
|
||||
write = i_tvalid & i_tready;
|
||||
read = o_tready & o_tvalid;
|
||||
end
|
||||
|
||||
assign i_tready = ~full;
|
||||
|
||||
// Issue a URAM read when data is available and the output FIFO has
|
||||
// enough free space to absorb all in-flight data in the read pipeline.
|
||||
logic [5:0] out_space;
|
||||
logic can_read;
|
||||
|
||||
always_comb begin
|
||||
can_read = ~empty & (out_space > READ_LATENCY[5:0]);
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear) begin
|
||||
wr_addr <= '0;
|
||||
end else if (write) begin
|
||||
wr_addr <= wr_addr + 1'b1;
|
||||
if (wr_ptr == ACTUAL_DEPTH - 1) begin
|
||||
wr_addr <= '0;
|
||||
wr_addr[SIZE] <= ~wr_addr[SIZE];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear) begin
|
||||
rd_addr <= '0;
|
||||
end else if (can_read) begin
|
||||
rd_addr <= rd_addr + 1'b1;
|
||||
if (rd_ptr == ACTUAL_DEPTH - 1) begin
|
||||
rd_addr <= '0;
|
||||
rd_addr[SIZE] <= ~rd_addr[SIZE];
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
//-------------------------------------------------
|
||||
// UltraRAM with pipelined read output
|
||||
//
|
||||
// Coding style matches the Xilinx UG901 SDP UltraRAM template exactly
|
||||
// so that Vivado can absorb mem_pipe_reg[] and memreg into the URAM
|
||||
// primitive's built-in pipeline registers (OREG). Key requirements:
|
||||
// - Single always block for read+write under a common mem_en
|
||||
// - No reset on the enable pipeline or data pipeline registers
|
||||
// - Pipeline enables derived from mem_en (maps to OREG_CE)
|
||||
//-------------------------------------------------
|
||||
|
||||
logic mem_en;
|
||||
always_comb begin
|
||||
mem_en = write | can_read;
|
||||
end
|
||||
|
||||
// the code below this line follows the UG901 template for SDP UltraRAM
|
||||
// -------------- start of template code --------------
|
||||
(* ram_style = "ultra" *)
|
||||
logic [WIDTH-1:0] mem [ACTUAL_DEPTH-1:0];
|
||||
|
||||
logic [WIDTH-1:0] memreg;
|
||||
logic [WIDTH-1:0] mem_pipe_reg [NBPIPE-1:0];
|
||||
logic mem_en_pipe_reg [NBPIPE:0];
|
||||
logic [WIDTH-1:0] dout;
|
||||
|
||||
// RAM : Simple Dual Port - Port A writes, Port B reads
|
||||
// Both under common mem_en to match URAM inference template
|
||||
always_ff @(posedge clk) begin
|
||||
if (mem_en) begin
|
||||
if (write) begin
|
||||
mem[wr_ptr] <= i_tdata;
|
||||
end
|
||||
memreg <= mem[rd_ptr];
|
||||
end
|
||||
end
|
||||
|
||||
// The enable of the RAM goes through a pipeline to produce a
|
||||
// series of pipelined enable signals required to control the data
|
||||
// pipeline. No reset - must match template for URAM register absorption.
|
||||
always_ff @(posedge clk) begin
|
||||
mem_en_pipe_reg[0] <= mem_en;
|
||||
for (int i = 0; i < NBPIPE; i = i + 1) begin
|
||||
mem_en_pipe_reg[i+1] <= mem_en_pipe_reg[i];
|
||||
end
|
||||
end
|
||||
|
||||
// RAM output data goes through a pipeline.
|
||||
always_ff @(posedge clk) begin
|
||||
if (mem_en_pipe_reg[0]) begin
|
||||
mem_pipe_reg[0] <= memreg;
|
||||
end
|
||||
|
||||
for (int i = 0; i < NBPIPE-1; i = i + 1) begin
|
||||
if (mem_en_pipe_reg[i+1]) begin
|
||||
mem_pipe_reg[i+1] <= mem_pipe_reg[i];
|
||||
end
|
||||
end
|
||||
|
||||
if (mem_en_pipe_reg[NBPIPE]) begin
|
||||
dout <= mem_pipe_reg[NBPIPE-1];
|
||||
end
|
||||
end
|
||||
// -------------- end of template code --------------
|
||||
|
||||
// Read-validity pipeline: tracks which pipeline slots contain actual
|
||||
// new read data vs stale data from write-only mem_en cycles. This is
|
||||
// separate from mem_en_pipe_reg to avoid interfering with URAM absorption.
|
||||
logic [NBPIPE+1:0] rd_valid_pipe;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear)
|
||||
rd_valid_pipe <= '0;
|
||||
else
|
||||
rd_valid_pipe <= {rd_valid_pipe[NBPIPE:0], can_read};
|
||||
end
|
||||
|
||||
logic dout_valid;
|
||||
always_comb begin
|
||||
dout_valid = rd_valid_pipe[NBPIPE+1];
|
||||
end
|
||||
|
||||
//-------------------------------------------------
|
||||
// Output FIFO
|
||||
//
|
||||
// An axi_fifo_short (32 entries) buffers the pipelined
|
||||
// URAM output and provides AXI-Stream backpressure.
|
||||
// Overflow is prevented by the can_read space check.
|
||||
//-------------------------------------------------
|
||||
|
||||
axi_fifo_short #(.WIDTH(WIDTH)) out_fifo (
|
||||
.clk (clk),
|
||||
.reset (reset),
|
||||
.clear (clear),
|
||||
.i_tdata (dout),
|
||||
.i_tvalid (dout_valid),
|
||||
.i_tready (),
|
||||
.o_tdata (o_tdata),
|
||||
.o_tvalid (o_tvalid),
|
||||
.o_tready (o_tready),
|
||||
.space (out_space),
|
||||
.occupied ()
|
||||
);
|
||||
|
||||
//-------------------------------------------------
|
||||
// Space and occupied (diagnostics, not exact)
|
||||
//-------------------------------------------------
|
||||
// Based on the read pattern the short FIFO may not be used to full capacity.
|
||||
// The overall useable space in all conditions is the URAM depth and the short
|
||||
// FIFO depth combined minus the number of elements needed for compensating
|
||||
// the read latency pipeline. When the short FIFO is filled to a level where
|
||||
// can_read deasserts, no more reads will be issued even though the elements
|
||||
// in the short FIFO may be free.
|
||||
localparam logic [15:0] NUMLINES = CASCADE_DEPTH * URAM_BLOCK_DEPTH + 32 - READ_LATENCY;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear)
|
||||
space <= NUMLINES;
|
||||
else if (read & ~write)
|
||||
space <= space + 1'd1;
|
||||
else if (write & ~read)
|
||||
space <= space - 1'd1;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (reset | clear)
|
||||
occupied <= '0;
|
||||
else if (read & ~write)
|
||||
occupied <= occupied - 1'd1;
|
||||
else if (write & ~read)
|
||||
occupied <= occupied + 1'd1;
|
||||
end
|
||||
|
||||
endmodule // axi_fifo_uram
|
||||
@ -1,29 +1,39 @@
|
||||
#
|
||||
# Copyright 2016 Ettus Research
|
||||
# Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
#-------------------------------------------------
|
||||
# Top-of-Makefile
|
||||
#-------------------------------------------------
|
||||
# Define BASE_DIR to point to the "top" dir
|
||||
BASE_DIR = $(abspath ../../../../top)
|
||||
BASE_DIR = $(abspath ../../../top)
|
||||
# Include viv_sim_preamble after defining BASE_DIR
|
||||
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||
|
||||
#-------------------------------------------------
|
||||
# Design Specific
|
||||
#-------------------------------------------------
|
||||
# Include makefiles and sources for the DUT and its dependencies
|
||||
include $(BASE_DIR)/../lib/control/Makefile.srcs
|
||||
include $(BASE_DIR)/../lib/fifo/Makefile.srcs
|
||||
|
||||
DESIGN_SRCS = $(abspath \
|
||||
$(CONTROL_LIB_SRCS) \
|
||||
$(FIFO_SRCS) \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Testbench Specific
|
||||
#-------------------------------------------------
|
||||
# Define only one toplevel module
|
||||
SIM_TOP = axi_fifo_tb
|
||||
SIM_TOP = axi_fifo_tb_all
|
||||
|
||||
SIM_SRCS = $(abspath \
|
||||
axi_fifo_tb.sv \
|
||||
$(LIB_DIR)/fifo/axi_fifo.v \
|
||||
$(LIB_DIR)/fifo/axi_fifo_flop.v \
|
||||
$(LIB_DIR)/fifo/axi_fifo_flop2.v \
|
||||
$(LIB_DIR)/fifo/axi_fifo_short.v \
|
||||
$(LIB_DIR)/fifo/axi_fifo_bram.v \
|
||||
)
|
||||
SIM_SRCS = \
|
||||
$(abspath axi_fifo/axi_fifo_tb.sv) \
|
||||
$(abspath axi_fifo_uram/axi_fifo_uram_tb.sv) \
|
||||
$(abspath axi_fifo_large/axi_fifo_large_tb.sv) \
|
||||
$(abspath axi_fifo_tb_all.sv) \
|
||||
|
||||
#-------------------------------------------------
|
||||
# Bottom-of-Makefile
|
||||
@ -4,23 +4,23 @@
|
||||
|
||||
`timescale 1ns/1ps
|
||||
`define SIM_TIMEOUT_US 1000
|
||||
`define NS_PER_TICK 1
|
||||
`define NUM_TEST_CASES 4
|
||||
|
||||
`include "sim_exec_report.vh"
|
||||
`include "sim_clks_rsts.vh"
|
||||
`include "test_exec.svh"
|
||||
`include "sim_cvita_lib.svh"
|
||||
`include "sim_axis_lib.svh"
|
||||
`include "sim_set_rb_lib.svh"
|
||||
|
||||
module axi_fifo_tb();
|
||||
import PkgTestExec::*;
|
||||
|
||||
/*********************************************
|
||||
** Setup Testbench
|
||||
*********************************************/
|
||||
`TEST_BENCH_INIT("axi_fifo_tb",`NUM_TEST_CASES,`NS_PER_TICK);
|
||||
localparam CLK_PERIOD = $ceil(1e9/166.67e6);
|
||||
`DEFINE_CLK(clk, CLK_PERIOD, 50);
|
||||
`DEFINE_RESET(reset, 0, 100);
|
||||
localparam CLK_PERIOD = 10; // 100 MHz
|
||||
bit clk, reset;
|
||||
|
||||
sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0))
|
||||
clk_gen (.clk(clk), .rst(reset));
|
||||
|
||||
// 4 variants: axi_fifo_flop, axi_fifo_flop2, axi_fifo_short, axi_fifo_bram
|
||||
localparam NUM_FIFOS = 4;
|
||||
@ -60,52 +60,63 @@ module axi_fifo_tb();
|
||||
initial begin
|
||||
clear = 'd0;
|
||||
|
||||
// Stop all clock events for simulation performance until setup is done.
|
||||
clk_gen.kill();
|
||||
|
||||
test.start_tb("axi_fifo_tb", `SIM_TIMEOUT_US * 1us);
|
||||
|
||||
clk_gen.revive();
|
||||
|
||||
// Drive reset sequence.
|
||||
clk_gen.start();
|
||||
clk_gen.reset(20);
|
||||
|
||||
/********************************************************
|
||||
** Test 1 -- Reset
|
||||
********************************************************/
|
||||
`TEST_CASE_START("Wait for Reset");
|
||||
test.start_test("Wait for Reset");
|
||||
m_axis.reset();
|
||||
s_axis.reset();
|
||||
while (reset) @(posedge clk);
|
||||
`TEST_CASE_DONE(~reset);
|
||||
@(negedge reset);
|
||||
test.end_test(~reset);
|
||||
|
||||
/********************************************************
|
||||
** Test 2 -- Check filling FIFOs
|
||||
********************************************************/
|
||||
`TEST_CASE_START("Check filling FIFOs");
|
||||
test.start_test("Check filling FIFOs");
|
||||
for (int i = 0; i < NUM_FIFOS; i++) begin
|
||||
$display("Testing FIFO %0d, SIZE %0d",i,2**FIFO_SIZES[i]);
|
||||
for (int k = 0; k < 2**FIFO_SIZES[i]; k++) begin
|
||||
$sformat(s,"FIFO size should be %0d entries, but detected %0d!",2**FIFO_SIZES[i],k);
|
||||
`ASSERT_FATAL(m_axis.axis.tready[i],s);
|
||||
test.assert_fatal(m_axis.axis.tready[i], s);
|
||||
m_axis.push_word(k,0,i);
|
||||
end
|
||||
$sformat(s,"FIFO depth appears to be greater than %0d entries! Might be due to output registering.",2**FIFO_SIZES[i]);
|
||||
`ASSERT_WARN(~m_axis.axis.tready[i],s);
|
||||
test.assert_warning(~m_axis.axis.tready[i], s);
|
||||
end
|
||||
`TEST_CASE_DONE(1);
|
||||
test.end_test(1);
|
||||
|
||||
/********************************************************
|
||||
** Test 3 -- Check emptying FIFOs
|
||||
********************************************************/
|
||||
`TEST_CASE_START("Check emptying FIFOs");
|
||||
test.start_test("Check emptying FIFOs");
|
||||
for (int i = 0; i < NUM_FIFOS; i++) begin
|
||||
$display("Testing FIFO %0d, SIZE %0d",i,2**FIFO_SIZES[i]);
|
||||
for (int k = 0; k < 2**FIFO_SIZES[i]; k = k + 1) begin
|
||||
$sformat(s,"FIFO prematurely empty! Occured after %0d reads!",k);
|
||||
`ASSERT_FATAL(s_axis.axis.tvalid[i],s);
|
||||
test.assert_fatal(s_axis.axis.tvalid[i], s);
|
||||
s_axis.pull_word(read_word,last,i);
|
||||
$sformat(s,"Read invalid FIFO word! Expected: %0d, Actual: %0d",k,read_word);
|
||||
`ASSERT_FATAL(read_word == k,s);
|
||||
test.assert_fatal(read_word == k, s);
|
||||
end
|
||||
`ASSERT_FATAL(~s_axis.axis.tvalid[i],"FIFO not empty after reading all entries!");
|
||||
test.assert_fatal(~s_axis.axis.tvalid[i], "FIFO not empty after reading all entries!");
|
||||
end
|
||||
`TEST_CASE_DONE(1);
|
||||
test.end_test(1);
|
||||
|
||||
/********************************************************
|
||||
** Test 4 -- Randomized Write / Read Timing
|
||||
********************************************************/
|
||||
`TEST_CASE_START("Randomized Write / Read");
|
||||
test.start_test("Randomized Write / Read");
|
||||
for (int i = 0; i < NUM_FIFOS; i++) begin
|
||||
$display("Testing FIFO %0d, SIZE %0d",i,2**FIFO_SIZES[i]);
|
||||
fork
|
||||
@ -122,13 +133,14 @@ module axi_fifo_tb();
|
||||
while ($signed($random()) > 0) @(posedge clk);
|
||||
s_axis.pull_word(read_word,last,i);
|
||||
$sformat(s,"Read invalid FIFO word! Expected: %0d, Actual: %0d",read_word,k);
|
||||
`ASSERT_FATAL(read_word == k,s);
|
||||
test.assert_fatal(read_word == k, s);
|
||||
end
|
||||
end
|
||||
join
|
||||
end
|
||||
`TEST_CASE_DONE(1);
|
||||
`TEST_BENCH_DONE;
|
||||
test.end_test();
|
||||
test.end_tb(0);
|
||||
clk_gen.kill();
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
192
fpga/usrp3/lib/sim/fifo/axi_fifo_large/axi_fifo_large_tb.sv
Normal file
192
fpga/usrp3/lib/sim/fifo/axi_fifo_large/axi_fifo_large_tb.sv
Normal file
@ -0,0 +1,192 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axi_fifo_large_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Testbench for axi_fifo_large.
|
||||
//
|
||||
// Ensures correct flow of data and accurate reporting of space/occupied
|
||||
// counts at start and end of test. Will not fill up the FIFO since this would
|
||||
// be very time consuming for larger sizes.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module axi_fifo_large_tb #(
|
||||
int WIDTH = 72,
|
||||
int DEPTH = 4096,
|
||||
string DEVICE = "ULTRASCALE",
|
||||
int MAX_NUM_URAM_BLOCKS = -1
|
||||
);
|
||||
|
||||
`include "test_exec.svh"
|
||||
|
||||
import PkgTestExec::*;
|
||||
import PkgAxiStreamBfm::*;
|
||||
import PkgRandom::*;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Local Parameters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
localparam real CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam time TIMEOUT = 50ms;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Clocks and Resets
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bit clk, rst;
|
||||
|
||||
sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0))
|
||||
clk_gen (.clk(clk), .rst(rst));
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// AXI-Stream Interfaces and BFM
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
AxiStreamIf #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) i_axis (.clk(clk), .rst(rst));
|
||||
AxiStreamIf #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) o_axis (.clk(clk), .rst(rst));
|
||||
|
||||
AxiStreamBfm #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) axis_bfm;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// DUT
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [19:0] space;
|
||||
wire [19:0] occupied;
|
||||
|
||||
// For the single-FIFO path (DEPTH <= 4096) the DUT instantiates axi_fifo whose
|
||||
// capacity is the next power of 2 >= DEPTH, plus 1 for the output register.
|
||||
// For the cascade path (DEPTH > 4096) capacity is rounded up to the next
|
||||
// multiple of 512 (BRAM_MIN_DEPTH).
|
||||
localparam int EXPECTED_SPACE = (DEPTH <= 4096) ?
|
||||
(1 << $clog2(DEPTH)) + 1 :
|
||||
((DEPTH + 511) / 512) * 512;
|
||||
|
||||
axi_fifo_large #(
|
||||
.WIDTH (WIDTH),
|
||||
.DEPTH (DEPTH),
|
||||
.DEVICE (DEVICE),
|
||||
.MAX_NUM_URAM_BLOCKS (MAX_NUM_URAM_BLOCKS)
|
||||
) dut (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (i_axis.tdata),
|
||||
.i_tvalid (i_axis.tvalid),
|
||||
.i_tready (i_axis.tready),
|
||||
.o_tdata (o_axis.tdata),
|
||||
.o_tvalid (o_axis.tvalid),
|
||||
.o_tready (o_axis.tready),
|
||||
.space (space),
|
||||
.occupied (occupied)
|
||||
);
|
||||
|
||||
// The FIFO has no tlast port. Tie o_axis.tlast high so the BFM slave
|
||||
// treats every received word as a complete single-word packet.
|
||||
assign o_axis.tlast = 1'b1;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Helper Tasks
|
||||
//---------------------------------------------------------------------------
|
||||
typedef AxiStreamPacket #(WIDTH, 1) AxisPacket_t;
|
||||
|
||||
// Total number of words to send per test
|
||||
// Just a few to test the functionality. Assume each FIFO in there is working
|
||||
// fine in itself.
|
||||
localparam int NUM_WORDS = 100;
|
||||
|
||||
// Send NUM_WORDS random single-word packets and verify received data matches.
|
||||
task automatic run_traffic_test(string test_name, int master_stall, int slave_stall);
|
||||
AxisPacket_t tx_pkts[$];
|
||||
AxisPacket_t rx_pkt;
|
||||
|
||||
test.start_test(test_name, TIMEOUT);
|
||||
|
||||
axis_bfm.set_master_stall_prob(master_stall);
|
||||
axis_bfm.set_slave_stall_prob(slave_stall);
|
||||
|
||||
// Generate and enqueue all TX words
|
||||
for (int p = 0; p < NUM_WORDS; p++) begin
|
||||
AxisPacket_t pkt = new();
|
||||
pkt.data.push_back(Rand#(WIDTH)::rand_bit());
|
||||
tx_pkts.push_back(pkt);
|
||||
axis_bfm.put(pkt.copy());
|
||||
end
|
||||
|
||||
// Receive and compare
|
||||
for (int p = 0; p < NUM_WORDS; p++) begin
|
||||
axis_bfm.get(rx_pkt);
|
||||
`ASSERT_ERROR(
|
||||
tx_pkts[p].data[0] === rx_pkt.data[0],
|
||||
$sformatf("Word %0d data mismatch! TX=%0h RX=%0h",
|
||||
p, tx_pkts[p].data[0], rx_pkt.data[0])
|
||||
)
|
||||
end
|
||||
|
||||
// Verify FIFO is empty
|
||||
repeat (20) @(posedge clk);
|
||||
`ASSERT_ERROR(occupied == 0,
|
||||
$sformatf("FIFO not empty after test, occupied = %0d", occupied))
|
||||
`ASSERT_ERROR(EXPECTED_SPACE == space,
|
||||
$sformatf("FIFO space mismatch after drain, expected %0d got %0d",
|
||||
EXPECTED_SPACE, space))
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Main Test Process
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
initial begin : main
|
||||
// stop all clock events for simulation performance
|
||||
clk_gen.kill();
|
||||
|
||||
begin
|
||||
string s;
|
||||
$sformat(s, "axi_fifo_large_tb - DEPTH=%0d MAX_NUM_URAM_BLOCKS=%0d WIDTH=%0d", DEPTH, MAX_NUM_URAM_BLOCKS, WIDTH);
|
||||
test.start_tb(s);
|
||||
end
|
||||
|
||||
clk_gen.revive();
|
||||
|
||||
// Setup BFM
|
||||
axis_bfm = new(i_axis.master, o_axis.slave);
|
||||
axis_bfm.run();
|
||||
|
||||
// Reset
|
||||
clk_gen.start();
|
||||
clk_gen.reset(20);
|
||||
@(negedge rst); // Wait for reset to deassert
|
||||
repeat (10) @(posedge clk);
|
||||
`ASSERT_ERROR(EXPECTED_SPACE == space,
|
||||
$sformatf("FIFO space mismatch after reset, expected %0d got %0d",
|
||||
EXPECTED_SPACE, space))
|
||||
|
||||
run_traffic_test("Slow master / slow slave", 50, 50);
|
||||
|
||||
//---------------------------------------
|
||||
// Done
|
||||
//---------------------------------------
|
||||
test.end_tb(0);
|
||||
clk_gen.kill();
|
||||
end : main
|
||||
|
||||
endmodule : axi_fifo_large_tb
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
70
fpga/usrp3/lib/sim/fifo/axi_fifo_tb_all.sv
Normal file
70
fpga/usrp3/lib/sim/fifo/axi_fifo_tb_all.sv
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axi_fifo_tb_all
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Wrapper testbench for axi_fifo testing.
|
||||
//
|
||||
|
||||
module axi_fifo_tb_all;
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Standard FIFO testing
|
||||
// --------------------------------------------------------------
|
||||
axi_fifo_tb #() axi_fifo_tb_i();
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// URAM testing
|
||||
// --------------------------------------------------------------
|
||||
localparam int URAM_TEST_DEPTH[3] = '{4096, 4096*3, 4096*5};
|
||||
for (genvar i = 0; i < $size(URAM_TEST_DEPTH); i++) begin : gen_uram_tb
|
||||
axi_fifo_uram_tb #(
|
||||
.WIDTH(72),
|
||||
.DEPTH(URAM_TEST_DEPTH[i])
|
||||
) axi_fifo_uram_tb_i ();
|
||||
end : gen_uram_tb
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Large FIFO testing
|
||||
// --------------------------------------------------------------
|
||||
localparam int NUM_URAM_BLOCKS[5] = '{-1, 0, 2, 8, 20};
|
||||
localparam int WIDTHS[3] = '{64, 144, 256};
|
||||
// Depth subset chosen to exercise every structural code path in axi_fifo_large:
|
||||
// - DEPTH <= 4096 (ULTRASCALE SIZE_THRESHOLD): single axi_fifo path
|
||||
// - DEPTH > 4096: URAM + BRAM cascade
|
||||
// * BRAM stages are driven by bits [13:9] of REMAINING_DEPTH_CEIL where
|
||||
// REMAINING = DEPTH mod 4096. The 8 unique bit patterns occur at
|
||||
// REMAINING in {0, 512, 1024, 1536, 2048, 2560, 3072, 3584}.
|
||||
// * URAM count = DEPTH / 4096; tested at 1, 2, 4, 8, 16 blocks.
|
||||
localparam int DEPTHS[24] = '{
|
||||
// Single-FIFO path (DEPTH <= ULTRASCALE threshold of 4096)
|
||||
512, 1024, 2048, 4096,
|
||||
// 1 URAM block + all 8 BRAM remainder patterns (REMAINING = 0..3584 step 512)
|
||||
// REMAINING=0 is the pure-URAM case; the rest add 1-3 small BRAM stages
|
||||
4096, 4608, 5120, 5632, 6144, 6656, 7168, 7680,
|
||||
// Higher URAM counts, pure-URAM (no BRAM remainder)
|
||||
8192, 16384, 32768,
|
||||
// Non-512-aligned depths: verify EXPECTED_SPACE rounds up to next 512 boundary
|
||||
100, 511, 513, 1000, 4097, 6000, 8456, 9103,
|
||||
// Large number
|
||||
2**16+512
|
||||
};
|
||||
|
||||
for (genvar i = 0; i < $size(NUM_URAM_BLOCKS); i++) begin : gen_uram
|
||||
for (genvar j = 0; j < $size(WIDTHS); j++) begin : gen_width
|
||||
for (genvar k = 0; k < $size(DEPTHS); k++) begin : gen_depth
|
||||
axi_fifo_large_tb #(
|
||||
.WIDTH(WIDTHS[j]),
|
||||
.DEPTH(DEPTHS[k]),
|
||||
.DEVICE("ULTRASCALE"),
|
||||
.MAX_NUM_URAM_BLOCKS(NUM_URAM_BLOCKS[i])
|
||||
) axi_fifo_large_tb_i();
|
||||
end : gen_depth
|
||||
end : gen_width
|
||||
end : gen_uram
|
||||
|
||||
endmodule
|
||||
208
fpga/usrp3/lib/sim/fifo/axi_fifo_uram/axi_fifo_uram_tb.sv
Normal file
208
fpga/usrp3/lib/sim/fifo/axi_fifo_uram/axi_fifo_uram_tb.sv
Normal file
@ -0,0 +1,208 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axi_fifo_uram_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Testbench for axi_fifo_uram.
|
||||
//
|
||||
// Verifies data integrity across the FIFO under three traffic scenarios:
|
||||
// 1. Full rate (no stalls on either side)
|
||||
// 2. Slow slave (fast master, slave stalls frequently)
|
||||
// 3. Slow master (master stalls frequently, fast slave)
|
||||
//
|
||||
// Parameters:
|
||||
// WIDTH : FIFO data width in bits
|
||||
// DEPTH : FIFO depth in entries
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module axi_fifo_uram_tb #(
|
||||
parameter WIDTH = 72,
|
||||
parameter DEPTH = 4096
|
||||
);
|
||||
|
||||
`include "test_exec.svh"
|
||||
`include "usrp_utils.svh"
|
||||
|
||||
import PkgTestExec::*;
|
||||
import PkgAxiStreamBfm::*;
|
||||
import PkgRandom::*;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Local Parameters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
localparam real CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam time TIMEOUT = 100ms;
|
||||
localparam int FIFO_SIZE = (DEPTH > 1) ? $clog2(DEPTH) : 0;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Clocks and Resets
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bit clk, rst;
|
||||
|
||||
sim_clock_gen #(.PERIOD(CLK_PERIOD), .AUTOSTART(0))
|
||||
clk_gen (.clk(clk), .rst(rst));
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// AXI-Stream Interfaces and BFM
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
AxiStreamIf #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) i_axis (.clk(clk), .rst(rst));
|
||||
AxiStreamIf #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) o_axis (.clk(clk), .rst(rst));
|
||||
|
||||
AxiStreamBfm #(.DATA_WIDTH(WIDTH), .TKEEP(0), .TUSER(0)) axis_bfm;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// DUT
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
wire [15:0] space;
|
||||
wire [15:0] occupied;
|
||||
|
||||
// Expected space is a multiple of 4096 (URAM block depth) plus overhead (0-32 words)
|
||||
// for input/output flop2 stages.
|
||||
localparam int EXPECTED_SPACE_MIN = ((DEPTH + 4095) / 4096) * 4096;
|
||||
localparam int EXPECTED_SPACE_MAX = EXPECTED_SPACE_MIN + 32;
|
||||
|
||||
axi_fifo_uram #(
|
||||
.WIDTH (WIDTH),
|
||||
.DEPTH (DEPTH)
|
||||
) dut (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (i_axis.tdata),
|
||||
.i_tvalid (i_axis.tvalid),
|
||||
.i_tready (i_axis.tready),
|
||||
.o_tdata (o_axis.tdata),
|
||||
.o_tvalid (o_axis.tvalid),
|
||||
.o_tready (o_axis.tready),
|
||||
.space (space),
|
||||
.occupied (occupied)
|
||||
);
|
||||
|
||||
// The FIFO has no tlast port. Tie o_axis.tlast high so the BFM slave
|
||||
// treats every received word as a complete single-word packet.
|
||||
assign o_axis.tlast = 1'b1;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Helper Tasks
|
||||
//---------------------------------------------------------------------------
|
||||
typedef AxiStreamPacket #(WIDTH, 1) AxisPacket_t;
|
||||
|
||||
// Total number of words to send per test.
|
||||
// Enforce a minimum of 100 words while still stressing deeper FIFOs.
|
||||
localparam int NUM_WORDS = `MAX((DEPTH * 2), 100);
|
||||
|
||||
// Send NUM_WORDS random single-word packets and verify received data matches.
|
||||
task automatic run_traffic_test(string test_name, int master_stall, int slave_stall);
|
||||
AxisPacket_t tx_pkts[$];
|
||||
AxisPacket_t rx_pkt;
|
||||
|
||||
test.start_test(test_name, TIMEOUT);
|
||||
|
||||
axis_bfm.set_master_stall_prob(master_stall);
|
||||
axis_bfm.set_slave_stall_prob(slave_stall);
|
||||
|
||||
// Generate and enqueue all TX words
|
||||
for (int p = 0; p < NUM_WORDS; p++) begin
|
||||
AxisPacket_t pkt = new();
|
||||
pkt.data.push_back(Rand#(WIDTH)::rand_bit());
|
||||
tx_pkts.push_back(pkt);
|
||||
axis_bfm.put(pkt.copy());
|
||||
end
|
||||
|
||||
// Receive and compare
|
||||
for (int p = 0; p < NUM_WORDS; p++) begin
|
||||
axis_bfm.get(rx_pkt);
|
||||
`ASSERT_ERROR(
|
||||
tx_pkts[p].data[0] === rx_pkt.data[0],
|
||||
$sformatf("Word %0d data mismatch! TX=%0h RX=%0h",
|
||||
p, tx_pkts[p].data[0], rx_pkt.data[0])
|
||||
)
|
||||
end
|
||||
|
||||
// Verify FIFO is empty
|
||||
repeat (20) @(posedge clk);
|
||||
`ASSERT_ERROR(occupied == 0,
|
||||
$sformatf("FIFO not empty after test, occupied = %0d", occupied))
|
||||
`ASSERT_ERROR(space >= EXPECTED_SPACE_MIN && space <= EXPECTED_SPACE_MAX,
|
||||
$sformatf("FIFO space out of range after drain, expected [%0d:%0d] got %0d",
|
||||
EXPECTED_SPACE_MIN, EXPECTED_SPACE_MAX, space))
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Main Test Process
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
initial begin : main
|
||||
// stop all clock events for simulation performance
|
||||
clk_gen.kill();
|
||||
|
||||
begin
|
||||
string s;
|
||||
$sformat(s, "axi_fifo_uram_tb - DEPTH=%0d WIDTH=%0d", DEPTH, WIDTH);
|
||||
test.start_tb(s);
|
||||
end
|
||||
|
||||
clk_gen.revive();
|
||||
|
||||
// Setup BFM
|
||||
axis_bfm = new(i_axis.master, o_axis.slave);
|
||||
axis_bfm.run();
|
||||
|
||||
// Reset
|
||||
clk_gen.start();
|
||||
clk_gen.reset(20);
|
||||
@(negedge rst); // Wait for reset to deassert
|
||||
repeat (10) @(posedge clk);
|
||||
`ASSERT_ERROR(space >= EXPECTED_SPACE_MIN && space <= EXPECTED_SPACE_MAX,
|
||||
$sformatf("FIFO space out of range after reset, expected [%0d:%0d] got %0d",
|
||||
EXPECTED_SPACE_MIN, EXPECTED_SPACE_MAX, space))
|
||||
|
||||
//---------------------------------------
|
||||
// Test 1: Full rate (no stalls)
|
||||
//---------------------------------------
|
||||
run_traffic_test("Full rate", 0, 0);
|
||||
|
||||
//---------------------------------------
|
||||
// Test 2: Slow slave (backpressure)
|
||||
//---------------------------------------
|
||||
run_traffic_test("Slow slave / fast master", 0, 70);
|
||||
|
||||
//---------------------------------------
|
||||
// Test 3: Slow master (starving)
|
||||
//---------------------------------------
|
||||
run_traffic_test("Slow master / fast slave", 70, 0);
|
||||
|
||||
//---------------------------------------
|
||||
// Test 4: Both sides stalling
|
||||
//---------------------------------------
|
||||
run_traffic_test("Slow master / slow slave", 50, 50);
|
||||
|
||||
//---------------------------------------
|
||||
// Done
|
||||
//---------------------------------------
|
||||
test.end_tb(0);
|
||||
clk_gen.kill();
|
||||
end : main
|
||||
|
||||
endmodule : axi_fifo_uram_tb
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
@ -22,3 +22,4 @@ top/x400/ip/eth_100g_bd/lbus_tb
|
||||
top/x400/rf/sim
|
||||
top/x300/dboards/obx/cpld/sim/obx_register_endpoints
|
||||
top/x300/dboards/obx/cpld/sim/obx_spi_passthrough
|
||||
lib/sim/fifo/axi_fifo
|
||||
|
||||
Loading…
Reference in New Issue
Block a user