Quick Start
- 准备环境:安装Vivado 2024.2或更高版本(或Quartus Prime 24.x),确保支持SystemVerilog-2012。
- 创建工程:新建RTL工程,目标器件选Xilinx Artix-7 XC7A35T或Intel Cyclone 10 LP,时钟频率示例:写时钟200 MHz,读时钟100 MHz。
- 编写异步FIFO顶层模块:实例化双端口RAM、写指针同步器、读指针同步器、空/满标志生成逻辑。
- 编写testbench:模拟写时钟域连续写入256个数据,读时钟域以半速率读出,检查数据完整性。
- 运行行为仿真:观察空/满标志翻转时刻,确认无亚稳态传播导致的数据错误。
- 综合与实现:检查资源报告(LUT/FF/BRAM),确认Fmax满足写时钟200 MHz要求。
- 上板验证(可选):用ILA抓取写/读指针差值,在200 MHz写/100 MHz读下连续运行1小时无数据丢失。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | BRAM 18Kb块,支持双端口 | Intel Cyclone 10 LP / Lattice ECP5 |
| EDA版本 | Vivado 2024.2 | 支持SystemVerilog-2012,综合优化好 | Quartus Prime 24.x / Synplify Premier |
| 仿真器 | Vivado Simulator 2024.2 | 内置,支持波形对比 | ModelSim SE-64 2024.2 / Verilator 5.0 |
| 写时钟频率 | 200 MHz(示例) | 典型高速写场景 | 按实际需求调整 |
| 读时钟频率 | 100 MHz(示例) | 典型慢读场景 | 按实际需求调整 |
| 接口依赖 | 无外部IP | 纯RTL实现 | 可使用XPM_FIFO宏(但本设计自研) |
| 约束文件 | XDC/SDC | 需约束两个时钟域、异步路径false_path | SDC for Quartus |
目标与验收标准
- 功能点:异步FIFO正确实现空/满标志,无亚稳态传播导致的数据丢失或重复。
- 性能指标:写时钟200 MHz下Fmax ≥ 200 MHz,读时钟100 MHz下Fmax ≥ 100 MHz,资源消耗≤ 2个BRAM18K + 200 LUT + 200 FF。
- 验收方式:仿真中写入256个递增数据,读出后逐字比对,无错误;上板后ILA抓取指针差值,连续运行1小时无数据丢失。
实施步骤
1. 工程结构与顶层模块
创建工程目录结构:src/rtl/ 存放RTL,src/tb/ 存放testbench,src/constr/ 存放约束文件。顶层模块命名为 async_fifo,参数化深度 DEPTH、数据宽度 DATA_WIDTH。
// async_fifo.v
module async_fifo #(
parameter DEPTH = 16, // FIFO深度,必须为2的幂
parameter DATA_WIDTH = 8 // 数据位宽
)(
input wire wr_clk,
input wire wr_rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
output wire full,
input wire rd_clk,
input wire rd_rst_n,
input wire rd_en,
output wire [DATA_WIDTH-1:0] rd_data,
output wire empty
);
localparam PTR_WIDTH = $clog2(DEPTH) + 1; // 指针位宽,多1位用于满/空判断
wire [PTR_WIDTH-1:0] wr_ptr, rd_ptr;
wire [PTR_WIDTH-1:0] wr_ptr_gray, rd_ptr_gray;
wire [PTR_WIDTH-1:0] wr_ptr_sync, rd_ptr_sync;
// 双端口RAM实例化
dp_ram #(
.DEPTH(DEPTH),
.DATA_WIDTH(DATA_WIDTH)
) u_ram (
.wr_clk(wr_clk),
.wr_en(wr_en),
.wr_addr(wr_ptr[PTR_WIDTH-2:0]),
.wr_data(wr_data),
.rd_clk(rd_clk),
.rd_en(rd_en),
.rd_addr(rd_ptr[PTR_WIDTH-2:0]),
.rd_data(rd_data)
);
// 写指针生成
ptr_gen #(
.PTR_WIDTH(PTR_WIDTH)
) u_wr_ptr (
.clk(wr_clk),
.rst_n(wr_rst_n),
.inc(wr_en),
.ptr(wr_ptr)
);
// 读指针生成
ptr_gen #(
.PTR_WIDTH(PTR_WIDTH)
) u_rd_ptr (
.clk(rd_clk),
.rst_n(rd_rst_n),
.inc(rd_en),
.ptr(rd_ptr)
);
// 二进制转格雷码
bin2gray #(
.WIDTH(PTR_WIDTH)
) u_wr_b2g (
.bin(wr_ptr),
.gray(wr_ptr_gray)
);
bin2gray #(
.WIDTH(PTR_WIDTH)
) u_rd_b2g (
.bin(rd_ptr),
.gray(rd_ptr_gray)
);
// 同步器:写指针格雷码同步到读时钟域
sync_2ff #(
.WIDTH(PTR_WIDTH)
) u_sync_wr (
.clk(rd_clk),
.rst_n(rd_rst_n),
.din(wr_ptr_gray),
.dout(wr_ptr_sync)
);
// 同步器:读指针格雷码同步到写时钟域
sync_2ff #(
.WIDTH(PTR_WIDTH)
) u_sync_rd (
.clk(wr_clk),
.rst_n(wr_rst_n),
.din(rd_ptr_gray),
.dout(rd_ptr_sync)
);
// 空/满标志生成
flag_gen #(
.PTR_WIDTH(PTR_WIDTH)
) u_flag (
.wr_clk(wr_clk),
.wr_rst_n(wr_rst_n),
.wr_ptr(wr_ptr),
.rd_ptr_sync(rd_ptr_sync),
.full(full),
.rd_clk(rd_clk),
.rd_rst_n(rd_rst_n),
.rd_ptr(rd_ptr),
.wr_ptr_sync(wr_ptr_sync),
.empty(empty)
);
endmodule逐行说明
- 第1行:模块声明,使用parameter实现深度和位宽可配置。
- 第2-3行:参数DEPTH必须为2的幂,因为指针是二进制循环;DATA_WIDTH可任意。
- 第4-14行:端口列表,包含写时钟域和读时钟域的所有信号。
- 第16行:计算指针位宽,多1位用于区分满与空(当指针二进制最高位不同且其余位相同时为满,完全相同时为空)。
- 第18-22行:内部连线声明,包括二进制指针、格雷码指针、同步后的指针。
- 第24-37行:实例化双端口RAM,写地址使用wr_ptr的低PTR_WIDTH-1位,读地址使用rd_ptr的低PTR_WIDTH-1位。
- 第39-47行:实例化写指针生成器,每个写时钟上升沿且wr_en有效时递增。
- 第49-57行:实例化读指针生成器,每个读时钟上升沿且rd_en有效时递增。
- 第59-67行:将二进制指针转换为格雷码,减少跨时钟域翻转位。
- 第69-77行:写指针格雷码通过两级触发器同步到读时钟域。
- 第79-87行:读指针格雷码通过两级触发器同步到写时钟域。
- 第89-101行:实例化空/满标志生成模块,在各自时钟域内比较指针。
2. 关键子模块实现
子模块包括:双端口RAM(dp_ram)、指针生成器(ptr_gen)、二进制转格雷码(bin2gray)、两级同步器(sync_2ff)、标志生成器(flag_gen)。以下给出核心代码。
// ptr_gen.v
module ptr_gen #(
parameter PTR_WIDTH = 4
)(
input wire clk,
input wire rst_n,
input wire inc,
output reg [PTR_WIDTH-1:0] ptr
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
ptr <= 0;
else if (inc)
ptr <= ptr + 1'b1;
end
endmodule逐行说明
- 第1-2行:参数化指针位宽,与顶层PTR_WIDTH一致。
- 第3-8行:端口声明,inc为递增使能。
- 第10-15行:同步复位,复位时指针清零;inc有效时指针加1,自然溢出实现循环。
// bin2gray.v
module bin2gray #(
parameter WIDTH = 4
)(
input wire [WIDTH-1:0] bin,
output wire [WIDTH-1:0] gray
);
assign gray = (bin >> 1) ^ bin;
endmodule逐行说明
- 第1-2行:参数WIDTH定义位宽。
- 第3-6行:格雷码公式:gray = bin右移1位异或bin,组合逻辑实现。
// sync_2ff.v
module sync_2ff #(
parameter WIDTH = 4
)(
input wire clk,
input wire rst_n,
input wire [WIDTH-1:0] din,
output wire [WIDTH-1:0] dout
);
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 <= din;
sync_reg2 <= sync_reg1;
end
end
assign dout = sync_reg2;
endmodule逐行说明
- 第1-2行:参数WIDTH定义同步器位宽。
- 第3-8行:端口声明,din为异步输入,dout为同步输出。
- 第10行:两级触发器寄存。
- 第12-18行:复位时清零;每个时钟沿将din打入sync_reg1,再将sync_reg1打入sync_reg2。
- 第20行:输出第二级寄存器的值,有效降低亚稳态概率。
// flag_gen.v
module flag_gen #(
parameter PTR_WIDTH = 4
)(
input wire wr_clk,
input wire wr_rst_n,
input wire [PTR_WIDTH-1:0] wr_ptr,
input wire [PTR_WIDTH-1:0] rd_ptr_sync,
output reg full,
input wire rd_clk,
input wire rd_rst_n,
input wire [PTR_WIDTH-1:0] rd_ptr,
input wire [PTR_WIDTH-1:0] wr_ptr_sync,
output reg empty
);
// 满标志:写时钟域,比较wr_ptr与同步后的rd_ptr_sync
always @(posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n)
full <= 1'b0;
else begin
// 满条件:wr_ptr与rd_ptr_sync的最高位不同,其余位相同
if ((wr_ptr[PTR_WIDTH-1] != rd_ptr_sync[PTR_WIDTH-1]) &&
(wr_ptr[PTR_WIDTH-2:0] == rd_ptr_sync[PTR_WIDTH-2:0]))
full <= 1'b1;
else
full <= 1'b0;
end
end
// 空标志:读时钟域,比较rd_ptr与同步后的wr_ptr_sync
always @(posedge rd_clk or negedge rd_rst_n) begin
if (!rd_rst_n)
empty <= 1'b1;
else begin
// 空条件:rd_ptr与wr_ptr_sync完全相等
if (rd_ptr == wr_ptr_sync)
empty <= 1'b1;
else
empty <= 1'b0;
end
end
endmodule逐行说明
- 第1-2行:参数PTR_WIDTH。
- 第3-12行:端口声明,分为写时钟域和读时钟域两部分。
- 第14-25行:满标志生成逻辑,在写时钟域内比较写指针与同步后的读指针。满条件:最高位不同(表示写指针多循环了一圈),且低位相同。
- 第27-38行:空标志生成逻辑,在读时钟域内比较读指针与同步后的写指针。空条件:完全相等。
3. 时序约束与CDC处理
在XDC文件中,将跨时钟域路径设置为false_path,避免工具对同步器路径进行时序分析。
# async_fifo.xdc
# 写时钟域到读时钟域的路径(同步器输入)设为false_path
set_false_path -from [get_clocks wr_clk] -to [get_clocks rd_clk]
# 读时钟域到写时钟域的路径(同步器输入)设为false_path
set_false_path -from [get_clocks rd_clk] -to [get_clocks wr_clk]
# 对同步器输出路径做正常时序分析(默认即可)逐行说明
- 第1行:注释,说明约束文件用途。
- 第2-3行:将写时钟域到读时钟域的所有路径设为false_path,因为同步器输入可能来自异步时钟域,无需满足建立/保持时间。
- 第4-5行:将读时钟域到写时钟域的路径设为false_path。
- 第6行:同步器输出到后续逻辑的路径由工具自动分析,无需额外约束。
4. 验证与仿真
编写testbench,模拟写时钟200 MHz、读时钟100 MHz,写入256个递增数据,读出后比对。
// tb_async_fifo.v
`timescale 1ns / 1ps
module tb_async_fifo;
parameter DEPTH = 16;
parameter DATA_WIDTH = 8;
reg wr_clk, wr_rst_n;
reg rd_clk, rd_rst_n;
reg wr_en, rd_en;
reg [DATA_WIDTH-1:0] wr_data;
wire [DATA_WIDTH-1:0] rd_data;
wire full, empty;
async_fifo #(
.DEPTH(DEPTH),
.DATA_WIDTH(DATA_WIDTH)
) uut (
.wr_clk(wr_clk),
.wr_rst_n(wr_rst_n),
.wr_en(wr_en),
.wr_data(wr_data),
.full(full),
.rd_clk(rd_clk),
.rd_rst_n(rd_rst_n),
.rd_en(rd_en),
.rd_data(rd_data),
.empty(empty)
);
// 写时钟生成:200 MHz
initial wr_clk = 0;
always #2.5 wr_clk = ~wr_clk;
// 读时钟生成:100 MHz
initial rd_clk = 0;
always #5 rd_clk = ~rd_clk;
// 写测试数据
integer i;
reg [DATA_WIDTH-1:0] expected;
reg [7:0] error_count;
initial begin
// 复位
wr_rst_n = 0;
rd_rst_n = 0;
wr_en = 0;
rd_en = 0;
wr_data = 0;
error_count = 0;
#20;
wr_rst_n = 1;
rd_rst_n = 1;
#10;
// 写入256个数据(16深度FIFO会多次满/空翻转)
for (i = 0; i < 256; i = i + 1) begin
@(posedge wr_clk);
while (full) @(posedge wr_clk); // 等待非满
wr_en <= 1;
wr_data <= i;
@(posedge wr_clk);
wr_en <= 0;
end
// 等待所有数据被读出
#100;
$finish;
end
// 读数据并比对
always @(posedge rd_clk) begin
if (!rd_rst_n) begin
rd_en <= 0;
expected <= 0;
end else begin
if (!empty) begin
rd_en <= 1;
@(posedge rd_clk);
rd_en <= 0;
if (rd_data !== expected) begin
$display("ERROR: expected %d, got %d", expected, rd_data);
error_count = error_count + 1;
end
expected <= expected + 1;
end else begin
rd_en <= 0;
end
end
end
// 结束检查
initial begin
#500;
if (error_count == 0)
$display("TEST PASSED: All data matched.");
else
$display("TEST FAILED: %d errors.", error_count);
end
endmodule逐行说明
- 第1行:timescale设置1ns精度。
- 第3-5行:模块声明,参数与顶层一致。
- 第7-13行:信号声明。
- 第15-30行:实例化UUT。
- 第32-33行:写时钟周期5ns(200 MHz)。
- 第35-36行:读时钟周期10ns(100 MHz)。
- 第38-41行:变量声明。
- 第43-58行:初始化过程,复位后写256个数据,每次写前等待full为低。
- 第60-76行:读过程,每次读前等待empty为低,读出后与期望值比对,错误计数。
- 第78-83行:结束检查,打印结果。
常见坑与排查
- <strong



