🏗️ 第09课:异步FIFO完整实现

📚 课程阶段:异步FIFO(4/5)
🎯 学习目标:将前面学到的所有组件整合,实现一个完整的、可综合的异步FIFO,包含双端口RAM、格雷码指针、两级同步器、满空标志

一、异步FIFO设计规格

设计规格

参数说明
数据宽度参数化 DATA_WIDTH默认8位
地址宽度参数化 ADDR_WIDTH默认4位,深度16
写时钟wr_clk独立写时钟
读时钟rd_clk独立读时钟
复位低有效异步复位两域分别同步释放
满标志组合逻辑输出写域生成
空标志组合逻辑输出读域生成
同步器2级FF格雷码指针跨域

二、完整异步FIFO实现

// async_fifo.v
// 异步FIFO完整实现
// 双时钟域,格雷码指针,两级同步器
// 深度必须为2的幂
module async_fifo #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4
)(
    // 写接口(wr_clk域)
    input  wire                      wr_clk,
    input  wire                      wr_rst_n,
    input  wire [DATA_WIDTH-1:0]     wr_data,
    input  wire                      wr_en,
    output wire                      full,

    // 读接口(rd_clk域)
    input  wire                      rd_clk,
    input  wire                      rd_rst_n,
    output wire [DATA_WIDTH-1:0]     rd_data,
    input  wire                      rd_en,
    output wire                      empty
);

    // ===================== 内部信号 =====================
    localparam PTR_WIDTH = ADDR_WIDTH + 1;

    // 写域指针
    reg  [PTR_WIDTH-1:0] wr_ptr_bin;
    wire [PTR_WIDTH-1:0] wr_ptr_gray;
    wire [PTR_WIDTH-1:0] rd_ptr_gray_sync;  // 读指针同步到写域

    // 读域指针
    reg  [PTR_WIDTH-1:0] rd_ptr_bin;
    wire [PTR_WIDTH-1:0] rd_ptr_gray;
    wire [PTR_WIDTH-1:0] wr_ptr_gray_sync;  // 写指针同步到读域

    // RAM
    reg [DATA_WIDTH-1:0] mem [0:(1<> 1);

    // ===================== 读指针 =====================
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n)
            rd_ptr_bin <= {PTR_WIDTH{1'b0}};
        else if (rd_en && !empty)
            rd_ptr_bin <= rd_ptr_bin + 1'b1;
    end

    assign rd_ptr_gray = rd_ptr_bin ^ (rd_ptr_bin >> 1);

    // ===================== RAM写入 =====================
    always @(posedge wr_clk) begin
        if (wr_en && !full)
            mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
    end

    // ===================== RAM读取 =====================
    assign rd_data = mem[rd_ptr_bin[ADDR_WIDTH-1:0]];

    // ===================== 同步器:读指针→写域 =====================
    reg [PTR_WIDTH-1:0] rd_ptr_gray_d1, rd_ptr_gray_d2;
    always @(posedge wr_clk or negedge wr_rst_n) begin
        if (!wr_rst_n) begin
            rd_ptr_gray_d1 <= {PTR_WIDTH{1'b0}};
            rd_ptr_gray_d2 <= {PTR_WIDTH{1'b0}};
        end else begin
            rd_ptr_gray_d1 <= rd_ptr_gray;
            rd_ptr_gray_d2 <= rd_ptr_gray_d1;
        end
    end
    assign rd_ptr_gray_sync = rd_ptr_gray_d2;

    // ===================== 同步器:写指针→读域 =====================
    reg [PTR_WIDTH-1:0] wr_ptr_gray_d1, wr_ptr_gray_d2;
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n) begin
            wr_ptr_gray_d1 <= {PTR_WIDTH{1'b0}};
            wr_ptr_gray_d2 <= {PTR_WIDTH{1'b0}};
        end else begin
            wr_ptr_gray_d1 <= wr_ptr_gray;
            wr_ptr_gray_d2 <= wr_ptr_gray_d1;
        end
    end
    assign wr_ptr_gray_sync = wr_ptr_gray_d2;

    // ===================== 满标志(写域)=====================
    assign full = (wr_ptr_gray == {~rd_ptr_gray_sync[PTR_WIDTH-1:PTR_WIDTH-2],
                                    rd_ptr_gray_sync[PTR_WIDTH-3:0]});

    // ===================== 空标志(读域)=====================
    assign empty = (wr_ptr_gray_sync == rd_ptr_gray);

endmodule

三、异步FIFO的时序细节

关键时序路径

写路径关键时序

  1. wr_en → full检查 → RAM写入 → wr_ptr更新(1个wr_clk周期)
  2. rd_ptr_gray → 2级同步 → full判断更新(2个wr_clk周期延迟)

读路径关键时序

  1. rd_en → empty检查 → rd_data输出 → rd_ptr更新(1个rd_clk周期)
  2. wr_ptr_gray → 2级同步 → empty判断更新(2个rd_clk周期延迟)

最坏情况场景:写端连续写入直到满,然后读端读取。读端需要2个rd_clk周期才能看到写指针的变化,空标志才能解除断言。这期间读端无法读出数据——这就是同步延迟的性能代价。

四、带可编程满空的异步FIFO

// async_fifo_prog.v
// 带可编程满空标志的异步FIFO
// 在基础异步FIFO上增加可编程阈值
module async_fifo_prog #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4,
    parameter PROG_FULL_THRESH  = 12,
    parameter PROG_EMPTY_THRESH = 4
)(
    input  wire                  wr_clk,
    input  wire                  wr_rst_n,
    input  wire [DATA_WIDTH-1:0] wr_data,
    input  wire                  wr_en,
    output wire                  full,
    output wire                  prog_full,

    input  wire                  rd_clk,
    input  wire                  rd_rst_n,
    output wire [DATA_WIDTH-1:0] rd_data,
    input  wire                  rd_en,
    output wire                  empty,
    output wire                  prog_empty
);

    localparam PTR_WIDTH = ADDR_WIDTH + 1;

    // 基础异步FIFO实例
    wire [PTR_WIDTH-1:0] wr_ptr_bin, rd_ptr_bin;
    wire [PTR_WIDTH-1:0] wr_ptr_gray, rd_ptr_gray;
    wire [PTR_WIDTH-1:0] rd_ptr_gray_sync, wr_ptr_gray_sync;

    reg [PTR_WIDTH-1:0] wr_ptr_bin_r, rd_ptr_bin_r;
    reg [DATA_WIDTH-1:0] mem [0:(1<> 1);

    // 读逻辑
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n)
            rd_ptr_bin_r <= 0;
        else if (rd_en && !empty)
            rd_ptr_bin_r <= rd_ptr_bin_r + 1;
    end

    assign rd_ptr_bin  = rd_ptr_bin_r;
    assign rd_ptr_gray = rd_ptr_bin ^ (rd_ptr_bin >> 1);
    assign rd_data     = mem[rd_ptr_bin_r[ADDR_WIDTH-1:0]];

    // 同步器
    reg [PTR_WIDTH-1:0] rd_gray_d1, rd_gray_d2;
    always @(posedge wr_clk or negedge wr_rst_n) begin
        if (!wr_rst_n) begin
            rd_gray_d1 <= 0; rd_gray_d2 <= 0;
        end else begin
            rd_gray_d1 <= rd_ptr_gray; rd_gray_d2 <= rd_gray_d1;
        end
    end
    assign rd_ptr_gray_sync = rd_gray_d2;

    reg [PTR_WIDTH-1:0] wr_gray_d1, wr_gray_d2;
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n) begin
            wr_gray_d1 <= 0; wr_gray_d2 <= 0;
        end else begin
            wr_gray_d1 <= wr_ptr_gray; wr_gray_d2 <= wr_gray_d1;
        end
    end
    assign wr_ptr_gray_sync = wr_gray_d2;

    // 满空标志
    assign full  = (wr_ptr_gray == {~rd_ptr_gray_sync[PTR_WIDTH-1:PTR_WIDTH-2],
                                     rd_ptr_gray_sync[PTR_WIDTH-3:0]});
    assign empty = (wr_ptr_gray_sync == rd_ptr_gray);

    // 可编程满空:需要将格雷码转回二进制计算count
    // 写域计算count(用于prog_full)
    wire [PTR_WIDTH-1:0] rd_bin_sync;
    assign rd_bin_sync[PTR_WIDTH-1] = rd_ptr_gray_sync[PTR_WIDTH-1];
    genvar i;
    generate
        for (i = PTR_WIDTH-2; i >= 0; i = i - 1) begin : gen_rd_g2b
            assign rd_bin_sync[i] = rd_ptr_gray_sync[i] ^ rd_bin_sync[i+1];
        end
    endgenerate

    wire [PTR_WIDTH-1:0] wr_count;
    assign wr_count   = wr_ptr_bin - rd_bin_sync;
    assign prog_full  = (wr_count >= PROG_FULL_THRESH);

    // 读域计算count(用于prog_empty)
    wire [PTR_WIDTH-1:0] wr_bin_sync;
    assign wr_bin_sync[PTR_WIDTH-1] = wr_ptr_gray_sync[PTR_WIDTH-1];
    generate
        for (i = PTR_WIDTH-2; i >= 0; i = i - 1) begin : gen_wr_g2b
            assign wr_bin_sync[i] = wr_ptr_gray_sync[i] ^ wr_bin_sync[i+1];
        end
    endgenerate

    wire [PTR_WIDTH-1:0] rd_count;
    assign rd_count   = wr_bin_sync - rd_ptr_bin;
    assign prog_empty = (rd_count <= PROG_EMPTY_THRESH);

endmodule

五、异步FIFO的验证策略

异步FIFO验证的关键场景

  1. 基本功能:写入→读出数据一致性
  2. 满写保护:满时写入不应破坏数据
  3. 空读保护:空时读取不应产生错误
  4. 同时读写:满/空临界时的同时操作
  5. 频率比:wr_clk和rd_clk不同频率比
  6. 指针回绕:指针从最大值回到0
  7. 复位恢复:复位后FIFO状态正确
  8. 亚稳态注入:模拟同步器亚稳态

六、异步FIFO的面积和时序优化

优化技巧

1. RAM实现选择

2. 同步器优化

3. 满空标志优化

4. 读数据优化

七、异步FIFO的跨域约束详解

在ASIC/FPGA实现流程中,异步FIFO的跨域路径需要特殊处理:

SDC约束写法

# 设置跨域路径为虚假路径(不需要时序分析)
set_false_path -from [get_clocks wr_clk] -to [get_clocks rd_clk]
set_false_path -from [get_clocks rd_clk] -to [get_clocks wr_clk]

# 或者使用时钟组约束(更严格)
set_clock_groups -asynchronous \
    -group [get_clocks wr_clk] \
    -group [get_clocks rd_clk]

# 同步器内部路径:允许最大延迟
set_max_delay 2.0 -from [get_pins rd_ptr_gray_reg/Q] \
                     -to [get_pins rd_ptr_gray_d1_reg/D]

# 标记同步器FF,防止工具优化
set_property ASYNC_REG TRUE [get_cells rd_ptr_gray_d1_reg]
set_property ASYNC_REG TRUE [get_cells rd_ptr_gray_d2_reg]

为什么需要false_path:跨域路径由同步器处理,不需要满足传统时序约束。如果不设置false_path,工具会报告跨域路径的时序违例。

ASYNC_REG属性:告诉综合工具这些FF用于跨域同步,不要优化或移动它们。在Xilinx FPGA中尤其重要,确保同步器FF在同一个CLB内。

八、关键概念总结

✅ 本课核心要点

  1. 完整异步FIFO = 双端口RAM + 格雷码指针 + 两级同步器 + 满空标志
  2. 写域:wr_ptr递增 + wr_ptr_gray同步rd_ptr_gray → 生成full
  3. 读域:rd_ptr递增 + rd_ptr_gray同步wr_ptr_gray → 生成empty
  4. 可编程满空需要格雷码→二进制转换计算count
  5. 验证覆盖:频率比、满空临界、指针回绕、复位恢复
  6. 优化方向:RAM选型、同步器保护、标志流水线化

📝 练习题

1. 编程题:为async_fifo添加FWFT(First-Word Fall-Through)支持。提示:在读域添加输出寄存器和预取逻辑。

2. 验证题:编写测试台,在wr_clk=100MHz, rd_clk=33MHz下测试异步FIFO,连续写入1000个数据然后读出验证。

3. 分析题:计算在wr_clk=200MHz, rd_clk=150MHz, ADDR_WIDTH=4的情况下,由于同步延迟导致的最大吞吐率损失。

4. 设计题:实现一个带错误注入的异步FIFO测试台:随机在同步器中注入1周期延迟,验证FIFO的保守性保证。

5. 思考题:在ASIC流程中,如何对异步FIFO的跨域路径做时序约束?SDC中应该使用什么命令?

🏆 成就解锁:FIFO架构师

🎯 实现了完整的异步FIFO

📍 里程碑:掌握了异步FIFO的全部设计细节

💡 下一步:异步FIFO验证