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

异步FIFO设计指南:基于双口RAM的Verilog实现与调试技巧

二牛学FPGA二牛学FPGA
技术分享
1天前
0
0
11

Quick Start

  • 准备环境:Vivado 2024.2 / Quartus Prime Pro 24.3,仿真器用 Vivado Simulator 或 ModelSim SE-64 2024.1。
  • 创建工程:新建 RTL 项目,添加双口 RAM 例化(Xilinx 用 Block Memory Generator,Intel 用 ALTSYNCRAM)。
  • 编写异步 FIFO 顶层:例化双口 RAM + 写指针(wptr)、读指针(rptr)、格雷码转换、同步器。
  • 编写 testbench:写时钟 100 MHz,读时钟 50 MHz,写使能连续,读使能随机,深度 16。
  • 运行行为仿真:观察 full、empty 标志正确,数据写入后读出无丢失。
  • 综合实现:检查资源(LUT/FF/BRAM),确保 Fmax 满足写时钟 > 200 MHz。
  • 上板验证:用 ChipScope / Signal Tap 捕获 full/empty 翻转时刻,验证数据完整性。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T / Intel Cyclone 10 LP主流低成本 FPGA,BRAM 资源充足Xilinx Kintex-7 / Intel Arria 10
EDA 版本Vivado 2024.2 / Quartus Prime Pro 24.32026 年稳定版本,支持最新 IP 核Vivado 2023.1 / Quartus 23.1
仿真器Vivado Simulator / ModelSim SE-64 2024.1支持 SystemVerilog 断言Questa / VCS
时钟/复位写时钟 100 MHz,读时钟 50 MHz;异步复位高有效典型跨时钟域场景写 200 MHz / 读 100 MHz
接口依赖双口 RAM IP(简单双口模式)写端口 A,读端口 B,独立时钟分布式 RAM(深度 ≤ 16)
约束文件XDC:set_clock_groups -asynchronous -group [get_clocks wr_clk] -group [get_clocks rd_clk]显式声明异步时钟域,避免时序分析误报SDC 等效语句

目标与验收标准

功能点:异步 FIFO 支持同时读写,full 和 empty 标志正确,数据无丢失/无重复。

性能指标:写时钟 Fmax ≥ 200 MHz(示例值,以实际器件为准),读时钟 Fmax ≥ 150 MHz。

资源:BRAM 1 块(深度 16,位宽 8),LUT ≤ 50,FF ≤ 40。

验收方式:仿真波形显示 full 在写指针领先读指针一圈时拉高,empty 在指针相等时拉高;上板用 ChipScope 抓取任意 1000 个写周期,读出数据与写入序列一致。

实施步骤

工程结构与顶层 RTL

创建以下文件结构:

  • async_fifo_top.v // 顶层,例化 RAM 与指针逻辑
  • dual_port_ram.v // 双口 RAM 封装(或 IP 核 wrapper)
  • gray_counter.v // 格雷码计数器(写/读共用)
  • sync_2ff.v // 双级同步器
  • tb_async_fifo.v // testbench
// async_fifo_top.v 示例(简化)
module async_fifo_top #(
    parameter DEPTH = 16,
    parameter WIDTH = 8
) (
    input  wire             wr_clk, rd_clk, rst_n,
    input  wire             wr_en, rd_en,
    input  wire [WIDTH-1:0] wr_data,
    output wire [WIDTH-1:0] rd_data,
    output wire             full, empty
);
    localparam PTR_WIDTH = $clog2(DEPTH);
    wire [PTR_WIDTH-1:0] waddr, raddr;
    wire [PTR_WIDTH-1:0] wgray, rgray;
    wire [PTR_WIDTH-1:0] wgray_sync, rgray_sync;

    // 例化双口 RAM
    dual_port_ram #(.DEPTH(DEPTH), .WIDTH(WIDTH)) u_ram (
        .clk_a(wr_clk), .clk_b(rd_clk),
        .addr_a(waddr), .addr_b(raddr),
        .din_a(wr_data), .dout_b(rd_data),
        .we_a(wr_en)
    );

    // 例化写指针计数器(格雷码)
    gray_counter #(.WIDTH(PTR_WIDTH)) u_wptr (
        .clk(wr_clk), .rst_n(rst_n),
        .en(wr_en && !full),
        .gray(wgray)
    );

    // 例化读指针计数器(格雷码)
    gray_counter #(.WIDTH(PTR_WIDTH)) u_rptr (
        .clk(rd_clk), .rst_n(rst_n),
        .en(rd_en && !empty),
        .gray(rgray)
    );

    // 例化同步器:写指针同步到读时钟域
    sync_2ff #(.WIDTH(PTR_WIDTH)) u_sync_w2r (
        .clk(rd_clk), .rst_n(rst_n),
        .async_in(wgray),
        .sync_out(wgray_sync)
    );

    // 例化同步器:读指针同步到写时钟域
    sync_2ff #(.WIDTH(PTR_WIDTH)) u_sync_r2w (
        .clk(wr_clk), .rst_n(rst_n),
        .async_in(rgray),
        .sync_out(rgray_sync)
    );

    // 组合逻辑产生 full(格雷码比较)
    assign full = (wgray_sync[PTR_WIDTH-1:PTR_WIDTH-2] != rgray[PTR_WIDTH-1:PTR_WIDTH-2]) &&
                  (wgray_sync[PTR_WIDTH-3:0] == rgray[PTR_WIDTH-3:0]);

    // 组合逻辑产生 empty(格雷码相等)
    assign empty = (rgray_sync == wgray);

    // 二进制地址转换(用于 RAM 寻址)
    assign waddr = wgray ^ (wgray >> 1);
    assign raddr = rgray ^ (rgray >> 1);

endmodule

逐行说明

  • 第 1 行:顶层模块声明,参数 DEPTH=16、WIDTH=8。
  • 第 2 行:例化双口 RAM,写端口时钟 wr_clk,读端口时钟 rd_clk。
  • 第 3 行:例化写指针计数器(格雷码),输出 wgray。
  • 第 4 行:例化读指针计数器,输出 rgray。
  • 第 5 行:例化同步器,将 wgray 同步到 rd_clk 域,将 rgray 同步到 wr_clk 域。
  • 第 6 行:组合逻辑产生 full(写指针格雷码同步后与读指针格雷码比较,高两位取反)。
  • 第 7 行:组合逻辑产生 empty(读指针格雷码同步后与写指针格雷码相等)。

关键模块:格雷码计数器

module gray_counter #(parameter WIDTH=4) (
    input  clk, rst_n, en,
    output reg [WIDTH-1:0] gray
);
    reg [WIDTH-1:0] binary;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            binary <= 0;
            gray   <= 0;
        end else if (en) begin
            binary <= binary + 1'b1;
            gray   <= (binary >> 1) ^ binary;
        end
    end
endmodule

逐行说明

  • 第 1 行:参数化宽度 WIDTH,默认 4 位(深度 16 需 4 位地址)。
  • 第 2 行:端口声明,en 为计数使能。
  • 第 3 行:内部二进制计数器 binary,用于生成格雷码。
  • 第 4 行:时序逻辑,异步复位低有效。
  • 第 5-7 行:复位时 binary 和 gray 清零。
  • 第 8-10 行:使能时 binary 加 1,gray 通过异或运算得到:gray[i] = binary[i] ^ binary[i+1]。

关键模块:双级同步器

module sync_2ff #(parameter WIDTH=4) (
    input  clk, rst_n,
    input  [WIDTH-1:0] async_in,
    output reg [WIDTH-1:0] sync_out
);
    reg [WIDTH-1:0] meta;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            meta     <= 0;
            sync_out <= 0;
        end else begin
            meta     <= async_in;
            sync_out <= meta;
        end
    end
endmodule

逐行说明

  • 第 1 行:参数化宽度,与格雷码宽度一致。
  • 第 2 行:异步输入 async_in,来自另一时钟域。
  • 第 3 行:meta 为第一级寄存器,用于采样异步输入,可能亚稳态。
  • 第 4 行:时序逻辑,复位清零。
  • 第 5-7 行:复位时两级寄存器清零。
  • 第 8-10 行:两级打拍,meta 采样 async_in,sync_out 采样 meta,降低亚稳态概率。

验证结果

指标测量值(示例)条件
写时钟 Fmax215 MHzArtix-7,速度等级 -1,时序约束宽松
读时钟 Fmax180 MHz同上
资源 LUT/FF/BRAM32 / 28 / 1深度 16,位宽 8,无流水线
数据吞吐100 MByte/s写时钟 100 MHz,连续写入
空满标志延迟2 个读时钟周期 + 2 个写时钟周期同步器延迟 + 比较逻辑

测量条件:Vivado 2024.2,Artix-7 XC7A35T-1CSG324C,时序约束仅声明异步时钟组,未添加额外 pipeline。

故障排查(Troubleshooting)

  • 现象 1:仿真中 full 一直为 0。原因:写指针同步后比较逻辑错误。检查:同步后的 wgray 是否等于 rgray 取反高两位。修复:确保比较公式为 full = (wgray_sync[WIDTH-1:WIDTH-2] != rgray[WIDTH-1:WIDTH-2]) && (wgray_sync[WIDTH-3:0] == rgray[WIDTH-3:0])。
  • 现象 2:empty 一直为 1。原因:读指针同步后与写指针比较时未考虑格雷码特性。检查:empty = (rgray_sync == wgray)。修复:确认同步后的 rgray_sync 与 wgray 位宽一致。
  • 现象 3:数据读出顺序错误。原因:写指针与读指针未对齐,或 RAM 地址映射错误。检查:写地址用 wptr(二进制),读地址用 rptr(二进制),确保 RAM 例化地址位宽正确。
  • 现象 4:上板后数据偶尔丢失。原因:同步器级数不足或时钟抖动。检查:将同步器改为 3 级(meta1, meta2, sync_out)。修复:增加一级寄存器。
  • 现象 5:综合报告显示大量异步路径。原因:未设置 set_clock_groups。检查:XDC 中是否声明异步时钟组。修复:添加约束。
  • 现象 6:Fmax 低于预期。原因:格雷码比较逻辑组合深度大。检查:综合报告中的最大路径。修复:在比较逻辑中插入一级流水线。
  • 现象 7:仿真中写使能无效时数据仍写入。原因:写使能未与写指针使能关联。检查:gray_counter 的 en 信号是否接 wr_en。修复:en = wr_en && !full。
  • 现象 8:读使能无效时数据被读出。原因:读使能未与读指针使能关联。检查:en = rd_en && !empty。修复:同上。

扩展与下一步

  • 参数化深度与位宽:将 DEPTH 和 WIDTH 作为顶层参数,自动计算指针位宽。
  • 带宽提升:使用“乒乓”双 FIFO 或增加数据位宽(如 32 位)提升吞吐。
  • 跨平台移植:将 Xilinx BRAM 例化替换为通用 RTL 双口 RAM,兼容 Intel/Lattice。
  • 加入断言:用 SystemVerilog 断言检查 full 时写使能无效、empty 时读使能无效。
  • 覆盖率驱动验证:用随机测试 + 功能覆盖点(full/empty 翻转次数、数据完整性)提高验证完备性。
  • 形式验证:用 JasperGold 或 VC Formal 证明 CDC 路径无亚稳态传播。

参考与信息来源

  • Clifford E. Cummings, “Simulation and Synthesis Techniques for Asynchronous FIFO Design”, SNUG 2002.
  • Xilinx UG901, “Vivado Design Suite User Guide: Synthesis”, 2024.
  • Intel AN 480, “Designing with Asynchronous FIFOs”, 2023.
  • IEEE Std 1364-2001, Verilog HDL.

技术附录

术语表

  • CDC:Clock Domain Crossing,跨时钟域。
  • 格雷码:每次只变化 1 位的二进制编码,用于 CDC 减少亚稳态影响。
  • 同步器:两级或多级寄存器链,用于采样异步信号。
  • BRAM:Block RAM,FPGA 内部专用存储器。
  • Fmax:最大工作频率,由时序分析报告给出。

检查清单

  • [ ] 格雷码计数器使能信号正确(写使能 && !full,读使能 && !empty)。
  • [ ] 同步器级数 ≥ 2,复位同步。
  • [ ] full 比较用同步后的 wgray,empty 比较用同步后的 rgray。
  • [ ] 双口 RAM 例化地址位宽 = $clog2(DEPTH)。
  • [ ] 约束文件声明异步时钟组。
  • [ ] 仿真验证 full/empty 翻转时刻,数据完整性。

关键约束速查

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

# Quartus SDC
set_clock_groups -asynchronous -group {wr_clk} -group {rd_clk}

逐行说明

  • 第 1 行:Vivado 约束,将 wr_clk 和 rd_clk 声明为异步组,工具不会分析跨时钟域路径时序。
  • 第 2 行:Quartus 等效约束,语法略有不同。
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/42189.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
1.06K20.57W4.05W3.67W
分享:
成电国芯FPGA赛事课即将上线
RISC-V FPGA软核在开源EDA工具链中的全流程实现指南(2026 Q2)
RISC-V FPGA软核在开源EDA工具链中的全流程实现指南(2026 Q2)上一篇
FPGA上部署轻量级YOLO模型的量化与加速实践指南(2026年Q2版)下一篇
FPGA上部署轻量级YOLO模型的量化与加速实践指南(2026年Q2版)
相关文章
总数:1.10K
国产FPGA在工业控制领域的应用设计与实现指南

国产FPGA在工业控制领域的应用设计与实现指南

QuickStart:快速体验国产FPGA在PLCIO扩展中的应用本…
技术分享
11天前
0
0
27
0
FPGA时序收敛实施指南:系统化分析与修复时序违例

FPGA时序收敛实施指南:系统化分析与修复时序违例

时序收敛是FPGA设计从功能仿真走向物理实现的关键环节,决定了设计能否在…
技术分享
22天前
0
0
40
0
基于FPGA的SPI Flash控制器:2026年高速读写时序设计

基于FPGA的SPI Flash控制器:2026年高速读写时序设计

QuickStart步骤1:准备硬件平台(如XilinxArtix-…
技术分享
6天前
0
0
21
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容