IP核使用(二):FIFO 与 Block RAM 配置详解(附异步 FIFO 跨时钟域实操)

本文是《FPGA入门到实战》专栏第17篇。第9篇介绍了 PLL IP核,本篇进入另外两个高频使用的 IP 核:FIFO GeneratorBlock Memory Generator。FIFO 是跨时钟域数据传输和流量缓冲的标准解决方案,BRAM IP 则是大容量片内存储的推荐用法。本篇从原理到 Vivado 配置,再到时序波形分析,最后完成异步 FIFO 跨时钟域实操。


1. FIFO 核心概念与应用场景

1.1 什么是 FIFO

FIFO(First In First Out,先进先出队列) 是一种缓冲存储器,数据按写入顺序读出。FIFO 有两个独立接口:

  • 写端口wr_clkwr_endinfull
  • 读端口rd_clkrd_endoutempty
写端口                       读端口
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 ModeStandard(读使能后下一拍出数据)或 First Word Fall Through(读使能同拍出数据)
Write Width写端口数据宽度(bits)
Write DepthFIFO 深度(条数,建议取 2 的幂次)
Read Width读端口数据宽度(可与写宽度不同)

First Word Fall Through(FWFT)模式说明

Standard 模式:rd_en=1 → 下一拍 dout 有效
FWFT    模式:rd_en=1 → 本拍   dout 有效(类似"预取")

工程中 FWFT 使用更广泛,读时序更直观。

2.4 Status Flags 页面配置

标志信号含义使用注意
fullFIFO 已满,不能写入写操作前必须检查
emptyFIFO 为空,不能读出读操作前必须检查
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 为低:本拍数据写入 FIFO
  • wr_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 NameIP 实例名称自定义
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 ModeWrite First / Read First / No Change(影响写后立即读的数据)
Enable Port TypeAlways 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 仿真验证关键点

验证点预期行为
连续写入直到 fullfull 拉高后继续写入,数据应丢失(或上游停止写)
写入后等待读出读出数据与写入数据顺序一致
空 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值