FPGA 入门 27:BRAM vs 分布式 RAM——数组落到哪种资源
更新时间:2026-09-12。本文是
fpga/入门层第 29 篇。LUT 怎么当存储见 LUT 与 FF 深入,存储深讲见进阶 BRAM/URAM 存储设计,FIFO 见 FIFO 设计。
本文要回答的问题
- FPGA 里的「存储器」到底有哪几种物理形态:BRAM、分布式 RAM、触发器阵列、URAM 各自长什么样?
- 同样写一个 reg 数组,综合器凭什么把它推断成 BRAM、分布式 RAM 或一堆 FF,推断规则是什么?
- 同步读和异步读在代码上差哪一行,为什么这一行决定能不能进 BRAM,时序上又差了什么?
- 18Kb/36Kb 一块的 BRAM 怎样配置不同位宽和深度?双端口、简单双口、真双口有什么区别?
- ROM(段码表、字模、系数表)如何用数组和初始化文件推断,初始化数据在上电后从哪里来?
- ram_style 属性怎么写、优先级如何,什么情况下「强制 block」会被工具拒绝并报警?
- FIFO、行缓存、查找表、系数存储这四类典型场景该如何选存储资源?
一、两种存储资源
| BRAM(块存储器) | 分布式 RAM | |
|---|---|---|
| 物理 | 专用 18Kb/36Kb SRAM 块 | LUT 配置成小存储 |
| 容量 | 大(Kb~Mb) | 小(每 LUT 64 位) |
| 位置 | 固定列、数量有限 | 随逻辑分散 |
| 读 | 同步(带 1 拍延迟) | 可异步(组合读) |
| 双端口 | 原生支持 | 受限 |
| 占资源 | 不占 LUT/FF | 吃 LUT |
| 适合 | 帧缓存/FIFO/大缓冲/ROM 深表 | 小查找表/小 FIFO/浅缓冲 |
二、可综合 RAM 写法
module ram_template #(parameter DW=8, AW=8)(
input wire clk, we,
input wire [AW-1:0] wa, ra,
input wire [DW-1:0] wd,
output reg [DW-1:0] rd
);
(* ram_style = "block" *) reg [DW-1:0] mem [0:(1<<AW)-1];
always @(posedge clk) // 同步写
if (we) mem[wa] <= wd;
always @(posedge clk) // 同步读 -> BRAM(读晚1拍)
rd <= mem[ra];
// 想要分布式/异步读: assign rd = mem[ra]; (+ ram_style="distributed")
endmoduleROM:initial 里 $readmemh("table.hex", mem) 或 case 常量,浅→分布式、深→BRAM。
三、怎么选
- 大缓冲/帧存/FIFO/双端口/跨时钟域 → BRAM(URAM 更大);
- 小表/小 FIFO/几十~几百深 → 分布式 RAM,省 BRAM;
- 常量 ROM:浅表分布式,深表 BRAM;
- 看资源利用率:BRAM 紧张→浅存储切 distributed;LUT 紧张→能进 BRAM 全进 BRAM。
四、四种存储物理形态全景
在为数组选择归宿前,先建立 FPGA 存储资源的全景图。同一比特的数据,可能落在四种完全不同的硬件上,成本和行为差异极大:
- 触发器(FF):每个 slice 里几十个边沿寄存器,特点是读零延迟、可以任意复位、但每位都占一个 FF 和相连布线。几十个比特的状态用它最合适,几万个比特就会瞬间吃光寄存器资源。
- 分布式 RAM(Distributed RAM):6 输入 LUT 本质上是一个 64 选 1 的小查表 ROM,把输出写回就成了 64×1 的小 RAM;多个 LUT 位拼、级联可以组成更宽更深的存储。它分布在逻辑单元之间、布线距离短,支持异步读(地址经组合译码立刻输出),写仍然同步。适合几百到几千比特的小缓冲、小 FIFO、查找表。
- BRAM(Block RAM):独立的硬核存储块,Xilinx 7 系列每块 18Kb,可两块拼成 36Kb;位置固定成列,容量大且完全不占 LUT/FF;原生同步读、支持多种位宽深度配置和单口/双端口,还带可选输出寄存器、字节使能、ECC 等。几 KB 到几百 KB 的存储都应优先用它。
- URAM(UltraRAM):高端 UltraScale 系列提供的更大块存储(单块 288Kb),面向大型数据缓存和片上 SRAM 替代,入门器件通常没有。
此外还有 SRL(把 LUT 当移位寄存器用,见 LUT 与 FF)适合固定延迟线。选择顺序的直觉是:让数据尽量「待在离它用途最近、单位成本最低」的资源上——控制状态住 FF、小表住 LUT、大数据住 BRAM。让一块 18Kb 的 BRAM 只存 16 个状态是浪费,让几千个 FF 去存一帧图像同样是灾难。
五、综合器的推断规则:它怎样识别 RAM
综合器不是看到数组就给存储,而是根据数组的访问模式判断你想表达什么。可综合 RAM 的核心特征有三个:存储体是数组、写入发生在时钟沿并受写使能控制、读出通过运行时可变的地址索引。满足这些,工具才会把它识别为一个「存储器实例」而不是展开成逻辑。具体分叉大致如下:
- 数组元素只被赋常量、地址在编译时全部确定(如 case 逐项给常量),工具识别为 ROM,按大小和读方式放进分布式 ROM 或 BRAM;
- 同步写 + 异步读(连续赋值 assign 读 mem),无法映射 BRAM(BRAM 没有纯组合读出端口),通常进分布式 RAM;
- 同步写 + 同步读(读也在 always 时钟沿里锁存输出寄存器),且容量超过工具的阈值,进 BRAM;读数据因此比地址晚一个时钟周期,这是 BRAM 的固有流水延迟;
- 数组很小(例如深度只有 4~16)或只有少数几个地址被实际访问,工具可能认为 RAM 不划算,直接用 FF 实现;
- 给数组元素加复位、在同一写口上做无法对应硬件的混合行为、读出经过复杂组合逻辑,都会破坏推断,可能被摊成 FF/LUT。
不同厂商综合器的阈值略有差异,所以最可靠的做法不是猜,而是显式加属性并核对报告。综合日志会出现「inferring RAM」或「Ram inferred」之类的提示,资源报告中 RAMB18/RAMB36 计数和「LUT as Distributed RAM」行会如实告诉你落点。养成「写完数组先综合一次看落点」的习惯,比死记阈值有效得多。
六、同步读与异步读:一行代码的连锁后果
同一个存储,读出代码只改一行,硬件行为和时序却完全不同,这是初学者最常踩的认知坑。
异步读写作 assign rdata = mem[raddr]:地址信号经 LUT 译码直接选出数据,地址一变,经过一段组合延迟后输出就变,不需要时钟。优点是零时钟延迟、控制直观,做小表、小寄存器堆很方便;代价是地址到输出是组合路径,存储越大,译码 mux 越大,路径延迟越长,可能直接拖累工作频率;而且只有分布式 RAM 能实现这种读。
同步读写作时钟沿内 rdata <= mem[raddr]:时钟沿把当前地址锁存进 BRAM 内部,下一拍数据才出现在输出端。优点是命中专用 BRAM、时序干净(输出自带寄存器,几乎不增加关键路径)、功耗低;代价是读延迟固定一拍,访问方要按「发地址、等一拍、取数据」的节奏设计。很多控制 bug 都源于把 BRAM 当「同拍可读」用:地址和数据在同一拍比较,永远拿到的是旧地址的数据。调试时在波形上对齐地址、时钟沿和数据,记住 BRAM 输出比地址晚一拍,大量「数据错位一个周期」的问题立刻解释得通。
想同时拥有 BRAM 的容量和「看起来零延迟」的接口,可以在 BRAM 内置输出寄存器之外再在外部包一层简单的旁路(write-first / read-during-write 模式选择)或在控制器里预取地址;这些都是接口适配问题,存储本身的同步特性不会改变。
七、BRAM 的组织:位宽深度换算与端口模式
一块 18Kb BRAM 的总比特数固定,但宽度和深度可以灵活配置,例如 16K×1、8K×2、4K×4、2K×9、1K×18、512×36(带校验位),选哪种由数据宽度决定;需要更大容量时多块级联,工具通常自动完成。设计数组时心里要装着这笔账:一个 1024×8 的存储是 8Kb,一块 18Kb 绰绰有余;一个 1024×32 是 32Kb,要两块拼成 36Kb;数组深度或宽度略超一块边界就会多占一整块,利用率可能只有一半——必要时调整数据格式(如压缩位宽、打包存储)提高密度。
端口模式有三种常见形态:
- 单端口(SPM):一个口兼做读写,同一时刻只能访问一个地址,适合配置参数、低速表;
- 简单双端口(SDP):一个只读口、一个只写口,口宽和时钟还可不同,是数据缓冲、跨时钟域 FIFO 的主力;写口 A 持续流入、读口 B 持续流出,天然解耦生产者和消费者;
- 真双端口(TDP):两个口都能读能写,可以接两个时钟域、两种位宽,用于需要双向随机访问的共享缓存、乒乓缓冲控制等。
同址同时读写时,BRAM 有三种可配置语义:WRITE_FIRST(输出刚写入的新值)、READ_FIRST(输出旧值)、NO_CHANGE(读口保持不变),行为必须在代码/IP 配置里明确,仿真和上板行为都依赖它。跨时钟域使用时选「简单双口、两口独立时钟」的 BRAM 模式,这正是异步 FIFO 的物理基础(CDC 原理见进阶 异步 FIFO)。
八、ROM 与初始化:段码表、字模、系数从哪来
FPGA 的 BRAM/分布式 RAM 在配置完成时可以带初值,因此「只读表」本质就是从不写入的 RAM。两种可综合写法:
- 常量填充:在 initial 块中用 for 循环或逐行给数组赋常量(段码、协议表、小函数表),综合器识别为 ROM;浅表直接进 LUT,深表进 BRAM;
- 初始化文件:用
$readmemh/$readmemb在 initial 中从 .hex/.mem 文件读入(正弦表、滤波器系数、点阵字库),仿真和综合都支持;文件随比特流配置进存储,上电即就绪,不需要外部 Flash 搬运。
ROM 的同步/异步读选择与 RAM 相同:组合读适合小表(如 数码管段码),时序好、一拍即得;大表走 BRAM 同步读以保频率。若运行中需要偶尔更新「表内容」,就不能算 ROM 了,可以用 BRAM 双口:一口平时只读、更新时由控制器写入,兼顾查表和在线配置。初始化数据的位宽、行数必须和数组严格对应,$readmemh 读到的行数不足会让剩余地址保持初值 x,仿真会直接暴露,硬件则取决于配置初值,务必在 Testbench 里检查整张表。
九、四类典型场景的选型决策
把选型原则落到入门阶段最常遇到的四类存储上,可以形成条件反射式的判断:
其一是 FIFO。浅 FIFO(十几到几十深、位宽不大,如 UART 接收缓冲)用分布式 RAM 甚至 FF 都可以,读延迟低、省 BRAM;深 FIFO(几十深以上、高吞吐、跨时钟域)必须 BRAM:简单双端口、两口独立时钟天然适配异步 FIFO,Xilinx FIFO IP 默认就是 BRAM 实现(FIFO 设计见进阶 FIFO)。判断「深度」时算一下总比特数,几百比特以内通常可以放心用分布式。
其二是行缓存和帧缓存。图像处理的行缓存(一行 1920 像素、每像素 16~24 位,约 38~46Kb)一行就需要两三块 18Kb BRAM,用分布式 RAM 完全不可行;帧缓存(整帧几百万字节)片上 BRAM 都放不下,必须外挂 DDR。乒乓缓冲(两块缓冲交替读写)也是 BRAM/外部存储器的典型用法。
其三是查找表和小状态存储。数码管段码(16 项×8 位)、按键映射、协议状态表、CRC 表、小的三角函数表,这类几十到几百字节的表首选分布式 ROM/RAM:异步读一拍即得、时序简单、不浪费大块资源;若表很大(如 1024 点正弦表、汉字字库),转 BRAM 同步读。
其四是数据流水和寄存器堆。乘法器前后的延迟级、滤波器抽头缓存,若只是少数几级延迟,用 FF;若抽头很多且需要整行/整块访问,用 BRAM 组织成循环缓冲;CPU 类设计里的小型寄存器堆(32 个 32 位寄存器)常被推断为分布式 RAM(两读一写正好契合多端口小存储),而大缓存才用 BRAM。
资源紧张时要做「全局平衡」:BRAM 利用率接近上限时,把浅 FIFO、小表显式切到 distributed 以腾块;反之 LUT 利用率吃紧时,把几十深以上的缓冲都赶进 BRAM。不要孤立优化某一个模块,资源报告要在整个工程层面看(资源概念见 资源利用率)。
十、ram_style 属性、IP 与资源报告对账
显式控制推断结果用综合属性:在数组声明前加 (* ram_style = "block" *) 强制 BRAM,写 "distributed" 强制分布式,写 "registers" 强制触发器(一般仅调试用)。属性是「强烈建议/要求」而不是魔法:如果代码模式本身无法映射 BRAM(异步读、带复位、端口结构不符),加了 block 属性工具会给出「无法按属性推断、改用其他资源」的警告,必须回头改 RTL。Quartus 中对应的是 ramstyle 属性,含义相同。
不想手写推断代码时,可以用厂商 IP(Block Memory Generator、FIFO Generator)图形化配置深度位宽、端口数、读模式、初始文件,生成带仿真模型的核;代价是可移植性降低、接口更繁琐。入门建议两条腿走路:简单规则数组手写推断(理解原理、代码简洁),复杂配置(ECC、内置输出级、特殊双口、跨时钟)用 IP(IP 使用方法见 IP 核入门)。
每次综合后做一次「资源对账」:打开利用率报告,核对 RAMB36/RAMB18 数量是否与你设计的存储块数匹配;看 Slice LUT 中 Distributed RAM 占用是否符合预期;确认 FF 没有因为某个大数组意外暴涨。日志里搜 RAM 相关推断和警告。这一步只要一分钟,却能挡住「数组被摊成上万个触发器」这类最夸张的资源事故。实现后的存储还可以在版图报告里看到 BRAM 列被使用的位置,帮助理解数据在芯片上的物理布局。
十一、自查清单与练习
写完存储代码后逐项确认:
- 数组声明、写时序、读方式符合目标资源的推断范式,综合日志明确报出 RAM/ROM 推断;
- 想要 BRAM 时读写都是同步的,没有给 mem 加复位,读出数据按晚一拍设计控制逻辑;
- 想要异步读时明确用分布式 RAM,并评估地址译码路径对频率的影响;
- 总容量、位宽、深度与 BRAM 配置算得过来,没有无意中多占整块;
- 同址读写语义明确(write-first/read-first),仿真覆盖该情形;
- 双端口访问不会产生地址冲突导致的不确定值,跨时钟端口按 CDC 规则处理;
- ROM 初始化文件行数位宽正确,仿真完整读表核对;
- 资源报告中 RAMB、分布式 LUT、FF 三项变化与预期一致。
练习路径:先写一个 256×8 单口 RAM,分别用同步读和异步读综合两次,对比资源报告里 BRAM 和分布式 RAM 的差异,亲眼确认「一行代码改变硬件归宿」;再写一个 16 项数码管段码 ROM 和 1024 点正弦 ROM,体会浅表与深表的落点;接着用简单双口 BRAM 搭一个跨时钟域 FIFO(配合 CDC 原理);最后故意给大数组加复位和异步读,观察工具的警告和 FF 暴涨。存储是数据通路设计的地基,理解「代码模式如何被翻译为物理资源」,你就从「能写出数组」进阶到「能决定数组住在哪里」——这种资源意识是 FPGA 工程师和纯软件工程师的重要分野。
十二、BRAM 的时序、流水与功耗细节
实际工程中用好 BRAM 还要注意几个与时序、吞吐、功耗相关的细节,入门阶段了解直觉即可。
输出寄存器与流水级数:BRAM 内部读出数据本身锁存一拍,很多 BRAM/IP 还提供一个可选的「输出寄存器(DOA_REG)」,再加一级会让读延迟变成两拍,但时序显著变好、频率更高。高吞吐设计宁可多等一拍也要把 BRAM 跑满频,控制器相应把读等待做成两级。反之,如果逻辑简单、频率要求低,可以关闭输出级换短延迟。这个选择应当和流水线整体级数统一规划,不要让存储延迟和计算流水错位(流水思想见 时序优化)。
读写同址的三种模式前面已提到,还要补一个工程细节:WRITE_FIRST 模式下写当拍输出新值,常用于需要「写进去马上读得到」的反馈结构(如累加数组);READ_FIRST 输出旧值,适合不关心同址读写的高吞吐场景;NO_CHANGE 节省一点功耗。行为级仿真和真实 BRAM 在这里可能默认不同,必须显式配置并用 Testbench 覆盖同址读写,否则综合前后行为不一致极难排查。
使能与功耗:BRAM 的端口使能(ENA/ENB)不只是「能不能读写」,关掉未使用端口的使能能显著降低动态功耗;长时间不用整块存储时还可让其进入睡眠模式。字节使能(byte write enable)支持按字节写入,用于 32/64 位数据中只更新部分字节的场景,避免读改写。时钟使能还可用于只在有效事务时翻转内部逻辑。
地址与数据对齐:BRAM 只对落在其范围内的地址负责,系统侧的地址通常要先减去基地址、再按字宽度移位(字节地址到字地址);多块级联时由译码逻辑产生片选。这些「地址翻译」放在 BRAM 外的控制逻辑里,存储本身只管索引。跨时钟端口还要注意两侧位宽不同时的对齐方式(窄侧地址对应宽侧的高低半字),IP 配置页通常给出图示,读数据手册对应章节即可。
复位限制再强调一次:BRAM 的输出寄存器可以复位,但存储阵列内容不能被信号复位到任意值;因此代码中任何 if(rst) mem[i]<=0 都会破坏 BRAM 推断。需要「清空缓存」语义时,用一个控制状态机在复位解除后按地址顺序写入零(清空过程中标记不可用),或直接让读写指针归零、旧数据因不会被访问而失效(FIFO 常用这种巧妙做法)。这条限制和 复位篇「数据通路不复位」的原则互相印证。
十三、从资源报告读出门道
综合/实现后的 Utilization 报告是存储设计的体检表,学会读它比背参数更重要。关注三行:Block RAM Tile(RAMB36/RAMB18)报告块存储用量,点开还能看到每块的级联和配置位宽;Slice LUT 下的 Distributed RAM 行单独统计被用作存储的 LUT,和普通逻辑 LUT 区分;Registers 行若随数组大小线性暴涨,几乎必然是存储推断失败被摊成了触发器。
对照方法是先在纸上估算:每个 BRAM 实例期望占几块 18Kb、所有分布式存储大约多少 LUT、FF 应只增加地址/数据锁存这几十个;报告数字若差一个数量级,必有推断问题。再看综合日志中的信息行,Xst/Vivado 会明确写「inferring block RAM」或「found X-bit register」,警告中出现「ram_style ignored」「unsupported reset」就是属性没生效的直接证据。版本迭代时把资源数字记入工程笔记,某次改动后 RAMB 突然减少而 LUT 暴增,就能立刻定位到是哪个模块的写法变了。这种「估算—报告—日志」三点对账的习惯,同样适用于 DSP48 和 LUT/FF 的资源审计(见 DSP48 篇)。
十四、从行为描述到硬件原语:综合属性与推断纪律
想可靠地拿到 BRAM,关键在于理解综合器是"按模式推断"的——它不认你给变量起的名字,而是根据你读写 RAM 的代码结构判断该映射成块 RAM、分布式 RAM 还是寄存器阵列。因此同样是存数据,写法不同综合结果可能差出上千个 LUT。推断成(单端口/简单双端口/真双端口)BRAM 的典型要素是:时钟驱动的读或写、用变量作为地址索引一片连续存储(数组)、读写位宽与深度落在块 RAM 支持的配置内、同步读(输出打一拍)尤其容易被稳定识别。若代码里带异步读(地址一变输出立刻变)、复位了存储数组、用了不规则的写使能,综合器往往只能退而求其次用 LUT 搭分布式 RAM 或一堆触发器。
正因为模式识别有"灰色地带",工程实践常借助综合属性(synthesis attributes)把意图显式钉死,避免工具猜错或换版本后行为漂移。例如在数组声明前加 (* ram_style = "block" *)(Xilinx)或 (* ramstyle = "M20K" *)(Intel),明确要求用块 RAM;写成 "distributed" 则强制分布式 RAM。属性是"建议/约束"而非魔法——若代码结构根本无法实现成 BRAM(比如非要异步复位每个存储字),工具会忽略属性并报警,所以正确顺序是:先按可推断的范式写,再用属性固化选择,最后以综合日志和资源报告为准核对(报告里会明确列出 Block Memory 用了几块、是哪种端口模式)。还应避免两个极端:一是到处加属性却不看报告,结果属性被静默忽略仍以为上了 BRAM;二是完全靠工具默认、升级工具或改一行代码后 RAM 类型悄悄改变、时序和资源突然异常。建议把"关键存储的预期资源类型 + 报告实际值"写进评审检查项,深度、位宽、端口模式、读写延迟用 parameter 集中定义,这样换器件、改容量时只需调参数,推断模式也保持稳定。
十五、BRAM 应用地图:FIFO、行缓存与系数表
理解了 BRAM 的物理特性,再看 FPGA 设计里几类"内存大户",会发现它们都是围绕 BRAM 组织的,识别这些典型形态有助于在架构阶段就估准资源。第一类是FIFO。无论跨时钟域异步 FIFO 还是同步 FIFO,本质都是一块双端口存储(一个口写、一个口读)加读写指针和空满判断,存储体几乎必然落在 BRAM 上(浅 FIFO 才用分布式 RAM)。选 FIFO 时要估的是深度 × 位宽:比如深度 1024、宽 16 位就是 16Kbit,恰好一块常见 BRAM 的量级;深度过浅却选块 RAM 会浪费(一块物理 RAM 只存几十字节),过深过宽则要级联多块。Xilinx FIFO Generator / Intel 的 DCFIFO 帮你处理指针、格雷码和空满标志,但深度必须按"突发长度与消费速率之差"来算,这正是数据流设计里的基本功。
第二类是行缓存(line buffer)与窗口缓存,常见于图像处理。做 3×3 卷积或中值滤波时,需要同时拿到相邻几行的像素,标准做法是用若干条 BRAM 行缓存把前几行整行存下来,配合移位寄存器在一个时钟流里吐出一个 3×3 窗口。这里 BRAM 容量直接由行宽×像素位宽×行数决定(1920×8bit×2 行约 30Kbit),是资源估算的核心;把"帧缓存"和"行缓存"分清也很重要——整帧(1920×1080)远超片上 RAM,必须放片外 DDR,片上只放少数几行,这种层级划分体现了片上 BRAM"快而小"的定位。第三类是系数表/查找表(LUT-as-ROM)与初始化文件:FIR 滤波器系数、三角函数表、数码管段码、协议常量表,可用 initial 块读入或用 $readmemh/$readmemb 从 .coe/.mif/.hex 加载,把 BRAM/ROM 配成只读查表,避免用海量组合逻辑硬算。第四类是数据缓冲与协议包缓存:网络包、ADC 采样块、DMA 前后的 Ping-Pong 缓冲,常用双端口 BRAM 一边接收一边处理来隐藏延迟。学会在架构图上先标出"哪几块数据需要缓存、各自深度×位宽多少、需要几个端口、要不要跨时钟域",再对照器件手册的 BRAM 规格(每块容量、可配端口、位宽组合),你就能在动手写代码前相当准确地预估块 RAM 用量,避免到了实现后期才发现片上内存不够、被迫返工改数据通路。
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 大数组被摊成 FF | LUT/FF 爆掉 | 按 RAM 范式写、查报告 |
| 异步读想要 BRAM | 推不出 BRAM | 读也放时钟沿(晚 1 拍) |
| 给 mem 加复位 | 放弃 BRAM 改 FF | 数据存储不复位 |
| 没看资源报告 | 用错资源不自知 | 查 RAMB / LUT as Memory |
| 浅表占 BRAM | 浪费块存储 | ram_style=distributed |
| 深 FIFO 用分布式 | LUT 被吃光 | 深 FIFO 用 BRAM |
相关与延伸
入门:LUT 与 FF 深入(分布式 RAM/SRL)、时钟与复位(数据不复位)。 深入:进阶 BRAM/URAM 存储设计(单双口/ROM/乒乓)、FIFO 设计、异步 FIFO。
一句话总结
RTL 里写的数组会被综合进两种物理存储:BRAM 是专用 18Kb/36Kb 大块 SRAM——容量大、原生双端口、同步读(带一拍延迟)、不占逻辑资源;分布式 RAM 是用 LUT 拼的小存储——容量小、可异步组合读、随逻辑分散但吃 LUT。推断成 RAM 的范式是:reg 数组 + 时钟沿按写使能写入;同步读(读也在 posedge)→BRAM,异步读(assign rd=mem[ra])→分布式;用 (* ram_style=\"block\"/\"distributed\" *) 强制指定。大数组推不出 BRAM 的最常见原因是异步读(BRAM 不支持)和给存储加了复位(BRAM 无法复位,会被摊成成千上万 FF 爆资源)。选择原则:帧缓存/FIFO/双端口/跨域/深 ROM 用 BRAM,小查找表/浅缓冲/小 FIFO 用分布式,最后一定看资源利用率报告里的 RAMB 和 'LUT as Distributed RAM' 对账。
上一篇:29 FPGA 时钟与复位(时钟树/PLL/异步断言同步释放) | 下一篇:31 DSP48 初认识(乘法乘加硬块) | FPGA 系列目录