diff --git a/fpga/usrp3/lib/fifo/Makefile.srcs b/fpga/usrp3/lib/fifo/Makefile.srcs index 8b48f6586..aa07314f6 100644 --- a/fpga/usrp3/lib/fifo/Makefile.srcs +++ b/fpga/usrp3/lib/fifo/Makefile.srcs @@ -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 \ diff --git a/fpga/usrp3/lib/fifo/axi_fifo_large.sv b/fpga/usrp3/lib/fifo/axi_fifo_large.sv new file mode 100644 index 000000000..8cb5d55a8 --- /dev/null +++ b/fpga/usrp3/lib/fifo/axi_fifo_large.sv @@ -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 diff --git a/fpga/usrp3/lib/fifo/axi_fifo_uram.sv b/fpga/usrp3/lib/fifo/axi_fifo_uram.sv new file mode 100644 index 000000000..84836d712 --- /dev/null +++ b/fpga/usrp3/lib/fifo/axi_fifo_uram.sv @@ -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 diff --git a/fpga/usrp3/lib/sim/fifo/axi_fifo/Makefile b/fpga/usrp3/lib/sim/fifo/Makefile similarity index 51% rename from fpga/usrp3/lib/sim/fifo/axi_fifo/Makefile rename to fpga/usrp3/lib/sim/fifo/Makefile index 3cd861176..abb4cddf2 100644 --- a/fpga/usrp3/lib/sim/fifo/axi_fifo/Makefile +++ b/fpga/usrp3/lib/sim/fifo/Makefile @@ -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 diff --git a/fpga/usrp3/lib/sim/fifo/axi_fifo/axi_fifo_tb.sv b/fpga/usrp3/lib/sim/fifo/axi_fifo/axi_fifo_tb.sv index c2832cade..d78ca2278 100644 --- a/fpga/usrp3/lib/sim/fifo/axi_fifo/axi_fifo_tb.sv +++ b/fpga/usrp3/lib/sim/fifo/axi_fifo/axi_fifo_tb.sv @@ -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 diff --git a/fpga/usrp3/lib/sim/fifo/axi_fifo_large/axi_fifo_large_tb.sv b/fpga/usrp3/lib/sim/fifo/axi_fifo_large/axi_fifo_large_tb.sv new file mode 100644 index 000000000..e97115196 --- /dev/null +++ b/fpga/usrp3/lib/sim/fifo/axi_fifo_large/axi_fifo_large_tb.sv @@ -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 diff --git a/fpga/usrp3/lib/sim/fifo/axi_fifo_tb_all.sv b/fpga/usrp3/lib/sim/fifo/axi_fifo_tb_all.sv new file mode 100644 index 000000000..31279ee08 --- /dev/null +++ b/fpga/usrp3/lib/sim/fifo/axi_fifo_tb_all.sv @@ -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 diff --git a/fpga/usrp3/lib/sim/fifo/axi_fifo_uram/axi_fifo_uram_tb.sv b/fpga/usrp3/lib/sim/fifo/axi_fifo_uram/axi_fifo_uram_tb.sv new file mode 100644 index 000000000..d6e9609db --- /dev/null +++ b/fpga/usrp3/lib/sim/fifo/axi_fifo_uram/axi_fifo_uram_tb.sv @@ -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 diff --git a/fpga/usrp3/tools/utils/testbenches.excludes b/fpga/usrp3/tools/utils/testbenches.excludes index 65d364ca0..bb1d8110a 100644 --- a/fpga/usrp3/tools/utils/testbenches.excludes +++ b/fpga/usrp3/tools/utils/testbenches.excludes @@ -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