Yosys ECP5 BRAM 推断失败:异步读为何没有进入 DP16KD
更新时间:2026-10-31。FAQ 速查页,属于 EDA 报错排查系列。这里的 Yosys ECP5 BRAM 推断失败,指存储器没有映射成期望的 DP16KD 等块随机存取存储器,而不是
synth_ecp5一定报错退出。其他开源工具的诊断入口见 开源 EDA 报错排查导航。
首屏答案:同一份 1024×16 位 RAM,只把 assign rdata = mem[raddr] 改为时钟沿上的 rdata <= mem[raddr],Yosys 0.33 的映射结果就从 256 个 TRELLIS_DPR16X4 加 1641 个 LUT4,变为 1 个 DP16KD。因此,看到 BRAM 数量为零时先检查读端口是否需要组合透明输出,不要急着调整器件容量或运行布局布线。
本文要回答的问题:Yosys ECP5 BRAM 推断失败怎么查
- 为什么异步读会绕开 DP16KD?
- 同步读写法需要保留哪些端口关系?
mapping memory与最后的stat应怎样对读?- 改成同步读后,功能时序多了什么约束?
一、先分清存储器类型,不把映射差异当综合错误
块随机存取存储器(Block RAM,BRAM)是 FPGA 内独立的存储单元;分布式 RAM 则利用逻辑资源附近的存储能力实现小型存储。ECP5 的 DP16KD 提供同步读写端口,而组合读要求地址变化后数据经组合路径直接出现,两者接口时序并不相同。
红色是本文要消除的透明读路径,蓝色是改写后的寄存读,橙色与绿色分别对应两类实际映射。关键不在数组是否写成 reg [15:0] mem [0:1023],而在读数据、地址与时钟的关系能否由目标单元直接实现。
synth_ecp5 即使没有选择 BRAM,也可以成功结束。判断是否“失败”要先明确期望:若电路本来就需要异步读,分布式 RAM 是合理实现;若设计预算依赖 BRAM,才需要修改接口时序或显式选择其他实现策略。
二、对照实验只改变读端口
两份源码具有相同的写时钟、写使能、读写地址、数据位宽和数组深度。异步组用连续赋值把读出数据直接接到端口:
always @(posedge clk) begin
if (we)
mem[waddr] <= wdata;
end
assign rdata = mem[raddr];这表示 raddr 改变后,不需要等待 clk,读数据就可以更新。日志明确选择了 TRELLIS_DPR16X4,而不是简单留下一个无法处理的存储器。
终端卡中 3129 个单元包含 256 个 TRELLIS_DPR16X4 和 1641 个 LUT4。所以“没进 BRAM”不等于“全部变成普通触发器”,实际结果必须读 stat,不能凭经验把分布式 RAM、逻辑和寄存器混为一谈。
同步组把读操作放进同一个时序过程,输出声明也相应改为 reg:
always @(posedge clk) begin
if (we)
mem[waddr] <= wdata;
rdata <= mem[raddr];
end这一改并不是给原输出随意补一级寄存器,而是把 RAM 本身的读端口定义为同步读。复跑后的映射和统计如下。
mapping memory sync_ram.mem via $__ECP5_DP16KD_
=== sync_ram ===
Number of wires: 69
Number of cells: 106
DP16KD 1
LUT4 42
TRELLIS_FF 44mapping memory 给出选择路径,stat 给出最终网表中的实际单元,两类证据在这里一致:出现了 1 个 DP16KD。外围仍有 42 个 LUT4 和 44 个 TRELLIS_FF,说明使用 BRAM 并不保证其他单元归零,也不能只凭一条映射行推断完整资源结果。
| 读端口写法 | 映射路径 | DP16KD | TRELLIS_DPR16X4 | LUT4 | TRELLIS_FF |
|---|---|---|---|---|---|
| 异步组合读 | $__TRELLIS_DPR16X4_ | 0 | 256 | 1641 | 0 |
| 时钟同步读 | $__ECP5_DP16KD_ | 1 | 0 | 42 | 44 |
表中所有数字均来自同一次 Yosys 0.33 综合末尾统计。它只描述这两个模块、这一工具版本和这套选项,不代表任意深度、位宽或器件都能获得相同比例,更不是布局布线后的时序或占用率结论。
三、修复时要接受同步读的功能变化
把连续赋值改成时序赋值后,读地址到读数据之间增加了一个时钟边界。上游逻辑若在同一个周期内改变地址并立即使用新数据,原异步设计可以工作,同步设计却会得到上一拍的数据。修复 BRAM 推断时,必须同时调整取数调度、流水线 valid 信号或等待状态。
还要保持数组访问足够直接。本例的写操作只发生在 posedge clk,读操作也只依赖 mem[raddr],工具才能识别出清晰的存储端口。若在读取表达式里再嵌入复杂运算、条件选择或多个输出拼接,应继续查看映射日志,不能假定“有时钟”就一定进入 DP16KD。
可按下面顺序排查:
- 在日志中找
mapping memory <模块>.<数组> via ...,确认实际模板。 - 在末尾
stat中分别查DP16KD、PDPW16KD、TRELLIS_DPR16X4和通用逻辑,不只看单元总数。 - 若没有进入 BRAM,回到读端口检查是否存在连续赋值或其他透明读路径。
- 改为同步读后复跑综合,并更新上游读等待逻辑;本文没有运行
nextpnr-ecp5,不能由此推出最终布线时序。
四、把排查结论限定在证据范围内
这组实验回答的是“读端口时序是否影响自动映射”,不是一份完整的 ECP5 存储器选型指南。数组深度、数据位宽、端口数量、初始化内容、读写冲突策略都可能改变可选模板。工程中的模块只要与本文有一项不同,就应重新运行自己的 synth_ecp5,不要照搬单元数量。尤其是读旧数据、读新数据和写穿透属于功能约定,不能为了复现本文的 DP16KD 数量而忽略。
排查记录最好同时保留三段信息:源码中的读写过程、mapping memory 的选择行、最后一个模块统计表。只有源码时,无法排除后续优化改变实现;只有单元总数时,又看不出存储器究竟去了哪里。把三者放在同一轮运行里,才能确认修改的是哪条路径。
| 现象 | 可以得出的判断 | 仍需补做的事 |
|---|---|---|
有 DP16KD 映射行 | 该存储器选择了 ECP5 块存储模板 | 查最终统计与功能回归 |
| 只有分布式 RAM | 当前接口未使用本文期望的块存储路径 | 检查异步读及其他端口约束 |
综合出现 ERROR | 这是综合失败,不只是推断偏好问题 | 先按错误位置修复输入或流程 |
| 单元统计发生变化 | 当前输入与选项下的实现发生变化 | 不直接推导频率、功耗或布线结果 |
如果同步化以后仍未出现 BRAM,不建议连续叠加寄存器碰运气。先把设计缩成只含数组、一个写端口和一个读端口的小模块,确认最小用例能够映射,再逐项加回初始化、复位、字节写使能和冲突处理。每一步都观察同一组映射与统计行,变化发生在哪一步就很清楚。
复位也值得单独核对。本文没有给整个数组加复位,只描述写入和读取;若工程在复位过程里遍历清空所有地址,那已经引入额外写行为与存储内容约束,不应直接套用本例。输出寄存器是否需要复位则是接口问题,与“把数组每个字清零”不是同一回事。
实验环境
- 机器/系统:MacBook Pro / macOS 26.5.2,Docker 容器
geek-eda:0.2 - 工具版本:Yosys 0.33 (git sha1 2584903a060)
- 日期:2026-10-31
- 完整命令:在代码目录执行
bash run.sh。脚本通过docker run --rm -v "$PWD:/work" -w /work geek-eda:0.2启动容器,分别执行yosys -Q -T -p "read_verilog <模块>.v; synth_ecp5 -top <模块>",保留映射行与综合末尾统计。
代码获取
两份 RAM 源码与运行脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-yosys-ecp5-bram-inference。准备同名容器镜像后进入该目录执行 bash run.sh,即可复现两组映射结果。
这一篇在知识体系里的位置
本篇处理 ECP5 后端存储器映射与接口时序的匹配,不是 Verilog 语法错误。需要回到工具类型与错误阶段定位时,从 开源 EDA 报错排查导航 进入;另一个“综合成功但专用结构未按预期出现”的例子是 Yosys FSM 状态机未识别。
总结
Yosys ECP5 BRAM 推断失败要先看实际映射,而不是把综合成功但没有 DP16KD 当成报错。本次 1024×16 位对照中,异步读使用 256 个 TRELLIS_DPR16X4 和 1641 个 LUT4;改为同步读并复跑后得到 1 个 DP16KD。修复必须同时接受读数据延后一拍的功能变化,并以映射行和 stat 共同确认结果。