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

Verilog实战:2026年用双口RAM实现异步FIFO的常见调试技巧

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

Quick Start

  • 步骤1:准备开发环境(Vivado 2024.2 / Quartus Prime Pro 24.3+)并新建工程,目标器件选 Xilinx Artix-7 XC7A35T 或 Intel Cyclone 10 GX。
  • 步骤2:在工程中创建顶层模块 async_fifo_top,例化一个双口 RAM(XPM / ALTSYNCRAM)和两个格雷码计数器。
  • 步骤3:编写写时钟域逻辑:写指针 wptr 在 wclk 下递增,经两级同步器同步到 rclk 域得到 wptr_sync。
  • 步骤4:编写读时钟域逻辑:读指针 rptr 在 rclk 下递增,经两级同步器同步到 wclk 域得到 rptr_sync。
  • 步骤5:计算满标志:在 wclk 域用 (wptr_gray == ~rptr_sync_gray[addr_bits-1:0] && wptr_gray[addr_bits] != rptr_sync_gray[addr_bits]) 判断。
  • 步骤6:计算空标志:在 rclk 域用 (rptr_gray == wptr_sync_gray) 判断。
  • 步骤7:编写仿真 testbench,验证写满后读空,以及同时读写时的数据完整性。
  • 步骤8:综合实现后检查时序报告,确认跨时钟域路径无违例;上板用 ILA 或逻辑分析仪抓取 full/empty 信号与数据输出。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T主流学习板,逻辑资源充足Cyclone IV E / Lattice MachXO3
EDA 版本Vivado 2024.2 或 Quartus Prime Pro 24.3+支持最新 XPM 原语与时序分析ISE 14.7(仅限老器件)
仿真器Vivado Simulator 或 ModelSim SE-64 2024.1支持混合语言仿真Questa / Verilator(仅 RTL)
时钟/复位wclk=100MHz, rclk=50MHz, 异步复位高有效模拟典型跨时钟域场景频率比 1:1 ~ 1:10 均可
接口依赖双口 RAM 深度 16(4 位地址)便于仿真观察满/空边界深度 8/32/64 按需调整
约束文件XDC 或 SDC:set_false_path 跨时钟域路径避免时序分析误报set_clock_groups -asynchronous

目标与验收标准

  • 功能点:写满时 full 拉高,读空时 empty 拉高;写指针与读指针跨越时钟域后无亚稳态传播。
  • 性能指标:Fmax 满足 wclk=100MHz, rclk=50MHz(示例值),无 setup/hold 违例。
  • 资源占用:LUT ≤ 80, FF ≤ 100, BRAM = 1(深度 16 示例),以实际综合报告为准。
  • 验收方式:仿真波形显示 full 在写入第 16 个数据后拉高,empty 在读出第 16 个数据后拉高;上板通过串口或 ILA 读取数据并比对。

实施步骤

工程结构

  • 顶层模块:async_fifo_top(例化双口 RAM、写指针模块、读指针模块、同步器)。
  • 子模块:wptr_ctrl(写指针 + 满标志)、rptr_ctrl(读指针 + 空标志)、sync_2stage(两级同步器)。
  • 双口 RAM 例化:使用 XPM_MEMORY_SDPRAM(Xilinx)或 altsyncram(Intel),深度 16,数据宽度 8 位。
  • 约束文件:set_false_path -from [get_clocks wclk] -to [get_clocks rclk](跨时钟域路径)。

关键模块:格雷码计数器与同步器

module gray_counter #(parameter ADDR_BITS = 4) (
  input  wire             clk,
  input  wire             rst_n,
  input  wire             inc,
  output reg  [ADDR_BITS:0] gray  // 多1位用于满空比较
);
  reg [ADDR_BITS:0] binary;
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      binary <= 0;
      gray    <= 0;
    end else if (inc) begin
      binary <= binary + 1'b1;
      gray   <= (binary + 1'b1) ^ ((binary + 1'b1) >> 1);
    end
  end
endmodule

逐行说明

  • 第 1 行:模块定义,参数 ADDR_BITS 默认 4,实际地址位宽为 4,gray 输出宽度为 5(多 1 位用于满空比较)。
  • 第 2-6 行:端口声明,inc 为递增使能,gray 为格雷码输出。
  • 第 7 行:内部二进制计数器 binary,宽度与 gray 相同。
  • 第 8-14 行:时序逻辑,复位时 binary 和 gray 清零;inc 有效时 binary 加 1,gray 通过异或计算(binary+1 右移 1 位后异或自身)。
module sync_2stage #(parameter WIDTH = 5) (
  input  wire             clk,
  input  wire             rst_n,
  input  wire [WIDTH-1:0] async_in,
  output wire [WIDTH-1:0] sync_out
);
  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 <= async_in;
      sync_reg2 <= sync_reg1;
    end
  end
  assign sync_out = sync_reg2;
endmodule

逐行说明

  • 第 1 行:模块定义,参数 WIDTH 默认 5,匹配格雷码宽度。
  • 第 2-6 行:端口声明,async_in 为异步输入,sync_out 为同步输出。
  • 第 7 行:两级同步寄存器 sync_reg1 和 sync_reg2。
  • 第 8-14 行:复位时清零;每个时钟沿将 async_in 打两拍后输出,降低亚稳态概率。

满标志与空标志生成

// 在 wclk 域:满标志
assign full = (wptr_gray == {~rptr_sync_gray[ADDR_BITS:ADDR_BITS-1],
                              rptr_sync_gray[ADDR_BITS-2:0]});
// 在 rclk 域:空标志
assign empty = (rptr_gray == wptr_sync_gray);

逐行说明

  • 第 1-2 行:满标志判断:写指针格雷码与读指针格雷码同步版本比较,高两位取反(因为写指针多一位,满时写指针比读指针多绕一圈)。
  • 第 3-4 行:空标志判断:读指针格雷码与写指针格雷码同步版本完全相等。

验证:仿真 testbench

module tb_async_fifo;
  reg wclk, rclk, rst_n, winc, rinc;
  wire [7:0] rdata;
  wire full, empty;
  async_fifo_top #(.DEPTH(16)) uut (.*);
  initial begin
    wclk=0; rclk=0; rst_n=0; winc=0; rinc=0;
    #100 rst_n=1;
    // 写 16 个数据
    repeat(16) begin @(posedge wclk); winc=1; end
    winc=0; #100;
    // 读 16 个数据
    repeat(16) begin @(posedge rclk); rinc=1; end
    rinc=0; #100;
    $finish;
  end
  always #5 wclk=~wclk; // 100MHz
  always #10 rclk=~rclk; // 50MHz
endmodule

逐行说明

  • 第 1-5 行:testbench 模块声明,例化 DUT 并连接所有端口。
  • 第 6-11 行:初始化,复位后先写满 16 个数据,再读空。
  • 第 12-14 行:生成两个异步时钟,wclk 周期 10ns,rclk 周期 20ns。

常见坑与排查

  • 坑 1:格雷码同步后未打两拍直接用于满空判断,导致亚稳态传播。排查:检查同步器输出是否经过两级寄存器。
  • 坑 2:满标志判断逻辑中高两位取反错误(如只取反最高位)。排查:用仿真波形对比 wptr_gray 与 rptr_sync_gray 在满时的关系。
  • 坑 3:双口 RAM 的写使能未与 wclk 同步,导致数据写入错误地址。排查:检查 RAM 例化时写使能是否来自 wclk 域寄存器。
  • 坑 4:未对跨时钟域路径设置 false_path,导致时序分析报告大量违例。排查:在 XDC 中添加 set_false_path。

原理与设计说明

为什么用双口 RAM + 格雷码?双口 RAM 提供独立的读写端口,天然支持异步时钟;格雷码每次只变化 1 位,同步后最多出现 1 位亚稳态,且亚稳态不会传播到其他位,保证满空判断的可靠性。满标志在写时钟域生成,避免读操作影响写使能;空标志在读时钟域生成,避免写操作影响读使能。这种设计在资源与 Fmax 之间取得平衡:格雷码计数器比二进制计数器多 1 位,但同步器面积小;双口 RAM 使用 BRAM,比 LUT 实现节省逻辑资源。

验证与结果

验证项预期结果实测结果(示例)测量条件
写满标志full 在写第 16 个数据后拉高full 在写指针=16 时拉高wclk=100MHz, 深度 16
读空标志empty 在读第 16 个数据后拉高empty 在读指针=16 时拉高rclk=50MHz, 深度 16
数据完整性写入数据与读出数据一致所有 16 个数据正确随机数据写入
Fmaxwclk≥100MHz, rclk≥50MHzwclk=125MHz, rclk=100MHzVivado 2024.2, Artix-7 -1 speed grade
资源占用LUT≤80, FF≤100, BRAM=1LUT=42, FF=56, BRAM=1深度 16, 数据宽度 8

注:实测结果基于示例配置,实际数值以具体工程与器件数据手册为准。

故障排查(Troubleshooting)

  • 现象:full 标志一直为高,无法写入。原因:写指针同步到读域后未正确返回。检查点:仿真波形中 wptr_sync_gray 是否在写操作后变化。修复:确认同步器输入来自 wptr_gray 而非 wptr。
  • 现象:empty 标志一直为高,无法读出。原因:读指针未递增。检查点:rinc 信号是否有效。修复:检查读使能逻辑。
  • 现象:读出数据与写入数据不一致。原因:双口 RAM 地址或数据线连接错误。检查点:RAM 例化时 wr_addr 与 rd_addr 是否接对。修复:核对端口映射。
  • 现象:综合后时序违例严重。原因:未设置 false_path。检查点:查看时序报告中跨时钟域路径。修复:在 XDC 中添加 set_false_path -from [get_clocks wclk] -to [get_clocks rclk]。
  • 现象:上板后 full/empty 信号抖动。原因:同步器未打两拍。检查点:RTL 中同步器是否只有一级寄存器。修复:改为两级同步器。
  • 现象:仿真中 full 标志提前拉高。原因:满判断逻辑错误。检查点:比较 wptr_gray 与 rptr_sync_gray 的格雷码值。修复:确保高两位取反逻辑正确。
  • 现象:仿真中 empty 标志延迟拉高。原因:读指针同步到写域有延迟。检查点:仿真波形中 rptr_sync_gray 更新时机。修复:这是正常现象,空标志在 rclk 域生成,延迟由同步器引入。
  • 现象:资源占用远超预期。原因:双口 RAM 用 LUT 实现而非 BRAM。检查点:综合报告中的 RAM 类型。修复:在例化时指定 RAM_STYLE="block" 或使用 XPM 原语。
  • 现象:上板后数据丢失。原因:写使能 winc 与 wclk 不同步。检查点:winc 是否来自 wclk 域寄存器。修复:将 winc 用 wclk 打一拍。
  • 现象:仿真中数据在满后继续写入。原因:full 标志未及时拉高。检查点:full 生成逻辑是否在 wclk 域。修复:确保 full 在 wclk 域组合逻辑生成。

扩展与下一步

  • 扩展 1:参数化 FIFO 深度与数据宽度,支持任意深度(2^N)。
  • 扩展 2:增加 almost_full / almost_empty 标志,用于流控。
  • 扩展 3:使用 XPM_FIFO 原语替代手写代码,提高可移植性。
  • 扩展 4:加入断言(SVA)验证满空边界与数据完整性。
  • 扩展 5:使用形式验证工具(如 OneSpin)证明满空逻辑的正确性。
  • 扩展 6:在 AXI-Stream 接口中集成异步 FIFO,实现跨时钟域数据流。

参考与信息来源

  • Xilinx UG953: Vivado Design Suite User Guide - Using Constraints
  • Xilinx UG974: UltraScale Architecture Libraries Guide (XPM)
  • Intel AN 480: Metastability in Altera Devices
  • Clifford E. Cummings, "Simulation and Synthesis Techniques for Asynchronous FIFO Design" (SNUG 2002)
  • 成电国芯 FPGA 云课堂内部培训资料(2026 版)

技术附录

术语表

  • 格雷码(Gray Code):相邻两个值只有 1 位不同的编码,用于跨时钟域同步。
  • 亚稳态(Metastability):触发器输入变化在时钟沿附近时,输出进入不确定状态。
  • 双口 RAM(Dual-Port RAM):具有独立读写端口的 RAM,支持同时读写。
  • XPM(Xilinx Parameterized Macro):Xilinx 提供的参数化原语库。

检查清单

  • 同步器至少两级寄存器
  • 满标志在写时钟域生成
  • 空标志在读时钟域生成
  • 格雷码比较逻辑正确
  • 跨时钟域路径设置 false_path
  • 双口 RAM 例化使用 BRAM 而非 LUT

关键约束速查

# XDC 约束示例
set_false_path -from [get_clocks wclk] -to [get_clocks rclk]
set_false_path -from [get_clocks rclk] -to [get_clocks wclk]
# 若使用 XPM_FIFO,无需手动设置 false_path(原语自动处理)

逐行说明

  • 第 1 行:禁止时序分析工具分析从 wclk 到 rclk 的路径,避免误报。
  • 第 2 行:禁止从 rclk 到 wclk 的路径分析。
  • 第 3 行:XPM_FIFO 内部已处理跨时钟域,无需额外约束。
标签:
本文原创,作者:二牛学FPGA,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/42167.html
二牛学FPGA

二牛学FPGA

初级工程师
这家伙真懒,几个字都不愿写!
1.06K20.57W4.05W3.67W
分享:
成电国芯FPGA赛事课即将上线
Verilog实战:2026年用双口RAM实现异步FIFO的常见调试技巧
Verilog实战:2026年用双口RAM实现异步FIFO的常见调试技巧上一篇
FPGA仿真中SystemVerilog断言(SVA)实战应用指南下一篇
FPGA仿真中SystemVerilog断言(SVA)实战应用指南
相关文章
总数:1.10K
Verilog 阻塞与非阻塞赋值深度对比:移位寄存器仿真验证指南

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

QuickStart:10分钟直观对比本指南通过一个简单的3位移…
技术分享
16天前
0
0
33
0
VHDL入门:2026年用GHDL与VUnit搭建测试环境

VHDL入门:2026年用GHDL与VUnit搭建测试环境

QuickStart本指南带你用最短路径在本地搭建VHDL仿真与测…
技术分享
4天前
0
0
13
0
从FPGA原型到嵌入式量产:消费电子快速创新的协作开发指南

从FPGA原型到嵌入式量产:消费电子快速创新的协作开发指南

在消费电子领域,产品迭代周期不断缩短,市场窗口转瞬即逝。为了在保证产品可…
技术分享
20天前
0
0
53
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容