3. 同步FIFO计数器设计

同步FIFO计数器设计 学习卡片

任务目标

使用计数器方式实现同步FIFO的满/空判断逻辑,并与指针差值判断法进行对比验证。计数器法通过记录当前有效数据个数,在写入时加1、读出时减1,从而直接获得空满状态。

设计原理

同步FIFO中,读写指针均随时钟更新。计数器法额外维护一个count寄存器:

  • 写使能且未满时,count <= count + 1;
  • 读使能且未空时,count <= count - 1;
  • 同时读写时,count保持不变。

空标志为count == 0,满标志为count == DEPTH。指针法则通过比较读写指针是否相等判断空,通过写指针反推一位后与读指针比较判断满。两种方法在功能上等价,但计数器法无需额外指针位宽扩展。

Verilog实现

module sync_fifo_counter #(
    parameter DATA_WIDTH = 8,
    parameter DEPTH      = 16,
    parameter ADDR_WIDTH = 4  // log2(DEPTH)
)(
    input  wire                  clk,
    input  wire                  rst_n,
    input  wire                  wr_en,
    input  wire                  rd_en,
    input  wire [DATA_WIDTH-1:0] din,
    output reg  [DATA_WIDTH-1:0] dout,
    output wire                  full,
    output wire                  empty,
    output wire [ADDR_WIDTH:0]   count  // 计数器宽度比地址宽1位
);

    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_reg;

    assign count = count_reg;
    assign full  = (count_reg == DEPTH);
    assign empty = (count_reg == 0);

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

endmodule

对比验证testbench

module tb_sync_fifo;
    parameter DEPTH = 16;
    reg clk = 0, rst_n = 0;
    reg wr_en = 0, rd_en = 0;
    reg [7:0] din = 0;
    wire [7:0] dout;
    wire full, empty;
    wire [4:0] count;

    // 指针法参考模型
    reg [3:0] ref_wr_ptr = 0, ref_rd_ptr = 0;
    reg [4:0] ref_count = 0;
    wire ref_full  = (ref_wr_ptr == ref_rd_ptr) && (ref_count == DEPTH);
    wire ref_empty = (ref_wr_ptr == ref_rd_ptr) && (ref_count == 0);

    sync_fifo_counter #(.DATA_WIDTH(8), .DEPTH(DEPTH)) uut (
        .clk(clk), .rst_n(rst_n), .wr_en(wr_en), .rd_en(rd_en),
        .din(din), .dout(dout), .full(full), .empty(empty), .count(count)
    );

    always #5 clk = ~clk;

    // 参考模型:用指针差值法计算空满
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            ref_wr_ptr <= 0; ref_rd_ptr <= 0; ref_count <= 0;
        end else begin
            if (wr_en && !ref_full) begin
                ref_wr_ptr <= ref_wr_ptr + 1;
                ref_count <= ref_count + 1;
            end
            if (rd_en && !ref_empty) begin
                ref_rd_ptr <= ref_rd_ptr + 1;
                ref_count <= ref_count - 1;
            end
        end
    end

    // 对比断言
    always @(posedge clk) begin
        if (rst_n) begin
            if (full  !== ref_full)  $display("FAIL: full mismatch at t=%0t", $time);
            if (empty !== ref_empty) $display("FAIL: empty mismatch at t=%0t", $time);
            if (count !== ref_count) $display("FAIL: count mismatch at t=%0t", $time);
        end
    end

    integer i;
    initial begin
        // 复位
        #20 rst_n = 1;
        // 写入8个数据
        for (i = 0; i < 8; i = i + 1) begin
            @(posedge clk);
            wr_en = 1; din = i;
        end
        // 连续读写4次(同时读写)
        for (i = 0; i < 4; i = i + 1) begin
            @(posedge clk);
            wr_en = 1; rd_en = 1; din = i + 100;
        end
        // 只读8次,清空FIFO
        for (i = 0; i < 8; i = i + 1) begin
            @(posedge clk);
            rd_en = 1; wr_en = 0;
        end
        // 写满测试
        for (i = 0; i < 20; i = i + 1) begin
            @(posedge clk);
            wr_en = 1; rd_en = 0; din = i;
        end
        #20;
        $display("Test completed. Check FAIL messages above.");
        $finish;
    end

    initial begin
        $monitor("t=%0t count=%0d full=%b empty=%b ref_full=%b ref_empty=%b",
                 $time, count, full, empty, ref_full, ref_empty);
    end
endmodule

运行与验证

  1. 仿真运行:使用Vivado/ModelSim/Verilator等工具编译两个文件并运行testbench。
  2. 通过标准:仿真过程中无任何FAIL消息输出,且count值始终与参考模型一致。
  3. 预期行为:写入8个数据后count=8;同时读写时count不变;写满16个后full=1,继续写无效;读空后empty=1,继续读无效。

关键点说明

  • 计数器宽度必须为ADDR_WIDTH+1,否则无法表示满状态DEPTH。
  • 同时读写时计数器不变,但指针各自递增,这是与指针法行为一致的关键。
  • 指针法需要额外1位宽度区分满/空,计数器法则直接比较计数值,逻辑更直观。