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

Verilog异步FIFO设计指南:从RTL实现到上板验证(基于2026年综合工具)

FPGA小白FPGA小白
技术分享
2小时前
0
0
3

Quick Start

以下步骤在 Vivado 2026.1 与 Modelsim SE-64 2025.1 环境下,基于 Xilinx Artix-7 XC7A35T 开发板,实现一个 8-bit 数据宽度、16 深度、格雷码指针同步的异步 FIFO。从创建工程到上板验证,总耗时约 30 分钟。

  • 新建 Vivado 工程,选择器件 xc7a35tcsg324-1,添加顶层文件 async_fifo_top.v、FIFO 核心模块 async_fifo.v、双端口 RAM 模块 dpram.v 与同步器模块 sync_bits.v。
  • 编写约束文件 async_fifo.xdc,为 wr_clk 与 rd_clk 分别指定主时钟周期(例如 50MHz 与 25MHz),并设置异步时钟组:set_clock_groups -asynchronous -group [get_clocks -include_generated_clocks wr_clk] -group [get_clocks -include_generated_clocks rd_clk]
  • 运行综合(Synthesis),检查无关键警告(Critical Warning)或 Latch 推断。
  • 运行实现(Implementation),检查时序报告:确保 wr_clk 域到 rd_clk 域的路径被标记为“False Path”或“Asynchronous Clock Group”,无 setup/hold 违规。
  • 编写 Testbench tb_async_fifo.v,实例化 FIFO,模拟写满、读空、同时读写场景,运行仿真至少 10 us。
  • 观察波形:写指针与读指针在跨时钟域时保持格雷码编码,同步后无毛刺;full 与 empty 标志在正确时钟沿跳变。
  • 上板验证:将 FIFO 输出连接到 8 个 LED,通过按键模拟写使能与读使能,观察 LED 数据流是否按预期顺序显示。
  • 验收点:当写使能连续 16 次后 full 拉高;读使能连续 16 次后 empty 拉高;同时读写时数据不丢失。

前置条件与环境

项目推荐值说明替代方案
器件/板卡Xilinx Artix-7 XC7A35T-1CSG324C主流低功耗 FPGA,资源适中Intel Cyclone IV / Lattice ECP5
EDA 版本Vivado 2026.1支持最新综合策略与异步时钟组约束Vivado 2024.2 / 2025.2
仿真器Modelsim SE-64 2025.1支持 VHDL/Verilog 混合仿真Vivado Simulator / Questa
时钟/复位wr_clk=50MHz, rd_clk=25MHz, 异步复位低有效典型异步 FIFO 跨时钟域场景任意频率比,建议 wr_clk ≥ rd_clk
接口依赖无外部 IP,纯 RTL 实现所有模块手写,便于理解与移植可使用 XPM_FIFO(Xilinx 原语)
约束文件async_fifo.xdc必须包含异步时钟组与 false path 约束SDC 格式(Synopsys)

目标与验收标准

  • 功能正确性:写满标志(full)在 FIFO 存满 16 个数据后立即拉高;读空标志(empty)在 FIFO 无数据后立即拉高;同时读写时数据不丢失、不重复。
  • 时序安全:跨时钟域路径(写指针到读时钟域、读指针到写时钟域)被综合工具识别为异步,无 setup/hold 违规;格雷码编码保证同步后最多一位变化。
  • 资源与性能:在 Artix-7 上实现,Fmax(写时钟)≥ 200MHz,Fmax(读时钟)≥ 200MHz,LUT 消耗 ≤ 120,FF 消耗 ≤ 100,Block RAM 使用 1 个(16×8 配置)。
  • 可移植性:RTL 代码无任何厂商原语(除 RAM 推断外),可移植到 Intel/Lattice/ASIC 流程。

实施步骤

工程结构与模块划分

  • 顶层模块 async_fifo_top.v:实例化 FIFO 核心,连接外部端口(wr_clk, rd_clk, rst_n, wr_en, rd_en, data_in, data_out, full, empty)。
  • FIFO 核心 async_fifo.v:包含双端口 RAM、写指针逻辑、读指针逻辑、空/满标志生成、同步器实例化。
  • 双端口 RAM dpram.v:深度 16,位宽 8,写使能同步于 wr_clk,读使能同步于 rd_clk。
  • 同步器 sync_bits.v:两级触发器链,用于将格雷码指针同步到目标时钟域。
  • Testbench tb_async_fifo.v:生成两个独立时钟,模拟写/读操作,自动比对数据。

关键模块 RTL 实现

以下给出 async_fifo.v 的核心代码。为突出重点,省略了 RAM 实例化与同步器实例化的细节,完整代码请参考附录。

// async_fifo.v - 核心模块
module async_fifo #(
 parameter DATA_WIDTH = 8,
 parameter FIFO_DEPTH = 16,
 parameter PTR_WIDTH = 4 // 2^4 = 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] data_in,
 output reg [DATA_WIDTH-1:0] data_out,
 output reg full,
 output reg empty
);

 // 内部信号
 reg [PTR_WIDTH-1:0] wr_ptr, rd_ptr;
 reg [PTR_WIDTH-1:0] wr_ptr_gray, rd_ptr_gray;
 reg [PTR_WIDTH-1:0] wr_ptr_gray_sync, rd_ptr_gray_sync;
 wire [PTR_WIDTH-1:0] wr_ptr_bin, rd_ptr_bin;
 wire wr_enable, rd_enable;

 // 写指针逻辑(wr_clk 域)
 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

 // 读指针逻辑(rd_clk 域)
 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

 // 格雷码编码
 function [PTR_WIDTH-1:0] bin2gray;
 input [PTR_WIDTH-1:0] bin;
 begin
 bin2gray = bin ^ (bin >> 1);
 end
 endfunction

 always @(posedge wr_clk or negedge rst_n)
 if (!rst_n) wr_ptr_gray <= 0;
 else wr_ptr_gray <= bin2gray(wr_ptr);

 always @(posedge rd_clk or negedge rst_n)
 if (!rst_n) rd_ptr_gray <= 0;
 else rd_ptr_gray <= bin2gray(rd_ptr);

 // 同步器实例化(两级触发器)
 sync_bits #(.WIDTH(PTR_WIDTH)) u_sync_wr2rd (
 .clk (rd_clk),
 .rst_n (rst_n),
 .data_in (wr_ptr_gray),
 .data_out (wr_ptr_gray_sync)
 );

 sync_bits #(.WIDTH(PTR_WIDTH)) u_sync_rd2wr (
 .clk (wr_clk),
 .rst_n (rst_n),
 .data_in (rd_ptr_gray),
 .data_out (rd_ptr_gray_sync)
 );

 // 空/满标志生成
 wire [PTR_WIDTH-1:0] wr_ptr_gray_sync_bin, rd_ptr_gray_sync_bin;
 // 将同步后的格雷码转回二进制(用于比较)
 function [PTR_WIDTH-1:0] gray2bin;
 input [PTR_WIDTH-1:0] gray;
 reg [PTR_WIDTH-1:0] bin;
 integer i;
 begin
 bin[PTR_WIDTH-1] = gray[PTR_WIDTH-1];
 for (i = PTR_WIDTH-2; i >= 0; i = i - 1)
 bin[i] = bin[i+1] ^ gray[i];
 gray2bin = bin;
 end
 endfunction

 always @(posedge wr_clk or negedge rst_n)
 if (!rst_n) full <= 1'b0;
 else begin
 wr_ptr_gray_sync_bin = gray2bin(wr_ptr_gray_sync);
 // 满条件:写指针追上同步后的读指针,且最高两位相反
 if ((wr_ptr[PTR_WIDTH-1] != wr_ptr_gray_sync_bin[PTR_WIDTH-1]) &&
 (wr_ptr[PTR_WIDTH-2] != wr_ptr_gray_sync_bin[PTR_WIDTH-2]) &&
 (wr_ptr[PTR_WIDTH-3:0] == wr_ptr_gray_sync_bin[PTR_WIDTH-3:0]))
 full <= 1'b1;
 else
 full <= 1'b0;
 end

 always @(posedge rd_clk or negedge rst_n)
 if (!rst_n) empty <= 1'b1;
 else begin
 rd_ptr_gray_sync_bin = gray2bin(rd_ptr_gray_sync);
 // 空条件:读指针等于同步后的写指针
 if (rd_ptr == rd_ptr_gray_sync_bin)
 empty <= 1'b1;
 else
 empty <= 1'b0;
 end

 // 双端口 RAM 实例化(略)

endmodule

逐行说明

  • 第 1-6 行:模块定义与参数化。DATA_WIDTH 与 FIFO_DEPTH 可配置,PTR_WIDTH 为地址位宽(log2(DEPTH))。
  • 第 7-15 行:端口声明。wr_clk 与 rd_clk 为异步时钟,rst_n 为异步复位低有效。wr_en/rd_en 为写/读使能,data_in/data_out 为数据总线,full/empty 为状态标志。
  • 第 18-19 行:内部信号定义。wr_ptr/rd_ptr 为二进制指针,wr_ptr_gray/rd_ptr_gray 为格雷码指针,wr_ptr_gray_sync/rd_ptr_gray_sync 为同步后的格雷码指针。
  • 第 22-26 行:写指针递增逻辑。在 wr_clk 上升沿,若 wr_en 有效且 FIFO 未满,则 wr_ptr 加 1。复位时清零。
  • 第 29-33 行:读指针递增逻辑。在 rd_clk 上升沿,若 rd_en 有效且 FIFO 非空,则 rd_ptr 加 1。
  • 第 36-43 行:格雷码编码函数 bin2gray。通过二进制右移一位后异或实现。例如 4'b0101 变为 4'b0111。
  • 第 45-49 行:在每个时钟域内将二进制指针转换为格雷码,并寄存到 wr_ptr_gray/rd_ptr_gray 中。这是跨时钟域传输前的最后一级寄存器。
  • 第 52-62 行:实例化两个同步器模块。sync_bits 内部为两级 D 触发器链,用于将格雷码指针同步到目标时钟域。同步后的值 wr_ptr_gray_sync 在 rd_clk 域使用,rd_ptr_gray_sync 在 wr_clk 域使用。
  • 第 65-79 行:格雷码转二进制函数 gray2bin。从最高位开始,逐位异或。例如 4'b0111 转回 4'b0101。
  • 第 81-91 行:满标志生成逻辑。在 wr_clk 域,将同步后的读指针(wr_ptr_gray_sync)转二进制,与写指针比较。满条件:写指针的最高两位与同步读指针的最高两位相反,且低位相等。这是经典的“MSB 翻转”判断法,确保 FIFO 满时不会误判。
  • 第 93-100 行:空标志生成逻辑。在 rd_clk 域,将同步后的写指针(rd_ptr_gray_sync)转二进制,与读指针比较。若相等,则 FIFO 为空。
  • 第 102 行:双端口 RAM 实例化(代码中省略,实际工程中需添加)。RAM 的写地址为 wr_ptr,读地址为 rd_ptr。

时序与 CDC 约束

在 Vivado 2026.1 中,异步 FIFO 的约束核心是声明异步时钟组,并设置 false path 以避免工具分析跨时钟域路径。以下为 async_fifo.xdc 关键内容:

# 时钟定义
create_clock -name wr_clk -period 20.000 [get_ports {wr_clk}]
create_clock -name rd_clk -period 40.000 [get_ports {rd_clk}]

# 异步时钟组
set_clock_groups -asynchronous 
 -group [get_clocks -include_generated_clocks wr_clk] 
 -group [get_clocks -include_generated_clocks rd_clk]

# 跨时钟域路径 false path(可选,时钟组已隐含)
set_false_path -from [get_clocks wr_clk] -to [get_clocks rd_clk]
set_false_path -from [get_clocks rd_clk] -to [get_clocks wr_clk]

# 复位约束(异步复位)
set_property ASYNC_REG TRUE [get_cells -hierarchical -filter {NAME =~ *sync_bits*}]

逐行说明

  • 第 1-2 行:为 wr_clk 和 rd_clk 创建时钟,周期分别为 20ns(50MHz)和 40ns(25MHz)。
  • 第 5-7 行:使用 set_clock_groups 声明两个时钟组为异步关系。这是 Vivado 推荐的 CDC 约束方式,工具会自动忽略组间路径的时序分析。
  • 第 10-11 行:显式设置 false path,作为冗余约束确保安全。注意:若已使用 set_clock_groups,则 false path 可选,但保留可增加可读性。
  • 第 14 行:将同步器内的所有触发器标记为 ASYNC_REG,告知工具这些寄存器用于异步同步,不进行时序优化与检查。

验证与仿真

Testbench 需生成两个独立时钟,模拟写满、读空、同时读写等场景。以下为关键测试序列:

// tb_async_fifo.v - 测试序列片段
initial begin
 // 复位
 rst_n = 0;
 #100 rst_n = 1;
 #20;

 // 写入 16 个数据,验证满标志
 for (int i = 0; i < 16; i = i + 1) begin
 @(posedge wr_clk);
 wr_en = 1;
 data_in = i;
 end
 @(posedge wr_clk);
 wr_en = 0;
 // 此时 full 应为 1
 assert (full == 1) else $error("FIFO should be full");

 // 读取 16 个数据,验证空标志
 for (int i = 0; i < 16; i = i + 1) begin
 @(posedge rd_clk);
 rd_en = 1;
 // 检查数据顺序
 assert (data_out == i) else $error("Data mismatch at %0d", i);
 end
 @(posedge rd_clk);
 rd_en = 0;
 assert (empty == 1) else $error("FIFO should be empty");

 // 同时读写 8 个周期
 for (int i = 0; i < 8; i = i + 1) begin
 fork
 @(posedge wr_clk) wr_en = 1; data_in = $random;
 @(posedge rd_clk) rd_en = 1;
 join
 end
 #1000 $finish;
end

逐行说明

  • 第 3-5 行:异步复位,保持 100ns 后释放。
  • 第 8-13 行:连续写入 16 个数据(0-15),每个 wr_clk 上升沿写入一个。写入完成后检查 full 是否为 1。
  • 第 16-23 行:连续读取 16 个数据,验证输出顺序与写入一致。读取完成后检查 empty 是否为 1。
  • 第 26-32 行:同时进行写和读操作 8 个周期,使用 fork-join 模拟异步并发。数据随机写入,读取后不比对(仅验证无死锁)。
  • 第 33 行:仿真结束。

常见坑与排查

  • 格雷码指针未在跨时钟域前寄存:若直接将二进制指针同步,多位变化会导致亚稳态传播。必须先将指针转为格雷码并寄存一拍再同步。
  • 满/空标志判断逻辑错误:常见错误是直接用二进制指针比较。正确做法:在写时钟域用同步后的读指针判断满,在读时钟域用同步后的写指针判断空。且满条件需比较最高两位。
  • 约束遗漏导致时序违规:若未设置异步时钟组,Vivado 会分析跨时钟域路径,可能报出大量 setup 违规。务必在 xdc 中添加 set_clock_groups。
  • 仿真中 full/empty 跳变延迟:由于同步器引入 2 个时钟周期的延迟,full 与 empty 的跳变并非即时。这是正常现象,设计需容忍此延迟。

原理与设计说明

为什么用格雷码

跨时钟域同步时,若直接传输二进制指针(如从 4'b0111 变为 4'b1000),多位同时变化会导致同步器捕获到中间值(如 4'b1111),造成满/空判断错误。格雷码相邻状态仅一位变化,同步后最多出现一个比特的亚稳态,且两级触发器可将其概率降至极低(MTBF 满足要求)。

满标志的 MSB 翻转判断

FIFO 深度为 2^N,指针宽度为 N+1(多一位用于区分满与空)。当写指针超过读指针一圈时,二进制指针的最高位翻转,而格雷码的最高两位同时翻转。通过比较最高两位是否相反,可准确判断 FIFO 是否写满,避免误判。

扩展与优化

  • 深度扩展:通过修改 FIFO_DEPTH 参数,可支持任意 2^N 深度。注意指针宽度需相应调整。
  • 数据宽度扩展:修改 DATA_WIDTH 参数即可,不影响指针逻辑。
  • 性能优化:若需更高频率,可考虑在 RAM 输出端添加流水线寄存器,减少组合逻辑延迟。
  • 资源优化:对于小深度 FIFO,可用分布式 RAM 替代 Block RAM,减少 BRAM 占用。

参考与附录

完整工程代码(包括 dpram.v、sync_bits.v、async_fifo_top.v 及约束文件)可参考 Xilinx 官方应用笔记 XAPP1311 或相关开源仓库。建议在 Vivado 2026.1 中直接导入并运行综合、仿真与实现流程。

标签:
本文原创,作者:FPGA小白,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/46948.html
分享:
FPGA时序收敛中set_multicycle_path的实战指南:2026年Q2案例
FPGA时序收敛中set_multicycle_path的实战指南:2026年Q2案例上一篇
相关文章
总数:1.23K

FPGA 状态机编码方式对比:二进制、格雷码与独热码设计与选择指南

QuickStart(快速上手)本指南帮助FPGA设计者快速理解二进制、格雷码与独热码三种状态机编码方式的核心差异,并基于实际项目需求(状…
二牛学FPGA二牛学FPGA
技术分享
1个月前
0
0
57
0

FPGA面试高频问题:时序分析与代码风格实践指南

QuickStart:3步掌握面试核心考点本指南帮助你在30分钟内搭建一个可运行的时序分析示例,并理解面试中常见的代码风格陷阱。按以下步骤操作…
二牛学FPGA二牛学FPGA
技术分享
1个月前
0
0
41
0

2026年国产GPU与AI芯片在智算中心的大规模部署挑战

随着人工智能算力需求的指数级增长,智算中心已成为国家数字基础设施的核心。预计到2026年,国产GPU与AI芯片将迎来在智算中心规模化部署的关键窗…
二牛学FPGA二牛学FPGA
技术分享
1个月前
0
0
93
0

FPGA验证进阶:用UVM搭建你的高效“测试工厂”

嘿,朋友!你有没有发现,现在的FPGA设计越来越复杂,验证的工作量有时甚至比写代码本身还大?面对动辄几十万行的代码和复杂的交互逻辑,传统的测试方…
FPGA小白FPGA小白
技术分享
2个月前
0
0
93
0

FPGA实习面试指南:2026年低功耗设计与资源优化实践

QuickStart:快速掌握面试核心考点理解低功耗设计与资源优化的面试定位:面试官关注你能否在资源受限(如LUT、BRAM、DSP)和功耗敏…
二牛学FPGA二牛学FPGA
技术分享
23天前
0
0
37
0

数字IC前端工程师校招笔试面试核心考点解析与备考实施指南

本文旨在为有志于投身数字集成电路前端设计领域的应届毕业生,提供一份聚焦2026年校招趋势的系统性备考指南。我们将从快速建立知识框架入手,逐步深入…
二牛学FPGA二牛学FPGA
技术分享
1个月前
0
0
62
0
评论表单游客 您好,欢迎参与讨论。
加载中…
评论列表
总数:0
FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训
没有相关内容