本文是《FPGA入门到实战》专栏第17篇。第9篇介绍了 PLL IP核,本篇进入另外两个高频使用的 IP 核:FIFO Generator 和 Block Memory Generator。FIFO 是跨时钟域数据传输和流量缓冲的标准解决方案,BRAM IP 则是大容量片内存储的推荐用法。本篇从原理到 Vivado 配置,再到时序波形分析,最后完成异步 FIFO 跨时钟域实操。
IP核使用(二):FIFO 与 Block RAM 配置详解
1. FIFO 核心概念与应用场景
1.1 什么是 FIFO
FIFO(First In First Out,先进先出队列) 是一种缓冲存储器,数据按写入顺序读出。FIFO 有两个独立接口:
- 写端口:
wr_clk、wr_en、din、full - 读端口:
rd_clk、rd_en、dout、empty
写端口 读端口
din ──→ [F I F O 队列] ──→ dout
wr_en rd_en
full ←─ ─→ empty
1.2 同步 FIFO vs 异步 FIFO
| 类型 | 读写时钟 | 用途 |
|---|---|---|
| 同步 FIFO | 同一时钟 | 同一时钟域内的流量缓冲,解耦生产者和消费者速率 |
| 异步 FIFO | 独立时钟 | 跨时钟域数据传输(最重要的用途) |
1.3 典型应用场景
场景一:跨时钟域(异步 FIFO)
模块A(100MHz)──写──→ [异步FIFO] ──读──→ 模块B(50MHz)
两个时钟域之间直接连线会导致亚稳态,异步 FIFO 内部用格雷码同步技术安全传递数据。
场景二:流量缓冲(同步 FIFO)
突发数据源(突发写)──→ [同步FIFO] ──→ 慢速消费者(匀速读)
突发写入的数据暂存在 FIFO 中,消费者按需读取,无需两端严格同步。
场景三:数据宽度转换
32-bit 写端口 ──→ [FIFO] ──→ 8-bit 读端口(Vivado FIFO 支持位宽不对称)
2. Vivado FIFO Generator 配置
2.1 打开 IP Catalog
Flow Navigator → IP Catalog → 搜索 FIFO Generator → 双击打开配置界面。
2.2 Basic 页面配置
| 选项 | 说明 | 推荐值 |
|---|---|---|
| Interface Type | 接口类型 | Native(基本接口,适合大多数场景) |
| FIFO Implementation | 存储实现方式 | Block RAM(容量大)或 Distributed RAM(小容量) |
| Synchronization Stages | 异步 FIFO 同步级数 | 2(默认,推荐) |
| FIFO Type | 同步/异步 | Independent Clocks(异步)或 Common Clock(同步) |
2.3 Native Ports 页面配置
| 选项 | 说明 |
|---|---|
| Read Mode | Standard(读使能后下一拍出数据)或 First Word Fall Through(读使能同拍出数据) |
| Write Width | 写端口数据宽度(bits) |
| Write Depth | FIFO 深度(条数,建议取 2 的幂次) |
| Read Width | 读端口数据宽度(可与写宽度不同) |
First Word Fall Through(FWFT)模式说明:
Standard 模式:rd_en=1 → 下一拍 dout 有效
FWFT 模式:rd_en=1 → 本拍 dout 有效(类似"预取")
工程中 FWFT 使用更广泛,读时序更直观。
2.4 Status Flags 页面配置
| 标志信号 | 含义 | 使用注意 |
|---|---|---|
full | FIFO 已满,不能写入 | 写操作前必须检查 |
empty | FIFO 为空,不能读出 | 读操作前必须检查 |
almost_full | 差 N 个就满(可配置阈值) | 用于提前流控 |
almost_empty | 差 N 个就空 | 用于提前流控 |
wr_data_count | 当前写入了多少条数据 | 可选 |
rd_data_count | 当前可读多少条数据 | 可选 |
3. FIFO 读写时序详解
3.1 写时序(Standard 模式)
wr_clk ___│‾│___│‾│___│‾│___│‾│___│‾│___
wr_en _______│‾‾‾‾‾‾‾‾‾│_________________
din _______[D0][D1][D2]_________________
full _____________________________│‾│____
wr_en高且full为低:本拍数据写入 FIFOwr_en高但full为高:写操作被忽略,数据丢失(必须避免)
// 正确的写操作:先检查 full
always @(posedge wr_clk or negedge rst_n) begin
if (!rst_n)
wr_en <= 1'b0;
else
wr_en <= has_data && !fifo_full; // full 时停止写入
end
3.2 读时序(Standard 模式)
rd_clk ___│‾│___│‾│___│‾│___│‾│___│‾│___
rd_en _______│‾‾‾‾‾‾‾‾‾│_________________
dout ___________ [D0][D1]______________ ← 读使能后延迟1拍出数
empty ___________________________│‾│____
rd_en高且empty为低:下一拍dout有效rd_en高但empty为高:读出的数据无意义(必须避免)
3.3 读时序(FWFT 模式)
rd_clk ___│‾│___│‾│___│‾│___│‾│___
rd_en _______│‾‾‾‾‾‾‾‾‾│___________
dout ____[D0][D1][D2]______________ ← 与 rd_en 同拍有效
valid _______│‾‾‾‾‾‾‾‾‾│___________
FWFT 模式下通常配合 valid 信号使用(Vivado FIFO Generator 可选配 valid 输出)。
4. Block Memory Generator 配置
4.1 打开配置
IP Catalog → 搜索 Block Memory Generator → 双击。
4.2 Basic 页面
| 选项 | 说明 | 常用值 |
|---|---|---|
| Component Name | IP 实例名称 | 自定义 |
| Memory Type | 存储类型 | Single Port RAM / Simple Dual Port RAM / True Dual Port RAM / Single Port ROM |
| ECC Options | 错误纠正 | No ECC(大多数情况) |
4.3 Port A Options 页面
| 选项 | 说明 |
|---|---|
| Write Width | 数据位宽(如 8、16、32) |
| Write Depth | 存储深度(条数) |
| Operating Mode | Write First / Read First / No Change(影响写后立即读的数据) |
| Enable Port Type | Always Enabled(简单场景)或 Use ENA Pin(可控使能) |
| Primitives Output Register | 输出寄存器(增加1拍延迟,但改善时序) |
4.4 ROM 初始化(COE 文件)
BRAM 作为 ROM 使用时,通过 .coe 文件初始化内容:
; COE 文件格式(sin_table.coe)
memory_initialization_radix = 16; ; 十六进制
memory_initialization_vector =
80, 8C, 98, A3, AE, B8, C1, CA, ; 正弦波采样值
D2, DA, E0, E6, EB, EF, F2, F4,
F5, F5, F4, F2, EF, EB, E6, E0,
DA, D2, CA, C1, B8, AE, A3, 98;
在 Other Options 页面加载此 COE 文件即可。
5. 实操:用异步 FIFO 处理跨时钟域数据
5.1 系统架构
[模块A: 100MHz] [模块B: 50MHz]
数据产生 ──wr_en/din──→ [异步FIFO] ──rd_en/dout──→ 数据消费
←──full────── ───────empty──→
5.2 顶层连接代码
module cdc_demo (
input wire clk_100m, // 100MHz 时钟域
input wire clk_50m, // 50MHz 时钟域
input wire rst_n,
// 源端(100MHz)接口
input wire [7:0] data_in,
input wire data_valid,
// 目标端(50MHz)接口
output wire [7:0] data_out,
output wire data_out_valid
);
wire fifo_full, fifo_empty;
wire fifo_wr_en, fifo_rd_en;
// 写端:data_valid 且未满时写入
assign fifo_wr_en = data_valid && !fifo_full;
// 读端:FIFO 非空时读出(FWFT 模式)
assign fifo_rd_en = !fifo_empty;
assign data_out_valid = !fifo_empty;
// 异步 FIFO 例化(由 FIFO Generator 生成)
fifo_async_8x256 u_fifo (
.wr_clk (clk_100m),
.rd_clk (clk_50m),
.rst (~rst_n),
.wr_en (fifo_wr_en),
.din (data_in),
.full (fifo_full),
.rd_en (fifo_rd_en),
.dout (data_out),
.empty (fifo_empty)
);
endmodule
5.3 数据丢失保护
当写端速率长期高于读端,FIFO 最终会填满。生产系统中需要设计**反压(Back-pressure)**机制:
// 写端:full 时暂停数据产生(反压上游)
assign upstream_ready = !fifo_full;
// 或者:almost_full 时提前发出警告
assign flow_ctrl_warning = fifo_almost_full;
5.4 仿真验证关键点
| 验证点 | 预期行为 |
|---|---|
| 连续写入直到 full | full 拉高后继续写入,数据应丢失(或上游停止写) |
| 写入后等待读出 | 读出数据与写入数据顺序一致 |
| 空 FIFO 读操作 | empty 为高时 rd_en 不应拉高 |
| 满 FIFO 写操作 | full 为高时 wr_en 不应拉高 |
| 异步时钟关系 | 不同时钟边沿下,数据传输无亚稳态(由IP保证) |
6. FIFO 使用常见错误
6.1 错误:满时仍写入
// 错误:没有检查 full,导致数据覆盖
always @(posedge wr_clk) begin
if (has_data) begin
wr_en <= 1'b1; // full 时继续写,FIFO 丢数据!
din <= data;
end
end
// 正确:
always @(posedge wr_clk) begin
wr_en <= has_data && !fifo_full;
din <= data;
end
6.2 错误:异步 FIFO 复位方式不对
Vivado 异步 FIFO 的复位是异步高有效(通常为 rst),且两个时钟域都需要稳定运行后才能释放复位:
// 正确:用 PLL locked 信号确保两个时钟都稳定后释放复位
assign fifo_rst = ~(rst_n & pll_100m_locked & pll_50m_locked);
6.3 错误:Standard 模式下误用 FWFT 时序
两种读模式的时序不同,使用前必须确认 IP 配置的是哪种模式,否则读出的数据会错位1拍。
附:本篇完整源码
cdc_demo.v
// ============================================================
// 文件名 : cdc_demo.v
// 描述 : 异步 FIFO 实现跨时钟域数据传输
// 写端 100MHz,读端 50MHz,FIFO 深度 256,宽度 8bit
// ============================================================
module cdc_demo (
input wire clk_100m,
input wire clk_50m,
input wire rst_n,
input wire [7:0] data_in,
input wire data_valid,
output wire [7:0] data_out,
output wire data_out_valid
);
wire fifo_full, fifo_empty;
assign data_out_valid = !fifo_empty;
fifo_async_8x256 u_fifo (
.wr_clk (~clk_100m), // 注意:部分 IP 用时钟反沿,按 IP 文档确认
.rd_clk (~clk_50m),
.rst (~rst_n),
.wr_en (data_valid && !fifo_full),
.din (data_in),
.full (fifo_full),
.rd_en (!fifo_empty),
.dout (data_out),
.empty (fifo_empty)
);
endmodule
tb_cdc_demo.v
// ============================================================
// 文件名 : tb_cdc_demo.v
// 描述 : 跨时钟域 FIFO 仿真 Testbench
// ============================================================
`timescale 1ns/1ps
module tb_cdc_demo;
reg clk_100m = 0, clk_50m = 0, rst_n = 0;
reg [7:0] data_in = 0;
reg data_valid = 0;
wire [7:0] data_out;
wire data_out_valid;
always #5 clk_100m = ~clk_100m; // 100MHz
always #10 clk_50m = ~clk_50m; // 50MHz
cdc_demo u_dut (
.clk_100m(clk_100m), .clk_50m(clk_50m), .rst_n(rst_n),
.data_in(data_in), .data_valid(data_valid),
.data_out(data_out), .data_out_valid(data_out_valid)
);
integer i;
initial begin
repeat(8) @(posedge clk_100m); #2;
rst_n = 1;
// 在 100MHz 域连续写入 16 个字节
for (i = 0; i < 16; i = i + 1) begin
@(posedge clk_100m); #2;
data_in = i[7:0];
data_valid = 1'b1;
end
@(posedge clk_100m); #2;
data_valid = 1'b0;
// 等待 50MHz 域读完
repeat(100) @(posedge clk_50m);
$display("=== Simulation Done ===");
$finish;
end
// 监控读端输出
always @(posedge clk_50m) begin
if (data_out_valid)
$display("[%0t] Read: data_out=0x%02h", $time, data_out);
end
endmodule
:FIFO 与 Block RAM 配置详解(附异步 FIFO 跨时钟域实操)&spm=1001.2101.3001.5002&articleId=159737287&d=1&t=3&u=7c2f98fbb00f4faa84f4b8aa91da31cc)
6069

被折叠的 条评论
为什么被折叠?



