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

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

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

Quick Start

  • 准备环境:安装Vivado 2024.2或更高版本(或Quartus Prime 24.x),确保支持SystemVerilog-2012。
  • 创建工程:新建RTL工程,目标器件选Xilinx Artix-7 XC7A35T或Intel Cyclone 10 LP,时钟频率示例:写时钟200 MHz,读时钟100 MHz。
  • 编写异步FIFO顶层模块:实例化双端口RAM、写指针同步器、读指针同步器、空/满标志生成逻辑。
  • 编写testbench:模拟写时钟域连续写入256个数据,读时钟域以半速率读出,检查数据完整性。
  • 运行行为仿真:观察空/满标志翻转时刻,确认无亚稳态传播导致的数据错误。
  • 综合与实现:检查资源报告(LUT/FF/BRAM),确认Fmax满足写时钟200 MHz要求。
  • 上板验证(可选):用ILA抓取写/读指针差值,在200 MHz写/100 MHz读下连续运行1小时无数据丢失。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35TBRAM 18Kb块,支持双端口Intel Cyclone 10 LP / Lattice ECP5
EDA版本Vivado 2024.2支持SystemVerilog-2012,综合优化好Quartus Prime 24.x / Synplify Premier
仿真器Vivado Simulator 2024.2内置,支持波形对比ModelSim SE-64 2024.2 / Verilator 5.0
写时钟频率200 MHz(示例)典型高速写场景按实际需求调整
读时钟频率100 MHz(示例)典型慢读场景按实际需求调整
接口依赖无外部IP纯RTL实现可使用XPM_FIFO宏(但本设计自研)
约束文件XDC/SDC需约束两个时钟域、异步路径false_pathSDC for Quartus

目标与验收标准

  • 功能点异步FIFO正确实现空/满标志,无亚稳态传播导致的数据丢失或重复。
  • 性能指标:写时钟200 MHz下Fmax ≥ 200 MHz,读时钟100 MHz下Fmax ≥ 100 MHz,资源消耗≤ 2个BRAM18K + 200 LUT + 200 FF。
  • 验收方式:仿真中写入256个递增数据,读出后逐字比对,无错误;上板后ILA抓取指针差值,连续运行1小时无数据丢失。

实施步骤

1. 工程结构与顶层模块

创建工程目录结构:src/rtl/ 存放RTL,src/tb/ 存放testbench,src/constr/ 存放约束文件。顶层模块命名为 async_fifo,参数化深度 DEPTH、数据宽度 DATA_WIDTH

// async_fifo.v
module async_fifo #(
    parameter DEPTH      = 16,          // FIFO深度,必须为2的幂
    parameter DATA_WIDTH = 8            // 数据位宽
)(
    input  wire                wr_clk,
    input  wire                wr_rst_n,
    input  wire                wr_en,
    input  wire [DATA_WIDTH-1:0] wr_data,
    output wire                full,

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

    localparam PTR_WIDTH = $clog2(DEPTH) + 1; // 指针位宽,多1位用于满/空判断

    wire [PTR_WIDTH-1:0] wr_ptr, rd_ptr;
    wire [PTR_WIDTH-1:0] wr_ptr_gray, rd_ptr_gray;
    wire [PTR_WIDTH-1:0] wr_ptr_sync, rd_ptr_sync;

    // 双端口RAM实例化
    dp_ram #(
        .DEPTH(DEPTH),
        .DATA_WIDTH(DATA_WIDTH)
    ) u_ram (
        .wr_clk(wr_clk),
        .wr_en(wr_en),
        .wr_addr(wr_ptr[PTR_WIDTH-2:0]),
        .wr_data(wr_data),
        .rd_clk(rd_clk),
        .rd_en(rd_en),
        .rd_addr(rd_ptr[PTR_WIDTH-2:0]),
        .rd_data(rd_data)
    );

    // 写指针生成
    ptr_gen #(
        .PTR_WIDTH(PTR_WIDTH)
    ) u_wr_ptr (
        .clk(wr_clk),
        .rst_n(wr_rst_n),
        .inc(wr_en),
        .ptr(wr_ptr)
    );

    // 读指针生成
    ptr_gen #(
        .PTR_WIDTH(PTR_WIDTH)
    ) u_rd_ptr (
        .clk(rd_clk),
        .rst_n(rd_rst_n),
        .inc(rd_en),
        .ptr(rd_ptr)
    );

    // 二进制转格雷码
    bin2gray #(
        .WIDTH(PTR_WIDTH)
    ) u_wr_b2g (
        .bin(wr_ptr),
        .gray(wr_ptr_gray)
    );

    bin2gray #(
        .WIDTH(PTR_WIDTH)
    ) u_rd_b2g (
        .bin(rd_ptr),
        .gray(rd_ptr_gray)
    );

    // 同步器:写指针格雷码同步到读时钟域
    sync_2ff #(
        .WIDTH(PTR_WIDTH)
    ) u_sync_wr (
        .clk(rd_clk),
        .rst_n(rd_rst_n),
        .din(wr_ptr_gray),
        .dout(wr_ptr_sync)
    );

    // 同步器:读指针格雷码同步到写时钟域
    sync_2ff #(
        .WIDTH(PTR_WIDTH)
    ) u_sync_rd (
        .clk(wr_clk),
        .rst_n(wr_rst_n),
        .din(rd_ptr_gray),
        .dout(rd_ptr_sync)
    );

    // 空/满标志生成
    flag_gen #(
        .PTR_WIDTH(PTR_WIDTH)
    ) u_flag (
        .wr_clk(wr_clk),
        .wr_rst_n(wr_rst_n),
        .wr_ptr(wr_ptr),
        .rd_ptr_sync(rd_ptr_sync),
        .full(full),
        .rd_clk(rd_clk),
        .rd_rst_n(rd_rst_n),
        .rd_ptr(rd_ptr),
        .wr_ptr_sync(wr_ptr_sync),
        .empty(empty)
    );

endmodule

逐行说明

  • 第1行:模块声明,使用parameter实现深度和位宽可配置。
  • 第2-3行:参数DEPTH必须为2的幂,因为指针是二进制循环;DATA_WIDTH可任意。
  • 第4-14行:端口列表,包含写时钟域和读时钟域的所有信号。
  • 第16行:计算指针位宽,多1位用于区分满与空(当指针二进制最高位不同且其余位相同时为满,完全相同时为空)。
  • 第18-22行:内部连线声明,包括二进制指针、格雷码指针、同步后的指针。
  • 第24-37行:实例化双端口RAM,写地址使用wr_ptr的低PTR_WIDTH-1位,读地址使用rd_ptr的低PTR_WIDTH-1位。
  • 第39-47行:实例化写指针生成器,每个写时钟上升沿且wr_en有效时递增。
  • 第49-57行:实例化读指针生成器,每个读时钟上升沿且rd_en有效时递增。
  • 第59-67行:将二进制指针转换为格雷码,减少跨时钟域翻转位。
  • 第69-77行:写指针格雷码通过两级触发器同步到读时钟域。
  • 第79-87行:读指针格雷码通过两级触发器同步到写时钟域。
  • 第89-101行:实例化空/满标志生成模块,在各自时钟域内比较指针。

2. 关键子模块实现

子模块包括:双端口RAM(dp_ram)、指针生成器(ptr_gen)、二进制转格雷码(bin2gray)、两级同步器(sync_2ff)、标志生成器(flag_gen)。以下给出核心代码。

// ptr_gen.v
module ptr_gen #(
    parameter PTR_WIDTH = 4
)(
    input  wire                clk,
    input  wire                rst_n,
    input  wire                inc,
    output reg  [PTR_WIDTH-1:0] ptr
);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            ptr <= 0;
        else if (inc)
            ptr <= ptr + 1'b1;
    end

endmodule

逐行说明

  • 第1-2行:参数化指针位宽,与顶层PTR_WIDTH一致。
  • 第3-8行:端口声明,inc为递增使能。
  • 第10-15行:同步复位,复位时指针清零;inc有效时指针加1,自然溢出实现循环。
// bin2gray.v
module bin2gray #(
    parameter WIDTH = 4
)(
    input  wire [WIDTH-1:0] bin,
    output wire [WIDTH-1:0] gray
);

    assign gray = (bin >> 1) ^ bin;

endmodule

逐行说明

  • 第1-2行:参数WIDTH定义位宽。
  • 第3-6行:格雷码公式:gray = bin右移1位异或bin,组合逻辑实现。
// sync_2ff.v
module sync_2ff #(
    parameter WIDTH = 4
)(
    input  wire                clk,
    input  wire                rst_n,
    input  wire [WIDTH-1:0]   din,
    output wire [WIDTH-1:0]   dout
);

    reg [WIDTH-1:0] sync_reg1, 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 <= din;
            sync_reg2 <= sync_reg1;
        end
    end

    assign dout = sync_reg2;

endmodule

逐行说明

  • 第1-2行:参数WIDTH定义同步器位宽。
  • 第3-8行:端口声明,din为异步输入,dout为同步输出。
  • 第10行:两级触发器寄存。
  • 第12-18行:复位时清零;每个时钟沿将din打入sync_reg1,再将sync_reg1打入sync_reg2。
  • 第20行:输出第二级寄存器的值,有效降低亚稳态概率。
// flag_gen.v
module flag_gen #(
    parameter PTR_WIDTH = 4
)(
    input  wire                wr_clk,
    input  wire                wr_rst_n,
    input  wire [PTR_WIDTH-1:0] wr_ptr,
    input  wire [PTR_WIDTH-1:0] rd_ptr_sync,
    output reg                 full,

    input  wire                rd_clk,
    input  wire                rd_rst_n,
    input  wire [PTR_WIDTH-1:0] rd_ptr,
    input  wire [PTR_WIDTH-1:0] wr_ptr_sync,
    output reg                 empty
);

    // 满标志:写时钟域,比较wr_ptr与同步后的rd_ptr_sync
    always @(posedge wr_clk or negedge wr_rst_n) begin
        if (!wr_rst_n)
            full <= 1'b0;
        else begin
            // 满条件:wr_ptr与rd_ptr_sync的最高位不同,其余位相同
            if ((wr_ptr[PTR_WIDTH-1] != rd_ptr_sync[PTR_WIDTH-1]) &&
                (wr_ptr[PTR_WIDTH-2:0] == rd_ptr_sync[PTR_WIDTH-2:0]))
                full <= 1'b1;
            else
                full <= 1'b0;
        end
    end

    // 空标志:读时钟域,比较rd_ptr与同步后的wr_ptr_sync
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n)
            empty <= 1'b1;
        else begin
            // 空条件:rd_ptr与wr_ptr_sync完全相等
            if (rd_ptr == wr_ptr_sync)
                empty <= 1'b1;
            else
                empty <= 1'b0;
        end
    end

endmodule

逐行说明

  • 第1-2行:参数PTR_WIDTH。
  • 第3-12行:端口声明,分为写时钟域和读时钟域两部分。
  • 第14-25行:满标志生成逻辑,在写时钟域内比较写指针与同步后的读指针。满条件:最高位不同(表示写指针多循环了一圈),且低位相同。
  • 第27-38行:空标志生成逻辑,在读时钟域内比较读指针与同步后的写指针。空条件:完全相等。

3. 时序约束与CDC处理

在XDC文件中,将跨时钟域路径设置为false_path,避免工具对同步器路径进行时序分析。

# async_fifo.xdc
# 写时钟域到读时钟域的路径(同步器输入)设为false_path
set_false_path -from [get_clocks wr_clk] -to [get_clocks rd_clk]
# 读时钟域到写时钟域的路径(同步器输入)设为false_path
set_false_path -from [get_clocks rd_clk] -to [get_clocks wr_clk]
# 对同步器输出路径做正常时序分析(默认即可)

逐行说明

  • 第1行:注释,说明约束文件用途。
  • 第2-3行:将写时钟域到读时钟域的所有路径设为false_path,因为同步器输入可能来自异步时钟域,无需满足建立/保持时间。
  • 第4-5行:将读时钟域到写时钟域的路径设为false_path。
  • 第6行:同步器输出到后续逻辑的路径由工具自动分析,无需额外约束。

4. 验证与仿真

编写testbench,模拟写时钟200 MHz、读时钟100 MHz,写入256个递增数据,读出后比对。

// tb_async_fifo.v
`timescale 1ns / 1ps

module tb_async_fifo;

    parameter DEPTH = 16;
    parameter DATA_WIDTH = 8;

    reg wr_clk, wr_rst_n;
    reg rd_clk, rd_rst_n;
    reg wr_en, rd_en;
    reg [DATA_WIDTH-1:0] wr_data;
    wire [DATA_WIDTH-1:0] rd_data;
    wire full, empty;

    async_fifo #(
        .DEPTH(DEPTH),
        .DATA_WIDTH(DATA_WIDTH)
    ) uut (
        .wr_clk(wr_clk),
        .wr_rst_n(wr_rst_n),
        .wr_en(wr_en),
        .wr_data(wr_data),
        .full(full),
        .rd_clk(rd_clk),
        .rd_rst_n(rd_rst_n),
        .rd_en(rd_en),
        .rd_data(rd_data),
        .empty(empty)
    );

    // 写时钟生成:200 MHz
    initial wr_clk = 0;
    always #2.5 wr_clk = ~wr_clk;

    // 读时钟生成:100 MHz
    initial rd_clk = 0;
    always #5 rd_clk = ~rd_clk;

    // 写测试数据
    integer i;
    reg [DATA_WIDTH-1:0] expected;
    reg [7:0] error_count;

    initial begin
        // 复位
        wr_rst_n = 0;
        rd_rst_n = 0;
        wr_en = 0;
        rd_en = 0;
        wr_data = 0;
        error_count = 0;
        #20;
        wr_rst_n = 1;
        rd_rst_n = 1;
        #10;

        // 写入256个数据(16深度FIFO会多次满/空翻转)
        for (i = 0; i < 256; i = i + 1) begin
            @(posedge wr_clk);
            while (full) @(posedge wr_clk); // 等待非满
            wr_en <= 1;
            wr_data <= i;
            @(posedge wr_clk);
            wr_en <= 0;
        end

        // 等待所有数据被读出
        #100;
        $finish;
    end

    // 读数据并比对
    always @(posedge rd_clk) begin
        if (!rd_rst_n) begin
            rd_en <= 0;
            expected <= 0;
        end else begin
            if (!empty) begin
                rd_en <= 1;
                @(posedge rd_clk);
                rd_en <= 0;
                if (rd_data !== expected) begin
                    $display("ERROR: expected %d, got %d", expected, rd_data);
                    error_count = error_count + 1;
                end
                expected <= expected + 1;
            end else begin
                rd_en <= 0;
            end
        end
    end

    // 结束检查
    initial begin
        #500;
        if (error_count == 0)
            $display("TEST PASSED: All data matched.");
        else
            $display("TEST FAILED: %d errors.", error_count);
    end

endmodule

逐行说明

  • 第1行:timescale设置1ns精度。
  • 第3-5行:模块声明,参数与顶层一致。
  • 第7-13行:信号声明。
  • 第15-30行:实例化UUT。
  • 第32-33行:写时钟周期5ns(200 MHz)。
  • 第35-36行:读时钟周期10ns(100 MHz)。
  • 第38-41行:变量声明。
  • 第43-58行:初始化过程,复位后写256个数据,每次写前等待full为低。
  • 第60-76行:读过程,每次读前等待empty为低,读出后与期望值比对,错误计数。
  • 第78-83行:结束检查,打印结果。

常见坑与排查

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

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
91819.25W3.99W3.67W
分享:
成电国芯FPGA赛事课即将上线
跨时钟域设计:异步FIFO深度计算与Verilog实现2026版
跨时钟域设计:异步FIFO深度计算与Verilog实现2026版上一篇
国产FPGA在工业以太网(EtherCAT)控制器中的2026年替代方案下一篇
国产FPGA在工业以太网(EtherCAT)控制器中的2026年替代方案
相关文章
总数:944
2026年硬件技术前瞻:从CXL 3.0到3D-IC,FPGA与芯片设计的六大演进脉络

2026年硬件技术前瞻:从CXL 3.0到3D-IC,FPGA与芯片设计的六大演进脉络

作为成电国芯FPGA云课堂的特邀观察者,我们持续追踪着硬件技术领域的每一…
技术分享
14天前
0
0
72
0
FPGA与GPU边缘AI推理选型指南:性能、功耗与开发实践

FPGA与GPU边缘AI推理选型指南:性能、功耗与开发实践

QuickStart:快速了解选型要点在边缘AI推理场景中,FPGA与…
技术分享
7天前
0
0
15
0
Vivado Block Design与RTL设计协同开发实战指南

Vivado Block Design与RTL设计协同开发实战指南

QuickStart(快速上手)创建Vivado工程,选择目标器件(如…
技术分享
9天前
0
0
21
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容