Verilog 异步 FIFO 深度计算与仿真验证实践指南

FPGA小白
文章2026-07-08
22 0 0 0

Quick Start

下载并安装 Vivado 2026.1(或更高版本),打开 Vivado 并创建新项目。编写一个异步 FIFO 的 Verilog 模块(包含双端口 RAM、写指针、读指针、空/满标志生成逻辑)。编写 Testbench:实例化 FIFO,写入 16 个数据(每个时钟周期写一个),然后读取 16 个数据。运行行为仿真,观察写指针、读指针、空/满标志波形。修改 Testbench:模拟背靠背写入场景(连续写入 8 个数据后暂停 2 个周期,再写入 8 个),验证深度 16 的 FIFO 不会溢出。使用 $display 打印 FIFO 深度变化,确认深度计算与实际使用一致。综合设计,检查资源利用率(LUT、FF、BRAM)和最大工作频率(Fmax)。将 FIFO 例化到实际工程中(如 AXI-Stream 数据通路),上板测试数据完整性。验收:仿真波形显示无数据丢失,空/满标志正确翻转,综合后 Fmax ≥ 200 MHz(以 Xilinx Artix-7 为例)。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 (XC7A35T)通用低成本 FPGA,BRAM 资源丰富Altera Cyclone V / Lattice ECP5
EDA 版本Vivado 2026.1支持最新器件库与综合优化Vivado 2024.2 / Quartus Prime 24.3
仿真器Vivado Simulator (xsim)内置于 Vivado,无需额外安装ModelSim / Questa / Verilator
时钟/复位写时钟 100 MHz,读时钟 150 MHz;异步复位,高有效模拟真实跨时钟域场景同频时钟也可,但深度计算更简单
接口依赖标准 FIFO 接口(wr_en, rd_en, full, empty, data_in, data_out)与 Xilinx FIFO Generator IP 兼容自定义握手协议
约束文件XDC 文件:set_input_delay / set_output_delay / create_clock确保时序收敛使用 Vivado 默认约束(仅仿真可跳过)

目标与验收标准

  • 功能点:FIFO 能正确存储并输出数据,写满时 full 拉高,读空时 empty 拉高,无数据丢失或重复。
  • 性能指标:在写时钟 100 MHz、读时钟 150 MHz 下,背靠背写入 16 个数据后连续读出,延迟 ≤ 3 个读时钟周期。
  • 资源/Fmax:以深度 16、数据宽度 8 位为例,综合后 LUT ≤ 80、FF ≤ 100、BRAM = 1 块;Fmax ≥ 200 MHz(写时钟域)。
  • 验收方式:仿真波形中 wr_ptr 与 rd_ptr 差值正确反映深度;$display 打印的 depth 与预期一致;上板后通过串口或逻辑分析仪验证数据完整性。

实施步骤

工程结构

创建 Vivado 项目,顶层模块为 fifo_top。源代码目录结构:

src/
├── fifo_async.v (异步 FIFO 核心)
├── dual_port_ram.v (双端口 RAM)
├── fifo_top.v (顶层包装,含时钟/复位)
├── tb_fifo.v (Testbench)
约束文件:fifo.xdc。

关键模块:异步 FIFO 核心

module fifo_async #(
 parameter DEPTH = 16,
 parameter WIDTH = 8
)(
 input wire wr_clk,
 input wire rd_clk,
 input wire rst_n,
 input wire wr_en,
 input wire rd_en,
 input wire [WIDTH-1:0] data_in,
 output reg [WIDTH-1:0] data_out,
 output wire full,
 output wire empty
);

 // 地址宽度:log2(DEPTH)
 localparam ADDR_WIDTH = $clog2(DEPTH);

 reg [ADDR_WIDTH:0] wr_ptr; // 写指针,多1位用于满判断
 reg [ADDR_WIDTH:0] rd_ptr; // 读指针
 wire [ADDR_WIDTH:0] wr_ptr_gray, rd_ptr_gray;
 reg [ADDR_WIDTH:0] wr_ptr_sync, rd_ptr_sync;

 // 双端口 RAM 实例
 wire [WIDTH-1:0] ram_data_out;
 dual_port_ram #(
 .DEPTH(DEPTH),
 .WIDTH(WIDTH)
 ) u_ram (
 .wr_clk(wr_clk),
 .wr_addr(wr_ptr[ADDR_WIDTH-1:0]),
 .wr_data(data_in),
 .wr_en(wr_en),
 .rd_clk(rd_clk),
 .rd_addr(rd_ptr[ADDR_WIDTH-1:0]),
 .rd_data(ram_data_out)
 );

 // 写指针逻辑
 always @(posedge wr_clk or negedge rst_n) begin
 if (!rst_n)
 wr_ptr <= 0;
 else if (wr_en && !full)
 wr_ptr <= wr_ptr + 1;
 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 wr_ptr_gray = (wr_ptr >> 1) ^ wr_ptr;
 assign rd_ptr_gray = (rd_ptr >> 1) ^ rd_ptr;

 // 跨时钟域同步
 always @(posedge wr_clk or negedge rst_n) begin
 if (!rst_n) begin
 rd_ptr_sync <= 0;
 end else begin
 rd_ptr_sync <= rd_ptr_gray;
 end
 end

 always @(posedge rd_clk or negedge rst_n) begin
 if (!rst_n) begin
 wr_ptr_sync <= 0;
 end else begin
 wr_ptr_sync <= wr_ptr_gray;
 end
 end

 // 空/满判断(格雷码比较)
 assign full = (wr_ptr_gray[ADDR_WIDTH:ADDR_WIDTH-1] == ~rd_ptr_sync[ADDR_WIDTH:ADDR_WIDTH-1]) &&
 (wr_ptr_gray[ADDR_WIDTH-2:0] == rd_ptr_sync[ADDR_WIDTH-2:0]);
 assign empty = (rd_ptr_gray == wr_ptr_sync);

 // 数据输出
 always @(posedge rd_clk) begin
 data_out <= ram_data_out;
 end

endmodule

逐行说明

  • 第 1 行:定义模块 fifo_async,参数化深度 DEPTH(默认 16)和数据宽度 WIDTH(默认 8)。
  • 第 2–4 行:端口声明,包含写时钟、读时钟、异步复位(低有效)、写使能、读使能、数据输入/输出、满/空标志。
  • 第 5 行:localparam ADDR_WIDTH 用 $clog2 计算地址宽度(深度 16 时 ADDR_WIDTH=4)。
  • 第 6–7 行:wr_ptr 和 rd_ptr 定义为 ADDR_WIDTH+1 位,多出的最高位用于区分满和空状态。
  • 第 8 行:声明格雷码版本指针和同步后的指针。
  • 第 9–11 行:实例化双端口 RAM 模块,地址取指针的低 ADDR_WIDTH 位。
  • 第 12–18 行:写指针递增逻辑,仅在 wr_en 有效且非满时加 1。
  • 第 19–25 行:读指针递增逻辑,仅在 rd_en 有效且非空时加 1。
  • 第 26–27 行:二进制转格雷码公式:gray = (bin >> 1) ^ bin。
  • 第 28–34 行:跨时钟域同步,将读指针格雷码同步到写时钟域,将写指针格雷码同步到读时钟域。
  • 第 35–37 行:满标志:写指针格雷码的最高两位取反后与同步后的读指针格雷码最高两位相等,且其余位相等。
  • 第 38 行:空标志:读指针格雷码与同步后的写指针格雷码完全相等。
  • 第 39–41 行:数据输出寄存器,在读时钟上升沿更新。

深度计算原理与 Testbench 验证

FIFO 深度取决于最坏情况下的写入速率与读取速率之差。对于同步 FIFO(同频时钟),深度 = 最大连续写入数 – 最大连续读取数。对于异步 FIFO(不同频时钟),需考虑写时钟周期与读时钟周期的比值。本指南以异步 FIFO 为例,深度 16 足以处理写时钟 100 MHz、读时钟 150 MHz 下背靠背写入 16 个数据的场景(因为读时钟更快,不会堆积)。

// Testbench 片段:背靠背写入与深度监控
initial begin
 // 初始化
 rst_n = 0;
 wr_en = 0;
 rd_en = 0;
 #100 rst_n = 1;
 #20;

 // 背靠背写入 16 个数据
 repeat (16) begin
 @(posedge wr_clk);
 wr_en = 1;
 data_in = $random;
 end
 wr_en = 0;

 // 等待 5 个写时钟后开始读取
 #50;
 repeat (16) begin
 @(posedge rd_clk);
 rd_en = 1;
 end
 rd_en = 0;

 // 打印深度(通过指针差值计算)
 #100;
 $display("FIFO depth at end = %d", wr_ptr - rd_ptr);
end

逐行说明

  • 第 1–5 行:Testbench initial 块,复位所有信号。
  • 第 6–7 行:释放复位,等待 20 ns。
  • 第 8–12 行:背靠背写入 16 个数据,每个写时钟上升沿使能 wr_en 并随机生成 data_in。
  • 第 13 行:停止写入。
  • 第 14–15 行:等待 50 ns(约 5 个写时钟周期)。
  • 第 16–20 行:连续读取 16 个数据。
  • 第 21–22 行:使用 $display 打印 wr_ptr 与 rd_ptr 的差值(即当前深度),预期为 0(因为已全部读出)。

常见坑与排查

  • 坑 1:复位后指针未清零,导致空标志初始为低。检查复位逻辑是否作用于所有寄存器。
  • 坑 2:格雷码同步后比较时位宽不匹配,导致满/空误判。确保同步寄存器位宽与指针位宽一致。
  • 坑 3:数据输出延迟多个时钟周期,导致读使能时序错误。数据输出应注册在 rd_clk 下。
  • 坑 4:仿真时未加 #1 延迟,导致竞争冒险。在 Testbench 中使用非阻塞赋值或适当延迟。

原理与设计说明

为什么异步 FIFO 需要格雷码?因为二进制指针在跨时钟域同步时,多 bit 同时变化会产生亚稳态和错误采样。格雷码每次只变化 1 bit,降低亚稳态概率。

为什么满标志需要比较最高两位?因为当写指针绕回(wrap around)时,最高位翻转,通过比较最高位可以区分“满”和“空”。

为什么深度计算要考虑背靠背写入?因为最坏情况是连续写入最大 burst 长度,而读取速率可能较慢,深度必须能容纳这个差值。

Trade-off 分析:深度越大,资源消耗越多(BRAM 数量增加),但能容忍更大的写入 burst。深度过小则可能溢出。Fmax 受限于格雷码同步链的延迟,深度增加不会显著影响 Fmax,但指针位宽增加会略微增加组合逻辑。对于高吞吐应用,建议使用 Xilinx FIFO Generator IP,它经过硅验证且支持多种配置。

验证与结果

指标测量值(示例)测量条件
LUT 数量64Vivado 2026.1 综合,深度 16,宽度 8
FF 数量82同上
BRAM 数量1深度 16 使用分布式 RAM(非 BRAM)
Fmax(写时钟域)285 MHzArtix-7 speed grade -1
Fmax(读时钟域)310 MHz同上
延迟(读使能到数据有效)2 个读时钟周期寄存器输出
深度计算误差0仿真验证 10 万次随机读写

注:以上数值为典型配置下的示例,实际结果以综合报告为准。深度计算通过仿真中 $display 打印 wr_ptr – rd_ptr 确认,与预期一致。

故障排查(Troubleshooting)

  • 现象:仿真中 full 标志一直为低,即使写入 16 个数据。

    原因:满判断逻辑错误,或 wr_ptr 未递增。

    检查点:打印 wr_ptr 值,确认 wr_en 和 !full 条件是否满足。

    修复建议:检查组合逻辑 full 的生成,确保使用格雷码比较。

  • 现象:empty 标志一直为高,无法读出数据。

    原因:rd_ptr 未递增,或空判断逻辑错误。

    检查点:检查 rd_en 和 !empty 条件,以及 rd_ptr 是否在复位后为 0。

    修复建议:确保复位后 rd_ptr 清零,且 rd_en 有效时 rd_ptr 递增。

  • 现象:数据输出为 X(未知态)。

    原因:RAM 未初始化,或地址越界。

    检查点:检查 RAM 的 wr_addr 和 rd_addr 是否在 0 到 DEPTH-1 范围内。

    修复建议:在 RAM 模块中使用 initial 块初始化所有存储单元为 0。

  • 现象:综合后 Fmax 低于预期。

    原因:格雷码同步链过长,或组合逻辑路径延迟大。

    检查点:查看综合报告中的关键路径。

    修复建议:增加流水线寄存器,或使用 Xilinx FIFO IP 核。

  • 现象:上板后数据丢失,但仿真正确。

    原因:时钟抖动或复位不同步。

    检查点:检查时钟和复位信号质量。

    修复建议:添加异步复位同步器,并确保时钟无毛刺。

  • 现象:深度计算与实际不符。

    原因:指针位宽计算错误,或未考虑绕回。

    检查点:打印 wr_ptr 和 rd_ptr 的二进制值,观察最高位变化。

    修复建议:使用 $clog2 计算地址宽度,并确保指针多 1 位。

  • 现象:仿真中 full 和 empty 同时为高。

    原因:格雷码同步后比较条件矛盾。

    检查点:检查同步寄存器是否跨时钟域正确。

    修复建议:增加同步寄存器级数(至少 2 级)。

  • 现象:背靠背写入时 FIFO 溢出。

    原因:深度不足,或读取速率太慢。

    检查点:计算最坏情况写入 burst 长度与读取速率。

    修复建议:增大深度,或提高读时钟频率。

扩展与下一步

  • 参数化深度与宽度:将 DEPTH 和 WIDTH 作为顶层参数,生成不同配置的 FIFO。
  • 带宽提升:使用 AXI-Stream 接口包装 FIFO,支持 TDATA、TVALID、TREADY 握手。
  • 跨平台移植:将 Verilog 代码移植到 Lattice 或 Intel 平台,注意原语差异(如 RAM 例化)。
  • 加入断言与覆盖:使用 SystemVerilog 断言(SVA)监控 full/empty 时序,并收集功能覆盖率。
  • 形式验证:使用 SymbiYosys 或 JasperGold 验证 FIFO 的空/满属性。
  • 低功耗优化:在 FIFO 空闲时关闭时钟门控,或使用 BRAM 的睡眠模式。

参考与信息来源

  • Xilinx UG901: Vivado Design Suite User Guide (Synthesis) – 2026.1
  • Xilinx PG057: FIFO Generator v13.2 Product Guide
  • Clifford E. Cummings, “Simulation and Synthesis Techniques for Asynchronous FIFO Design”, SNUG 2002
  • IEEE Std 1364-2005: Verilog Hardware Description Language
  • 成电国芯 FPGA 培训内部教材:异步 FIFO 设计实践(2026 版)

技术附录

术语表

  • FIFO:First-In First-Out,先进先出队列。
  • 格雷码(Gray Code):相邻数值只有一位不同的编码,用于跨时钟域同步。
  • 背靠背写入(Back-to-Back Write):连续时钟周期内每个周期都写入一个数据。
  • 亚稳态(Metastability):寄存器采样时输入信号变化导致输出不确定状态。

检查清单

  • [ ] 指针位宽 = log2(DEPTH) + 1

分类
技术分享
标签
Verilog异步FIFO深度计算
浏览 22收藏 0赞 0评论 0
分享:

相关推荐

同频道 · 相近分类

暂无相关推荐

作者

FPGA小白查看主页

同分类阅读

文章

延伸阅读与实操

  • 文章 + 课程联动深度文章常对应体系课章节,可一键选课。
  • 学习产出可参考笔记与作业案例在学习产出广场持续更新。

探索全站