FPGA 中 FIFO 资源的占用以及计算:别只看深度和位宽

📅 2026/7/5 8:21:33
FPGA 中 FIFO 资源的占用以及计算:别只看深度和位宽
1. 前言很多 FPGA 新人第一次用 FIFO IP 的时候容易有一个误解FIFO 占用资源 位宽 × 深度。这个说法只对了一半。比如一个 FIFO 配置成data_width 8 bit depth 1024理论存储量确实是8 × 1024 8192 bit 8 Kb但综合之后它不一定只占 8Kb 的 FPGA 资源。原因很简单FPGA 里面的存储资源不是按 1 bit、1 bit 零售给你的而是按固定颗粒度分配的。常见 FPGA 里会有 BRAM、LUTRAM、UltraRAM 等资源。以 Xilinx/AMD 7 系列、UltraScale 系列为例常见 Block RAM 是 36Kb 颗粒度也可以拆成两个 18Kb 使用。官方文档中也明确提到UltraScale 架构中的一个 36Kb block RAM 可以配置成两个独立的 18Kb RAM。(AMD 文档)所以FIFO 资源计算不能只看理论 bit 数还要看它最后映射到什么硬件资源。2. FIFO 资源主要由哪几部分组成一个 FIFO 不是只有一块存储器它至少包含下面几类资源FIFO 存储阵列 读写指针 full/empty 判断逻辑 可选计数逻辑如果是异步 FIFO还会多出异步 FIFO 存储阵列 写指针 读指针 格雷码转换 跨时钟同步寄存器 full/empty 判断逻辑可以简单画成这样写时钟域 读时钟域 ---------------- ---------------- wr_en -| 写指针 wr_ptr | | 读指针 rd_ptr |- rd_en wr_data | | | |- rd_data --------------- --------------- | | v v ----------------------------------------------- | FIFO Memory | | BRAM / LUTRAM / URAM / REG | ----------------------------------------------- ^ ^ | | full / almost_full empty / almost_empty其中最占资源的是FIFO Memory也就是存储阵列。指针、比较器、同步寄存器这些通常占 LUT 和 FF。除非 FIFO 很浅否则它们不是资源大头。3. 第一步先算理论存储量FIFO 最基本的理论存储量公式是FIFO_BITS DATA_WIDTH × FIFO_DEPTH举几个例子FIFO 配置理论存储量说明8bit × 2562048 bit 2Kb很小可能用 LUTRAM8bit × 10248192 bit 8Kb可能用 LUTRAM也可能用 18Kb BRAM16bit × 102416384 bit 16Kb接近一个 18Kb BRAM32bit × 102432768 bit 32Kb接近一个 36Kb BRAM64bit × 102465536 bit 64Kb通常需要多个 BRAM128bit × 2048262144 bit 256Kb已经是较大的 FIFO这里要注意一个点理论存储量只是“你真正存了多少数据”不是“FPGA 最后给你分配了多少资源”。比如 8bit × 1024 是 8Kb但如果工具用一个 18Kb BRAM 实现那实际占用就是一个 18Kb 资源颗粒利用率只有8192 / 18432 ≈ 44.4%这就是为什么小 FIFO 用 BRAM 有时候看起来很浪费。4. 第二步看 FIFO 会映射到什么资源FIFO 常见实现资源有四类。4.1 用寄存器实现如果 FIFO 很浅比如8bit × 4 16bit × 8 32bit × 4这种 FIFO 用寄存器也能做。优点是简单、快。缺点是深度一大FF 数量会很夸张。比如32bit × 1024 32768 bit如果全部用寄存器实现理论上至少需要 32768 个 FF还不算控制逻辑。这显然不划算。所以工程里一般不会用寄存器实现中大型 FIFO。4.2 用 LUTRAM / Distributed RAM 实现LUTRAM 也叫 Distributed RAM本质上是用 FPGA 逻辑资源里的 LUT 来做小 RAM。Vivado 综合可以根据 HDL 写法把 RAM 映射成 distributed RAM 或 block RAM。AMD 官方文档中也提到Vivado synthesis 能识别不同 RAM coding styles并映射到 distributed RAM 或 block RAM。(AMD 文档)LUTRAM 适合小深度 FIFO 小容量 FIFO 对延迟比较敏感的局部缓存 不想浪费 BRAM 的场景比如8bit × 64 512 bit 8bit × 128 1024 bit 16bit × 64 1024 bit这种 FIFO 用一个 18Kb BRAM 就太浪费了通常用 LUTRAM 更合适。但 LUTRAM 也不是免费资源。它会消耗 LUT可能影响逻辑布局和时序。4.3 用 BRAM 实现BRAM 是最常见的 FIFO 存储资源。以 Xilinx/AMD 常见结构为例1 个 36Kb BRAM ≈ 36864 bit 1 个 18Kb BRAM ≈ 18432 bit官方 RAMB36E1/RAMB36E2 文档中也能看到36Kb block RAM 可以按不同宽度和深度配置例如 32K×1、16K×2、8K×4、4K×9、2K×18、1K×36Simple Dual Port 模式下还可以做到 72bit 宽度。(AMD 文档)FIFO 本质上通常是一个写端口 一个读端口所以很多 FIFO 会使用 Simple Dual Port RAM 结构。BRAM 适合中等深度 FIFO 较大位宽 FIFO 跨模块缓存 数据流缓冲 异步 FIFO比如16bit × 1024 32bit × 1024 64bit × 512 64bit × 1024这些都比较适合用 BRAM。4.4 用 UltraRAM 实现UltraRAM 一般用于更大的缓存比如大深度 FIFO 图像行缓存 大数据包缓存 高吞吐数据缓存普通新人刚开始写小型模块时不一定马上会用到 UltraRAM。但如果 FIFO 已经到几十万 bit 甚至几 Mbit就要考虑是不是该用 UltraRAM或者干脆走外部 DDR。5. 第三步用 18Kb / 36Kb 粒度估算 BRAM 数量先给一个粗略估算公式。如果按 18Kb BRAM 估算BRAM18_NUM ≈ ceil(DATA_WIDTH × FIFO_DEPTH / 18432)如果按 36Kb BRAM 估算BRAM36_NUM ≈ ceil(DATA_WIDTH × FIFO_DEPTH / 36864)其中ceil() 表示向上取整举例例 18bit × 1024FIFO_BITS 8 × 1024 8192 bit按 18Kb 估算8192 / 18432 0.44 向上取整 1 个 18Kb BRAM所以结果可能是1 个 RAMB18也可能工具觉得太小直接用 LUTRAM。这个要看综合策略和 FIFO IP 配置。例 216bit × 1024FIFO_BITS 16 × 1024 16384 bit按 18Kb 估算16384 / 18432 0.89 向上取整 1 个 18Kb BRAM这个利用率就比较高。所以 16bit × 1024 这种 FIFO用一个 18Kb BRAM 比较合理。例 332bit × 1024FIFO_BITS 32 × 1024 32768 bit按 36Kb 估算32768 / 36864 0.89 向上取整 1 个 36Kb BRAM所以常见结果是1 个 RAMB36或者2 个 RAMB18这两个在容量上是接近的。例 464bit × 1024FIFO_BITS 64 × 1024 65536 bit按 36Kb 估算65536 / 36864 1.78 向上取整 2 个 36Kb BRAM所以这个 FIFO 大概率会占用多个 BRAM。例 5128bit × 2048FIFO_BITS 128 × 2048 262144 bit按 36Kb 估算262144 / 36864 7.11 向上取整 8 个 36Kb BRAM所以这个 FIFO 已经不是“小 FIFO”了。如果设计里有很多这种 FIFOBRAM 很快就会被吃完。6. 为什么实际结果可能和公式不一样上面的公式只是粗略估算。实际综合结果可能不同主要有下面几个原因。6.1 BRAM 有固定宽度和深度组合BRAM 不是任意位宽、任意深度都能完美拼出来。比如一个 36Kb BRAM 可以配置成类似下面的组合32K × 1 16K × 2 8K × 4 4K × 9 2K × 18 1K × 36 512 × 72这就会带来一个问题FIFO 的理论 bit 数虽然够但宽度和深度不一定刚好匹配 BRAM 的最佳模式。比如8bit × 4096 32768 bit理论上不到 36Kb看起来一个 36Kb BRAM 就够。而且因为 36Kb BRAM 支持类似 4K × 9 的模式所以它确实比较适合。但是如果换成20bit × 2048 40960 bit理论上超过 36Kb就不是一个 36Kb BRAM 能完整放下的了。6.2 FIFO 深度可能会被工具调整很多 FIFO IP 内部更喜欢 2 的整数次幂深度比如16 32 64 128 256 512 1024 2048 4096如果你设置一个非 2 次幂深度比如depth 1000工具内部可能按接近的硬件结构实现最终可用深度、实际深度、资源占用不一定和你想象完全一致。AMD Embedded FIFO Generator 文档也提醒过FIFO 的 actual depth 不一定总是等于 GUI 里选择的 depth因为实际深度会受实现方式和功能选项影响。(AMD 文档)所以工程上建议FIFO 深度尽量用 2 的整数次幂这样指针设计简单资源估算也更直观。6.3 FIFO 额外功能会增加逻辑资源下面这些选项一般不会明显增加存储 bit 数但会增加 LUT/FF 或控制逻辑almost_full almost_empty data_count prog_full prog_empty first word fall through简称 FWFT ECC 异步时钟 复位输出寄存器比如almost_full本质上就是对计数值或指针差值做判断assign almost_full (fifo_count ALMOST_FULL_TH);这会多一些比较逻辑。异步 FIFO 还要做格雷码转换和跨时钟同步bin pointer - gray pointer - sync - gray compare这些都会消耗 FF 和 LUT。但一般来说只要 FIFO 不特别小资源大头仍然是存储阵列。6.4 小 FIFO 可能被综合成 LUTRAM比如你写了一个8bit × 64 512 bit理论上非常小。如果工具用 1 个 18Kb BRAM那利用率只有512 / 18432 ≈ 2.8%这太浪费。所以综合工具可能会把它实现成 LUTRAM。如果你明确希望用 BRAM可以加综合属性。以 Vivado 为例RAM_STYLE可以指导工具把 RAM 推断成 block、distributed、registers 或 ultra 等类型。(AMD 文档)例如(* ram_style block *) reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];或者(* ram_style distributed *) reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];注意这只是指导综合工具不是所有情况下都 100% 按你的想法实现。7. 如果 FPGA 手册里写的是 6Kb RAM该怎么算有些 FPGA 器件或工具报告里可能不是 18Kb、36Kb而是 6Kb、9Kb、10Kb、20Kb 等颗粒度。这时计算方法完全一样。假设某器件的单个 RAM block 是6Kb 6 × 1024 6144 bit那么一个 FIFO16bit × 1024 16384 bit粗略需要ceil(16384 / 6144) ceil(2.67) 3 个 6Kb RAM block再比如8bit × 512 4096 bit粗略需要ceil(4096 / 6144) 1 个 6Kb RAM block但是仍然要注意实际占用还要看 RAM block 支持的宽度/深度组合不要只看总 bit 数。正确思路是第一步算 DATA_WIDTH × DEPTH 第二步看目标 FPGA 的 RAM block 粒度比如 6Kb、18Kb、36Kb 第三步向上取整 第四步再结合宽度/深度模式判断是否会多占 第五步最终以综合报告为准8. 常见 FIFO 资源估算表下面这张表可以作为新人快速判断FIFO 配置理论 bit 数可能实现方式资源判断8 × 64512 bitLUTRAM / REG很小不建议浪费 BRAM8 × 2562048 bitLUTRAM小 FIFO8 × 10248192 bitLUTRAM / 1 个 18Kb BRAM看综合策略16 × 102416384 bit1 个 18Kb BRAM利用率较高32 × 102432768 bit1 个 36Kb BRAM常见配置64 × 102465536 bit2 个 36Kb BRAM中等 FIFO128 × 2048262144 bit约 8 个 36Kb BRAM大 FIFO256 × 40961048576 bit大量 BRAM / URAM需要谨慎这张表不是绝对结果而是工程估算。最终资源要看综合报告 report_utilization IP 配置 Summary 目标 FPGA 型号 FIFO 是否异步 是否开启 FWFT / data_count / ECC9. 一个简单同步 FIFO 例子下面给一个简化版同步 FIFO用来说明资源推断。注意这段代码是教学用模板不是为了替代厂商 FIFO IP。实际项目里异步 FIFO、复杂 AXI-Stream FIFO优先用成熟 IP 或经过验证的模块。module sync_fifo #( parameter DATA_WIDTH 8, parameter ADDR_WIDTH 10, // DEPTH 2^ADDR_WIDTH parameter RAM_STYLE block // block or distributed )( input wire clk, input wire rst_n, input wire wr_en, input wire [DATA_WIDTH-1:0] wr_data, output wire full, input wire rd_en, output reg [DATA_WIDTH-1:0] rd_data, output wire empty, output wire [ADDR_WIDTH:0] fifo_count ); localparam DEPTH (1 ADDR_WIDTH); (* ram_style RAM_STYLE *) reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; reg [ADDR_WIDTH-1:0] wr_ptr; reg [ADDR_WIDTH-1:0] rd_ptr; reg [ADDR_WIDTH:0] count; wire do_wr; wire do_rd; assign full (count DEPTH); assign empty (count 0); assign fifo_count count; // 简化写法满的时候不写空的时候不读 assign do_wr wr_en !full; assign do_rd rd_en !empty; always (posedge clk or negedge rst_n) begin if (!rst_n) begin wr_ptr {ADDR_WIDTH{1b0}}; rd_ptr {ADDR_WIDTH{1b0}}; rd_data {DATA_WIDTH{1b0}}; count {(ADDR_WIDTH1){1b0}}; end else begin // write if (do_wr) begin mem[wr_ptr] wr_data; wr_ptr wr_ptr 1b1; end // read if (do_rd) begin rd_data mem[rd_ptr]; rd_ptr rd_ptr 1b1; end // count update case ({do_wr, do_rd}) 2b10: count count 1b1; 2b01: count count - 1b1; default: count count; endcase end end endmodule如果参数是DATA_WIDTH 32 ADDR_WIDTH 10那么DEPTH 2^10 1024 FIFO_BITS 32 × 1024 32768 bit这类 FIFO 大概率会推断到 BRAM而不是纯寄存器。如果改成DATA_WIDTH 8 ADDR_WIDTH 6那么DEPTH 64 FIFO_BITS 8 × 64 512 bit这类 FIFO 用 LUTRAM 更合理。10. 看综合报告时重点看什么以 Vivado 为例综合或实现后可以看report_utilization重点关注几类资源LUT LUT as Memory Register / FF Block RAM Tile RAMB18 RAMB36 URAM如果你看到LUT as Memory 增加很多说明 FIFO 或 RAM 可能被实现成 LUTRAM。如果你看到RAMB18 / RAMB36 增加说明 FIFO 使用了 BRAM。如果你原本希望用 BRAM结果 LUTRAM 暴涨要检查1. FIFO 深度是否太小 2. HDL 写法是否不利于 BRAM 推断 3. 是否加了不合适的复位逻辑 4. 是否需要添加 ram_style block 5. 是否应该直接使用 FIFO Generator / XPM FIFO如果你原本希望节省 BRAM结果一个小 FIFO 占了 1 个 18Kb BRAM也要检查1. 是否强制 ram_style block 2. IP 是否选择了 Block RAM 实现 3. 是否可以改成 Distributed RAM 4. FIFO 深度是否可以降低11. FIFO 资源优化建议11.1 小 FIFO 不要随便用 BRAM比如8bit × 32 8bit × 64 16bit × 32这些 FIFO 很小用 LUTRAM 更合适。如果项目里有几十个这种小 FIFO都强制用 BRAM会非常浪费。11.2 中型 FIFO 优先用 BRAM比如16bit × 1024 32bit × 1024 64bit × 512这些用 BRAM 通常比较合理。11.3 大 FIFO 要警惕 BRAM 被吃光比如128bit × 4096 524288 bit 512Kb 256bit × 4096 1048576 bit 1Mb如果 FPGA 总 BRAM 本来就不多这种 FIFO 放几个就很危险。这时要重新考虑FIFO 深度是否真的需要这么大 能不能用更小的 backpressure 能不能拆成分级缓存 能不能用 URAM 能不能外接 DDR11.4 不要盲目加 data_count很多新人喜欢把 FIFO 的各种信号都打开full empty almost_full almost_empty wr_data_count rd_data_count prog_full prog_empty但实际模块可能只用到full empty或者最多加一个almost_full信号开得越多控制逻辑越复杂。不是不能开而是要知道它们不是免费的。11.5 异步 FIFO 优先用成熟 IP异步 FIFO 不只是一个 RAM。它还涉及格雷码 跨时钟同步 full 判断 empty 判断 亚稳态风险 复位释放顺序新人工程师不建议一上来就手写异步 FIFO 用在正式项目。学习可以手写项目里优先用厂商 FIFO IP、XPM FIFO 或公司内部验证过的模块。12. 新人最容易犯的几个错误错误 1只算 bit 数不看 RAM 颗粒度比如8bit × 1024 8Kb新人可能觉得只占 8Kb。但实际可能占1 个 18Kb BRAM或者用 LUTRAM。这要看实现方式。错误 2以为 FIFO 深度写多少硬件就刚好是多少实际 FIFO IP 可能因为实现方式、FWFT、独立时钟、RAM 结构等原因actual depth 和配置深度不完全一样。工程上要看 IP summary 和综合报告。错误 3在复位里清空整个 mem有些新人会这样写integer i; always (posedge clk or negedge rst_n) begin if (!rst_n) begin for (i 0; i DEPTH; i i 1) mem[i] 0; end end这对大 RAM 不推荐。真实 BRAM 通常不会因为你的复位信号在一个周期内把所有存储单元清零。你真正需要复位的一般是wr_ptr rd_ptr count valid 标志 输出寄存器不是整个 RAM 数组。错误 4小 FIFO 到处例化最后 LUT 爆了如果项目里很多小 FIFO 都用 LUTRAM单个看不多但数量一多LUT 也会被吃掉。所以资源优化不是一句“BRAM 好”或者“LUTRAM 好”。正确判断是小容量优先 LUTRAM 中容量优先 BRAM 大容量考虑 BRAM / URAM / DDR错误 5只看 FIFO 个数不看位宽两个 FIFO 数量都是 1 个但资源完全可能差很多8bit × 1024 8Kb 128bit × 1024 128KbFIFO 资源主要看位宽 × 深度不是只看 FIFO 个数。13. 总结FIFO 资源计算可以按下面这个流程走1. 先算理论存储量 DATA_WIDTH × FIFO_DEPTH 2. 判断实现资源 REG / LUTRAM / BRAM / URAM 3. 如果用 BRAM 按 18Kb、36Kb 或目标 FPGA 的 RAM block 粒度估算 4. 注意实际资源不是单纯 bit 数 还受宽度、深度、RAM 模式、FIFO 功能影响 5. 最后一定看综合报告 report_utilization / IP summary一句话总结FIFO 占多少资源不是只看“深度 × 位宽”而是要看它最后映射到了 FPGA 里的哪种存储颗粒LUTRAM、18Kb BRAM、36Kb BRAM、6Kb RAM block还是 UltraRAM。工程里最实用的判断是小 FIFO优先 LUTRAM别浪费 BRAM 中 FIFO优先 BRAM资源和时序都比较均衡 大 FIFO警惕 BRAM 消耗必要时考虑 URAM 或 DDR 异步 FIFO优先用成熟 IP不要随便手写上板能把这些想清楚FIFO 资源占用基本就不会再看懵了。