2. 同步FIFO参数化设计

任务目标

设计一个参数化同步FIFO,支持数据位宽和存储深度通过参数配置,并验证不同参数配置下的读写功能正确性。本案例聚焦FIFO核心逻辑,不包含跨时钟域处理。

设计实现

采用参数化设计,通过 DATA_WIDTH 和 DEPTH 两个参数控制位宽与深度。使用二进制计数器生成读写地址,通过读写指针相等判断空/满状态。存储体使用寄存器数组实现,深度限制为2的幂次以便地址回绕。

module sync_fifo #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH      = 16
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire                  wr_en,
    input  wire [DATA_WIDTH-1:0] wr_data,
    input  wire                  rd_en,
    output wire [DATA_WIDTH-1:0] rd_data,
    output wire                  full,
    output wire                  empty
);

    // 地址位宽(深度需为2的幂)
    localparam ADDR_WIDTH = $clog2(DEPTH);

    // 存储体
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];

    // 读写指针与计数器
    reg [ADDR_WIDTH-1:0] wr_ptr;
    reg [ADDR_WIDTH-1:0] rd_ptr;
    reg [ADDR_WIDTH:0]   count;  // 多1位用于满判断

    // 读写指针与计数更新
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_ptr <= 0;
            rd_ptr <= 0;
            count  <= 0;
        end else begin
            case ({wr_en && !full, rd_en && !empty})
                2'b10: begin  // 仅写
                    mem[wr_ptr] <= wr_data;
                    wr_ptr <= wr_ptr + 1'b1;
                    count  <= count + 1'b1;
                end
                2'b01: begin  // 仅读
                    rd_ptr <= rd_ptr + 1'b1;
                    count  <= count - 1'b1;
                end
                2'b11: begin  // 同时读写
                    mem[wr_ptr] <= wr_data;
                    wr_ptr <= wr_ptr + 1'b1;
                    rd_ptr <= rd_ptr + 1'b1;
                end
                default: ;    // 无操作
            endcase
        end
    end

    // 状态输出
    assign full  = (count == DEPTH);
    assign empty = (count == 0);
    assign rd_data = mem[rd_ptr];

endmodule

测试平台与验证

编写参数化testbench,通过 defparam 或模块实例化参数分别验证 8×16 和 16×8 两种配置。测试流程:先连续写入DEPTH个数据直至满,再连续读出并比对数据,最后验证空标志。

module tb_sync_fifo;

    // 配置1:8位宽 × 16深度
    parameter W1 = 8, D1 = 16;
    // 配置2:16位宽 × 8深度
    parameter W2 = 16, D2 = 8;

    reg clk, rst_n;
    reg wr_en, rd_en;
    reg [W1-1:0] wdata1;
    wire [W1-1:0] rdata1;
    wire full1, empty1;

    reg [W2-1:0] wdata2;
    wire [W2-1:0] rdata2;
    wire full2, empty2;

    integer i, err;

    // 时钟
    initial clk = 0;
    always #5 clk = ~clk;

    // 实例化两个配置
    sync_fifo #(.DATA_WIDTH(W1), .DEPTH(D1)) u_fifo1 (
        .clk(clk), .rst_n(rst_n),
        .wr_en(wr_en), .wr_data(wdata1),
        .rd_en(rd_en), .rd_data(rdata1),
        .full(full1), .empty(empty1)
    );

    sync_fifo #(.DATA_WIDTH(W2), .DEPTH(D2)) u_fifo2 (
        .clk(clk), .rst_n(rst_n),
        .wr_en(wr_en), .wr_data(wdata2),
        .rd_en(rd_en), .rd_data(rdata2),
        .full(full2), .empty(empty2)
    );

    // 测试任务
    task test_fifo;
        input [31:0] depth;
        integer i;
        begin
            err = 0;
            // 复位
            rst_n = 0;
            wr_en = 0; rd_en = 0;
            #20 rst_n = 1;
            #10;

            // 写满
            for (i = 0; i < depth; i = i + 1) begin
                @(posedge clk);
                wr_en = 1;
                wdata1 = i;
                wdata2 = i * 3;
            end
            @(posedge clk);
            wr_en = 0;

            // 检查满标志
            if (u_fifo1.full !== 1'b1 || u_fifo2.full !== 1'b1) begin
                $display("FAIL: full flag not asserted");
                err = err + 1;
            end

            // 读空并比对
            for (i = 0; i < depth; i = i + 1) begin
                @(posedge clk);
                rd_en = 1;
                #1;
                if (rdata1 !== i || rdata2 !== i * 3) begin
                    $display("FAIL: data mismatch at %0d, got %0d/%0d", i, rdata1, rdata2);
                    err = err + 1;
                end
            end
            @(posedge clk);
            rd_en = 0;

            // 检查空标志
            if (u_fifo1.empty !== 1'b1 || u_fifo2.empty !== 1'b1) begin
                $display("FAIL: empty flag not asserted");
                err = err + 1;
            end

            if (err == 0)
                $display("PASS: depth=%0d test passed", depth);
            else
                $display("FAIL: depth=%0d, errors=%0d", depth, err);
        end
    endtask

    initial begin
        // 测试配置1(深度16)
        test_fifo(D1);
        // 测试配置2(深度8)
        test_fifo(D2);
        #50 $finish;
    end

endmodule

运行与判定标准

运行方式:在仿真工具(如Vivado XSim、ModelSim)中,将上述两个模块加入工程,以 tb_sync_fifo 为顶层运行仿真。

通过条件:仿真控制台输出两行 PASS 信息,分别对应深度16和深度8的测试,且无 FAIL 信息。若出现数据不匹配或标志位错误,需检查读写时序或指针逻辑。

关键点说明

  • 读写同时使能时,count 保持不变,但数据仍正确写入与读出,这是FIFO的标准行为。
  • 深度必须为2的幂,否则 $clog2 会向上取整导致地址空间大于实际存储,造成逻辑错误。
  • 空/满标志在读写使能的同一时钟沿更新,仿真中需在时钟沿后采样(如 #1)以获得稳定值。