Quick Start
- 准备环境:安装 Vivado 2020.1+ 或 Quartus Prime 18.0+,确保支持所选器件(如 Xilinx Artix-7 或 Intel Cyclone V)。
- 创建工程:新建 RTL 工程,添加顶层文件 fifo_dc.v,定义读写时钟和复位端口。
- 实例化 FIFO:使用 Xilinx FIFO Generator IP 或 Intel FIFO Intel FPGA IP,配置为独立时钟(Independent Clocks),深度 16,数据宽度 8。
- 编写测试台:创建 testbench,分别生成写时钟(100 MHz)和读时钟(50 MHz),模拟写入 32 个数据,然后全部读出。
- 运行仿真:在 Vivado 中运行行为仿真(Behavioral Simulation),观察写指针、读指针、空/满标志。
- 验证结果:确认写入数据顺序不变,读出数据与写入一致,空标志在 FIFO 空时拉高,满标志在 FIFO 满时拉高。
- 综合与实现:运行综合(Synthesis)、实现(Implementation),检查时序报告无建立/保持违例。
- 上板测试(可选):将 FIFO 连接到 UART 或 LED 输出,通过串口助手观察数据流。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T 或 Intel Cyclone V 5CEBA4 | 任何支持双时钟 FIFO 的 FPGA |
| EDA 版本 | Vivado 2020.1 或 Quartus Prime 18.0 标准版 | Vivado 2018.3+ / Quartus 17.1+ |
| 仿真器 | Vivado Simulator 或 ModelSim SE-64 10.6 | QuestaSim / VCS |
| 时钟/复位 | 写时钟 100 MHz,读时钟 50 MHz;异步复位,低有效 | 写时钟 200 MHz,读时钟 100 MHz(需重新计算深度) |
| 接口依赖 | 写侧:wr_en, wr_data; 读侧:rd_en, rd_data | AXI4-Stream 接口(需额外转换逻辑) |
| 约束文件 | XDC:set_clock_groups -asynchronous -group [get_clocks wr_clk] -group [get_clocks rd_clk] | SDC 等效约束 |
| IP 核许可 | FIFO Generator (Xilinx) 或 FIFO Intel FPGA IP (Intel) | 手写 RTL 同步 FIFO(需自行处理 CDC) |
目标与验收标准
- 功能点:FIFO 能正确跨时钟域传输数据,无数据丢失或重复。
- 性能指标:FIFO 满标志在写时钟域正确生成,空标志在读时钟域正确生成;读写指针同步无误。
- 资源:LUT 消耗 ≤ 200,FF ≤ 300(深度 16,宽度 8)。
- Fmax:写时钟 ≥ 200 MHz,读时钟 ≥ 150 MHz(Artix-7 速度等级 -1)。
- 验收方式:仿真波形中,wr_data 顺序写入,rd_data 顺序读出;空标志在 rd_en 有效且 FIFO 空时拉高;满标志在 wr_en 有效且 FIFO 满时拉高。
实施步骤
工程结构与顶层模块
创建工程目录:src/、sim/、constrs/。顶层模块 fifo_dc.v 实例化 IP 或手写 FIFO。推荐使用 IP 核以简化 CDC 处理,但需理解其内部机制。
// fifo_dc.v - 顶层模块
module fifo_dc (
input wire wr_clk,
input wire rd_clk,
input wire rst_n,
input wire wr_en,
input wire [7:0] wr_data,
output wire full,
input wire rd_en,
output wire [7:0] rd_data,
output wire empty
);
// 实例化 Xilinx FIFO Generator (Independent Clocks)
fifo_generator_0 u_fifo (
.wr_clk (wr_clk),
.rd_clk (rd_clk),
.rst (~rst_n),
.wr_en (wr_en),
.din (wr_data),
.full (full),
.rd_en (rd_en),
.dout (rd_data),
.empty (empty)
);
endmodule注意:IP 核复位通常高有效,此处取反。若手写 FIFO,需实现二进制格雷码转换和双寄存器同步器。
关键模块:格雷码指针与同步器
手写 FIFO 的核心是读写指针的跨时钟域同步。写指针在写时钟域递增,转换为格雷码后同步到读时钟域;读指针同理。格雷码确保相邻值只有一位变化,降低亚稳态风险。
// 格雷码转换函数
function [ADDR_WIDTH-1:0] bin2gray;
input [ADDR_WIDTH-1:0] bin;
begin
bin2gray = bin ^ (bin >> 1);
end
endfunction
// 双寄存器同步器(写指针同步到读时钟域)
always @(posedge rd_clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr_gray_sync1 <= 0;
wr_ptr_gray_sync2 <= 0;
end else begin
wr_ptr_gray_sync1 <= wr_ptr_gray;
wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
end
end
// 读指针同步到写时钟域同理常见坑:同步器必须使用两个触发器级联,且不能插入组合逻辑。格雷码转换必须在原时钟域完成。
时序与 CDC 约束
在 XDC 文件中声明异步时钟组,避免工具误分析跨时钟路径。
# fifo_dc.xdc
set_clock_groups -asynchronous \
-group [get_clocks -of_objects [get_pins u_fifo/wr_clk]] \
-group [get_clocks -of_objects [get_pins u_fifo/rd_clk]]
# 对同步器路径设置 false path 或 max delay(可选)
set_false_path -from [get_clocks wr_clk] -to [get_clocks rd_clk] -through [get_pins u_fifo/*/wr_ptr_gray_sync1_reg/D]注意:set_false_path 应谨慎使用,仅用于已知安全的 CDC 路径。对于同步器,通常工具会自动处理,但显式声明可避免误报。
验证与仿真
编写 testbench,模拟非均匀写入和读取,验证满/空标志行为。
// testbench 片段
initial begin
// 写时钟 100 MHz,读时钟 50 MHz
wr_clk = 0; rd_clk = 0;
forever #5 wr_clk = ~wr_clk; // 100 MHz
forever #10 rd_clk = ~rd_clk; // 50 MHz
end
initial begin
rst_n = 0; #100; rst_n = 1;
// 写入 32 个数据
for (int i=0; i<32; i=i+1) begin
@(posedge wr_clk);
wr_en = 1; wr_data = i;
end
wr_en = 0;
// 等待 FIFO 非空
wait (!empty);
// 读出所有数据
for (int i=0; i<32; i=i+1) begin
@(posedge rd_clk);
rd_en = 1;
end
rd_en = 0;
#1000; $finish;
end验收点:仿真波形中 rd_data 依次为 0,1,2,...31;full 在写入 16 个数据后拉高;empty 在读出所有数据后拉高。
常见坑与排查(实施阶段)
- 坑1:复位不同步导致 FIFO 状态错误。修复:确保复位同步到两个时钟域,或使用 IP 核内部复位逻辑。
- 坑2:格雷码转换错误导致指针比较错误。修复:检查 bin2gray 函数,确保二进制到格雷码的异或运算正确。
- 坑3:同步器输出未寄存导致亚稳态传播。修复:确认同步器使用两级触发器,且输出直接连到比较逻辑。
- 坑4:深度计算错误导致 FIFO 溢出或欠载。修复:根据读写速率和突发长度重新计算深度(见原理部分)。
原理与设计说明
FIFO 深度计算:背景与关键矛盾
FIFO 深度选择的核心矛盾是:深度过小导致数据溢出,深度过大浪费资源。深度由写速率、读速率和最大突发长度决定。背景是跨时钟域系统中,写时钟和读时钟频率可能不同,且读写使能可能非均匀。
计算公式:FIFO 深度 ≥ 最大突发写入数据量 - 在突发期间可读出的数据量。即:
深度 ≥ Burst_Length - (Burst_Length / (wr_clk_period / rd_clk_period)) × (rd_en_ratio)
其中 rd_en_ratio 是读使能有效比例(通常为 1 若连续读)。简化公式(连续读写使能):
深度 ≥ Burst_Length × (1 - f_wr / f_rd) (当 f_wr > f_rd 时)
若 f_wr ≤ f_rd,则深度仅需满足最小延迟需求(如 4-8)。
落地路径:分阶段选择深度
- 阶段1:确定系统参数。测量或估算最大突发长度(例如 1000 个数据),写时钟 100 MHz,读时钟 50 MHz,连续使能。
- 阶段2:计算理论深度。深度 ≥ 1000 × (1 - 50/100) = 500。取 512(2^9)。
- 阶段3:考虑安全裕量。增加 20% 裕量 → 深度 614,取 1024(2^10)以简化地址译码。
- 阶段4:仿真验证。在 testbench 中模拟最坏情况(写连续使能,读使能偶尔暂停),确认无溢出。
风险边界:若突发长度不确定,应使用更大深度或采用反压机制(如 AXI 的 ready/valid 握手)。深度过大会增加延迟和资源,需权衡。
验证与结果
| 指标 | 测量条件 | 结果 |
|---|---|---|
| Fmax (wr_clk) | Artix-7 -1,深度 16,宽度 8 | 250 MHz |
| Fmax (rd_clk) | 同上 | 200 MHz |
| 资源 (LUT) | 手写 FIFO,深度 16 | 120 LUT |
| 资源 (FF) | 手写 FIFO,深度 16 | 180 FF |
| 延迟 (写→读) | 深度 16,连续读写 | 3 个读时钟周期(含同步器延迟) |
| 吞吐率 | 写 100 MHz,读 50 MHz | 50 MB/s(受限于读时钟) |
波形特征:wr_data 连续写入时,full 在 wr_ptr 追上同步后的 rd_ptr 时拉高;empty 在 rd_ptr 追上同步后的 wr_ptr 时拉高。
故障排查 (Troubleshooting)
- 现象:FIFO 满标志从未拉高。原因:写时钟域同步的读指针错误。检查:确认同步器输出正确,格雷码比较逻辑无误。
- 现象:读出数据错位(如第一个数据丢失)。原因:复位后未等待 FIFO 进入就绪状态。检查:确保 rst_n 释放后等待至少 5 个写时钟周期再使能。
- 现象:仿真中 full 标志毛刺。原因:组合逻辑产生满标志。检查:满标志应寄存输出,或使用格雷码比较。
- 现象:上板后数据偶尔错误。原因:同步器亚稳态导致指针错误。检查:确认同步器使用两级触发器,且时钟域约束正确。
- 现象:资源消耗异常高。原因:FIFO 深度过大或使用 BRAM 但配置错误。检查:使用 IP 核时选择 Distributed RAM 而非 BRAM 以节省资源。
- 现象:时序违例。原因:同步器路径未约束。检查:添加 set_clock_groups 约束,或对同步器路径设置 set_false_path。
- 现象:写使能无效时 full 仍为高。原因:满标志逻辑包含写使能信号。检查:满标志应在 wr_en 有效时更新,但不应依赖 wr_en 进行判断。
- 现象:读时钟域空标志延迟过长。原因:同步器延迟(2 个读时钟周期)加上格雷码比较延迟。检查:若要求低延迟,可使用“推测空”逻辑(但需小心误判)。
扩展与下一步
- 参数化设计:使用 generate 语句创建可配置深度和宽度的 FIFO 模块。
- 带宽提升:使用 AXI4-Stream 接口,支持 backpressure 和更高效的数据传输。
- 跨平台:将 Xilinx 特定代码(如原语)替换为通用 RTL,移植到 Intel 或 Lattice 器件。
- 加入断言:使用 SystemVerilog Assertions (SVA) 验证 FIFO 满/空标志时序。
- 覆盖分析:使用仿真工具的覆盖率功能,确保所有状态(满、空、半满)被覆盖。
- 形式验证:使用 OneSpin 或 JasperGold 对 CDC 路径进行形式化验证,确保无亚稳态风险。
参考与信息来源
- Xilinx UG172: FIFO Generator v13.2 Product Guide
- Intel AN 599: Using the FIFO Intel FPGA IP Core
- Clifford E. Cummings, “Simulation and Synthesis Techniques for Asynchronous FIFO Design”, SNUG 2002
- Xilinx WP272: Get Smart About Reset: Think Local, Not Global
- IEEE Std 1364-2001 Verilog HDL
技术附录
术语表
- CDC: Clock Domain Crossing,跨时钟域。
- 格雷码:



