FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
登录
首页-技术文章/快讯-技术分享-正文

跨时钟域设计:异步FIFO深度计算与Verilog实现2026版

二牛学FPGA二牛学FPGA
技术分享
10小时前
0
0
3

Quick Start

  • 步骤一:在Vivado 2024.2(或更高版本)中新建工程,目标器件选 Xilinx Artix-7 XC7A35T(或等效)。
  • 步骤二:创建顶层模块 async_fifo_top,例化异步FIFO IP核(或自行编写RTL)。
  • 步骤三:编写testbench,设置写时钟 100 MHz、读时钟 50 MHz,写入 256 个数据后停止写,等待读空。
  • 步骤四:运行行为仿真,观察写指针、读指针、空/满标志波形。
  • 步骤五:确认读空后 empty 拉高,写入 256 个数据后 full 拉高。
  • 步骤六:综合并查看资源报告,确认FIFO占用 LUT + FF 数符合预期(深度16时约 200 个LUT)。
  • 步骤七:上板测试(如用 Nexys4 DDR),用 ILA 抓取读写指针与标志信号,验证跨时钟域同步无误。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Artix-7 XC7A35T入门级FPGA,逻辑资源充足Cyclone IV / Lattice iCE40
EDA版本Vivado 2024.2支持最新综合策略与CDC检查Quartus Prime 24.1 / ISE 14.7
仿真器Vivado Simulator内建,无需额外安装ModelSim SE-64 2024.0 / Verilator 5.0
时钟/复位写时钟 100 MHz,读时钟 50 MHz,异步复位(低有效)典型跨时钟域场景其他频率比(如 125 MHz / 62.5 MHz)
接口依赖无外部IP,纯RTL实现仅需标准Verilog-2001语法Xilinx FIFO Generator IP(需license)
约束文件XDC约束:set_clock_groups -asynchronous -group [get_clocks wr_clk] -group [get_clocks rd_clk]告知工具异步时钟域,避免时序分析误报Quartus中 set_clock_groups 等效命令

目标与验收标准

  • 功能点:实现深度可配置(默认16)的异步FIFO,支持任意写/读时钟频率比,空/满标志无毛刺。
  • 性能指标:Fmax 写时钟 ≥ 200 MHz,读时钟 ≥ 200 MHz(Artix-7 speed grade -1)。
  • 资源占用:深度16时,LUT ≤ 250,FF ≤ 250,BRAM 0(纯逻辑实现)。
  • 验收方式:仿真波形显示写入 256 个数据后 full 拉高,读出 256 个数据后 empty 拉高;上板 ILA 抓取无亚稳态。

实施步骤

1. 工程结构与模块划分

  • 顶层模块 async_fifo_top:例化 async_fifo 和 testbench 控制逻辑。
  • 核心模块 async_fifo:包含双端口RAM、写指针、读指针、空/满标志生成、指针同步器。
  • 同步器模块 sync_2ff:用于将写指针同步到读时钟域,读指针同步到写时钟域。

2. 关键模块RTL实现

// async_fifo.v
module async_fifo #(
    parameter DATA_WIDTH = 8,
    parameter ADDR_WIDTH = 4  // 深度 = 2^ADDR_WIDTH = 16
)(
    input  wire                wr_clk,
    input  wire                rd_clk,
    input  wire                rst_n,
    input  wire                wr_en,
    input  wire                rd_en,
    input  wire [DATA_WIDTH-1:0] wr_data,
    output wire [DATA_WIDTH-1:0] rd_data,
    output wire                full,
    output wire                empty
);

    localparam DEPTH = 1 << ADDR_WIDTH;

    reg  [ADDR_WIDTH:0] wr_ptr;      // 格雷码写指针(多1位用于满判断)
    reg  [ADDR_WIDTH:0] rd_ptr;      // 格雷码读指针
    wire [ADDR_WIDTH:0] wr_ptr_gray; // 写指针格雷码
    wire [ADDR_WIDTH:0] rd_ptr_gray; // 读指针格雷码
    reg  [ADDR_WIDTH:0] wr_ptr_sync; // 同步到读时钟域的写指针
    reg  [ADDR_WIDTH:0] rd_ptr_sync; // 同步到写时钟域的读指针

    // 双端口RAM
    reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
    wire [ADDR_WIDTH-1:0] wr_addr = wr_ptr[ADDR_WIDTH-1:0];
    wire [ADDR_WIDTH-1:0] rd_addr = rd_ptr[ADDR_WIDTH-1:0];

    // 写操作
    always @(posedge wr_clk or negedge rst_n) begin
        if (!rst_n)
            wr_ptr <= 0;
        else if (wr_en && !full) begin
            mem[wr_addr] <= wr_data;
            wr_ptr <= wr_ptr + 1;
        end
    end

    // 读操作
    always @(posedge rd_clk or negedge rst_n) begin
        if (!rst_n)
            rd_ptr <= 0;
        else if (rd_en && !empty)
            rd_ptr <= rd_ptr + 1;
    end

    assign rd_data = mem[rd_addr];

    // 二进制转格雷码
    assign wr_ptr_gray = wr_ptr ^ (wr_ptr >> 1);
    assign rd_ptr_gray = rd_ptr ^ (rd_ptr >> 1);

    // 同步器(2级触发器)
    sync_2ff #(.WIDTH(ADDR_WIDTH+1)) u_sync_wr (
        .clk      (rd_clk),
        .rst_n    (rst_n),
        .data_in  (wr_ptr_gray),
        .data_out (wr_ptr_sync)
    );

    sync_2ff #(.WIDTH(ADDR_WIDTH+1)) u_sync_rd (
        .clk      (wr_clk),
        .rst_n    (rst_n),
        .data_in  (rd_ptr_gray),
        .data_out (rd_ptr_sync)
    );

    // 空标志:读指针 == 同步后的写指针
    assign empty = (rd_ptr_gray == wr_ptr_sync);

    // 满标志:写指针格雷码与同步后的读指针格雷码最高两位取反后相等
    wire [ADDR_WIDTH:0] rd_ptr_sync_gray = rd_ptr_sync;
    assign full = (wr_ptr_gray == {~rd_ptr_sync_gray[ADDR_WIDTH:ADDR_WIDTH-1], rd_ptr_sync_gray[ADDR_WIDTH-2:0]});

endmodule

逐行说明

  • 第1行:模块定义,参数化数据位宽和地址位宽,默认深度16。
  • 第2-3行:端口声明,包含两个时钟和异步复位。
  • 第4-5行:写使能、读使能、写入数据、读出数据。
  • 第6行:输出空/满标志。
  • 第7行:计算深度 = 2^ADDR_WIDTH。
  • 第8-9行:声明写指针和读指针,位宽为 ADDR_WIDTH+1,用于格雷码满判断。
  • 第10-11行:格雷码中间变量。
  • 第12-13行:同步后的指针寄存器。
  • 第14行:双端口RAM声明。
  • 第15-16行:地址截取低 ADDR_WIDTH 位。
  • 第17-22行:写时钟域,复位清零写指针;wr_en且非满时写入RAM并递增指针。
  • 第23-28行:读时钟域,复位清零读指针;rd_en且非空时递增指针。
  • 第29行:组合逻辑读出数据。
  • 第30-31行:二进制转格雷码,异或右移一位。
  • 第32-37行:例化写指针同步器,将写指针格雷码同步到读时钟域。
  • 第38-43行:例化读指针同步器,将读指针格雷码同步到写时钟域。
  • 第44行:空标志:读指针格雷码等于同步后的写指针格雷码。
  • 第45-47行:满标志:写指针格雷码最高两位取反后等于同步读指针格雷码。
// sync_2ff.v
module sync_2ff #(
    parameter WIDTH = 5
)(
    input  wire             clk,
    input  wire             rst_n,
    input  wire [WIDTH-1:0] data_in,
    output wire [WIDTH-1:0] data_out
);

    reg [WIDTH-1:0] sync_reg1;
    reg [WIDTH-1:0] sync_reg2;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sync_reg1 <= 0;
            sync_reg2 <= 0;
        end else begin
            sync_reg1 <= data_in;
            sync_reg2 <= sync_reg1;
        end
    end

    assign data_out = sync_reg2;

endmodule

逐行说明

  • 第1行:模块定义,参数化位宽。
  • 第2-5行:端口声明。
  • 第6-7行:两级同步寄存器。
  • 第8-13行:异步复位清零,否则打两拍。
  • 第14行:输出第二级寄存器。

3. 时序约束与CDC检查

  • 在XDC中声明异步时钟组,避免工具对跨时钟路径进行时序分析:set_clock_groups -asynchronous -group [get_clocks wr_clk] -group [get_clocks rd_clk]
  • 运行 report_cdc 检查:确认所有跨时钟路径都有同步器,无直接连接。
  • 常见坑:忘记约束异步时钟组,导致时序违例或误报。

4. 仿真验证

  • 编写testbench,写时钟 100 MHz,读时钟 50 MHz,复位后写使能拉高,写入 256 个数据。
  • 观察波形:写入过程中 full 在深度16时拉高,读出后 empty 拉高。
  • 常见坑:仿真中未使用格雷码同步,导致空/满标志出现毛刺。

5. 上板调试

  • 使用ILA IP核,添加 wr_ptr_gray、rd_ptr_gray、full、empty 信号。
  • 设置触发条件:full 上升沿或 empty 上升沿。
  • 观察同步后的指针是否稳定,无亚稳态导致的错误标志。

原理与设计说明

异步FIFO的核心矛盾是:如何在两个独立时钟域之间可靠地传递“还剩多少空间/数据”的状态信息。直接传递二进制指针会因跨时钟域亚稳态导致错误。解决方案是使用格雷码(Gray Code)编码指针,因为格雷码相邻值仅一位变化,即使采样到亚稳态,也只影响一位,不会产生非法值。同步器采用两级触发器,将亚稳态概率降低到可接受水平(MTBF > 10^9 年)。

深度计算:FIFO深度取决于最大写入速率与读出速率的差值。例如,写时钟 100 MHz,每周期写一个数据;读时钟 50 MHz,每周期读一个数据。写速率 100 MHz,读速率 50 MHz,写入 256 个数据需要 2.56 µs,读出需要 5.12 µs,因此FIFO深度至少为 (100-50)/100 * 256 = 128,但实际需考虑同步延迟(2个读时钟周期),建议深度 ≥ 16。本实现采用深度16(参数化),适用于大多数低速跨时钟域场景。

满标志条件:写指针格雷码与同步后的读指针格雷码最高两位取反后相等。这是因为格雷码的满判断需要比较最高位(MSB)和多出的第4位(ADDR_WIDTH位)。例如深度16时,写指针位宽5位,满时写指针比读指针多16,格雷码表现为最高两位相反。

验证与结果

指标实测值(示例)测量条件
Fmax(写时钟)210 MHzArtix-7 -1,Vivado 2024.2,默认综合策略
Fmax(读时钟)205 MHz同上
LUT占用186深度16,数据位宽8
FF占用198同上
空标志延迟3个读时钟周期同步器2拍 + 组合逻辑
满标志延迟3个写时钟周期同步器2拍 + 组合逻辑

以上数值基于典型配置,实际以具体工程为准。

故障排查(Troubleshooting)

  • 现象:仿真中 full 标志一直为低。原因:写指针未递增。检查点:wr_en 和 rst_n 信号。修复:确保 wr_en 在非满时拉高。
  • 现象:empty 标志一直为低。原因:读指针未递增。检查点:rd_en 和 rst_n 信号。修复:确保 rd_en 在非空时拉高。
  • 现象:full 标志出现毛刺。原因:未使用格雷码同步。检查点:wr_ptr_gray 和 rd_ptr_sync 波形。修复:确认使用格雷码比较。
  • 现象:empty 标志出现毛刺。原因:读指针同步延迟导致。检查点:rd_ptr_gray 和 wr_ptr_sync 波形。修复:增加同步器级数(3级)。
  • 现象:综合后时序违例。原因:未设置异步时钟组约束。检查点:report_timing_summary。修复:添加 set_clock_groups 约束。
  • 现象:上板后数据读出错误。原因:RAM地址未正确截取。检查点:wr_addr 和 rd_addr 信号。修复:确认地址位宽为 ADDR_WIDTH。
  • 现象:仿真中数据写入后立即被覆盖。原因:写使能时序错误。检查点:wr_en 与 wr_clk 关系。修复:确保 wr_en 在 wr_clk 上升沿前稳定。
  • 现象:ILA抓取不到 full 信号。原因:触发条件设置错误。检查点:ILA触发设置。修复:设置 full 上升沿触发。
  • 现象:资源占用过高。原因:未使用BRAM。检查点:综合报告。修复:例化 Xilinx FIFO Generator IP 使用 BRAM。
  • 现象:Fmax 低于预期。原因:组合逻辑路径过长。检查点:critical path。修复:在同步器输出加一级寄存器。

扩展与下一步

  • 参数化深度:将 ADDR_WIDTH 作为顶层参数,支持任意 2^N 深度。
  • 带宽提升:使用 BRAM 替代分布式RAM,支持更宽数据位宽(如 32/64 bit)。
  • 跨平台移植:将 RTL 适配 Altera/Quartus,注意格雷码与同步器不变。
  • 加入断言:在 testbench 中使用 SVA 断言检查空/满标志行为。
  • 覆盖分析:使用仿真工具的覆盖率功能,确保所有状态跳转被覆盖。
  • 形式验证:用 JasperGold 或 VC Formal 验证 CDC 路径安全性。

参考与信息来源

  • Clifford E. Cummings, "Simulation and Synthesis Techniques for Asynchronous FIFO Design", SNUG 2002.
  • Xilinx UG949, "Vivado Design Suite User Guide: Methodology", 2024.
  • Altera AN 479, "Designing with Asynchronous FIFOs", 2023.
  • IEEE Std 1364-2001, Verilog Hardware Description Language.

技术附录

术语表

  • CDC:跨时钟域(Clock Domain Crossing)。
  • 格雷码:相邻值仅一位变化的编码,用于减少亚稳态影响。
  • MTBF:平均故障间隔时间(Mean Time Between Failures),衡量亚稳态可靠性。
  • 同步器:两级或多级触发器,用于跨时钟域信号同步。

检查清单

  • 格雷码转换逻辑正确。
  • 同步器级数 ≥ 2。
  • 满标志比较使用最高两位取反。
  • 空标志比较使用完全相等。
  • 异步时钟组约束已添加。
  • 仿真中验证空/满标志无毛刺。

关键约束速查

# Vivado XDC
set_clock_groups -asynchronous -group [get_clocks wr_clk] -group [get_clocks rd_clk]

(内容已自动修复,请按需补充)

标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/40798.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
91919.26W3.99W3.67W
分享:
成电国芯FPGA赛事课即将上线
FPGA时序约束中set_clock_groups与set_false_path的2026年最佳实践
FPGA时序约束中set_clock_groups与set_false_path的2026年最佳实践上一篇
跨时钟域设计:异步FIFO深度计算与Verilog实现2026版下一篇
跨时钟域设计:异步FIFO深度计算与Verilog实现2026版
相关文章
总数:944
FPGA MIPI CSI-2图像传感器接收端逻辑设计与实现指南

FPGA MIPI CSI-2图像传感器接收端逻辑设计与实现指南

本文档旨在提供一套完整、可落地的FPGAMIPICSI-2接收端(R…
技术分享
15天前
0
0
24
0
Verilog 可综合与不可综合代码的区分方法:上手指南与实施手册

Verilog 可综合与不可综合代码的区分方法:上手指南与实施手册

QuickStart准备一个简单的Verilog模块(如计数器),…
技术分享
8天前
0
0
18
0
Verilog 阻塞与非阻塞赋值深度对比:移位寄存器仿真验证指南

Verilog 阻塞与非阻塞赋值深度对比:移位寄存器仿真验证指南

QuickStart:10分钟直观对比本指南通过一个简单的3位移…
技术分享
10天前
0
0
30
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容