Yosys memory not inferred:数组没被推断成 $mem cell,面积爆炸排查
更新时间:2026-10-29。FAQ 速查页,属于 EDA 报错排查系列。本篇处理的不是报错,而是一种「静默失败」:Yosys 综合能跑完,但你声明的大数组没有被
memory_collect推断成$memcell,而是被展开成上万个触发器和多路选择器,面积直接爆炸。相关的 Yosys 显式报错可对照 ERROR: Module not declared 与 No top module selected。
首屏答案:read_verilog 前端阶段打出 Warning: Replacing memory \mem with list of registers,或 proc; memory_collect; stat 的统计里没有 $mem_v2 cell、cell 总数却成千上万,就是 memory not inferred。根因几乎总是写法不合推断模板——最常见的是在组合 always 块里异步写数组(无时钟沿),其次是整组复位循环、多块驱动同一数组。修法是改成标准同步 RAM 模板:写口与读口都放进 always @(posedge clk),让 memory_collect 能把它整体收进一个 $mem_v2 cell,交给后端映射到 BRAM。
本文要回答的问题
- memory not inferred 在日志里长什么样,哪一行是最早期信号?
- 为什么不合模板的写法会让 256×16 的数组膨胀成 6 万多个 cell?
- 哪些写法是 Yosys 推断不了的「反模板」?
- 标准同步 RAM 模板怎么写,修复后 stat 应该看到什么?
一、现象:一条 Warning 和一份爆炸的统计
最小复现是一个「看起来能工作」的 RAM 写法——组合块里异步写、组合异步读:
// ram_bad.v:组合 always 块里异步写数组(无时钟沿)
module ram_bad (
input wire we,
input wire [7:0] waddr,
input wire [7:0] raddr,
input wire [15:0] din,
output wire [15:0] dout
);
reg [15:0] mem [0:255];
always @(*) begin
if (we) mem[waddr] = din; // 异步写:不合 memory 推断模板
end
assign dout = mem[raddr]; // 异步读
endmodule对它跑 yosys -p 'read_verilog ram_bad.v; proc; memory_collect; stat',前端阶段就给出最早期的信号:
关键就是 Warning: Replacing memory \mem with list of registers. See ram_bad.v:12 这一行:前端在读代码时就已经判定 mem 的写法(第 12 行的组合块异步写)无法当成存储器处理,直接把 256×16 的数组「摊平」成 4096 个独立寄存器。这一步之后,memory_collect 再想收也收不回来了——原材料已经没了。继续跑到 stat,灾难以数字形式呈现:
读这份统计抓三个数:Number of cells: 67332,一个 256×16 的小 RAM 膨胀出六万多个单元;$eq 65535——256 个写使能各配一个 8 位地址比较器(256×256 组合爆炸),加上 256 个 $dlatch 锁存每一位的写入;而 $mem_v2 一行根本没有出现。这就是面积爆炸的完整指纹:等值比较器海 + 锁存器阵,替换了本该有的一个存储器 cell。这份统计里 Number of memories: 0 和 memory bits: 0 两个零同样值得记住——memory_collect 阶段静默地什么都没做,综合流程照样走完,不主动看 stat 就发现不了。
二、根本原因:推断模板不匹配
Yosys 的 memory 推断分两步:前端先把数组保留为 memory 对象,proc 把过程块转成 $memwr/$memrd 原语,最后 memory_collect 把同一数组上的读写原语收进一个 $mem_v2 cell。任何一步断掉,数组就退化成普通触发器/锁存器加译码逻辑。三种高发反模板一张卡看全:
这张卡按「模板在哪一环断裂」分类:异步写死在前端(连 memory 对象都不给留);复位循环和多块写死在收集阶段(写口形态超出一个存储器 cell 能表达的边界)。为什么一个 256×16 的数组摊平后会膨胀到 6 万多个 cell?写入路径上,每个地址槽位需要一个「写地址等于我」的比较器,256 个槽位配 256 个 8 位等值比较器,外加把写使能叠上去的与门阵列;而本篇反例里异步写意味着每个槽位还要一把锁存器随时透明地跟着数据走。读出路径上,256 选 1 的多路选择器按位铺开又是几千个 $mux 节点。存储器的价值恰恰在于把这些译码和选择都固化进了硅片里的地址译码器,软件工具推断不出存储器,就得用通用逻辑把这一切现场拼出来——代价是平方级的膨胀。
共同的物理本质很简单:FPGA 的 BRAM 是「时钟沿写入、地址译码内置」的硬核资源,任何无法用「一个时钟沿 + 一个写地址 + 一个写使能」描述的写入行为,都只能用通用触发器和译码逻辑现场拼。反过来,只要写法收敛到这个模板,推断就是自动的。
三、修复:标准同步 RAM 模板
把写和读都收进时钟沿,是 Yosys(以及 Vivado/Quartus 等所有综合器)都认的标准模板:
// ram_good.v:写与读都在 always @(posedge clk) 里
module ram_good (
input wire clk,
input wire we,
input wire [7:0] waddr,
input wire [7:0] raddr,
input wire [15:0] din,
output reg [15:0] dout
);
reg [15:0] mem [0:255];
always @(posedge clk) begin
if (we)
mem[waddr] <= din;
dout <= mem[raddr]; // 同步读:数据拍一拍出来
end
endmodule对修复版跑同一条命令复跑确认:
对照一目了然:$mem_v2 1 出现了,Number of cells 从 67332 掉到 8(4 个 $dff 是写地址/写数据/写使能/读数据的打拍寄存器,3 个 $mux 是读写口外围),面积收敛了四个数量级。后续的 memory_bram、FPGA 映射流程现在才有对象可以往 BRAM 上放。注意功能语义的变化:同步读的数据比地址晚一拍,调用方时序要跟着调。
如果你的设计本来就想要异步读(组合读),那不属于「写错」而是「资源选型」——FPGA 上的分布式 RAM(LUT RAM)恰好就是异步读形态,Yosys 的 memory_dff 与厂商映射流程会把合模板的异步读数组映到 LUTRAM;但前提是写口仍然必须是时钟沿同步写。异步写没有任何 FPGA 存储资源对应,这是本篇反例不可协商的地方。
四、排查清单
- 先看前端告警:日志里搜
Replacing memory,命中即定位到源码行,这是最早的信号,比看 stat 快得多。 memory_collect后跑stat:确认$mem_v2(或$mem)计数符合预期;数组不小却没有 memory cell,就是没推断上。select -list看推断结果:memory_collect后执行select -assert-none t:$mem_v2或在交互模式ls确认 cell 名里有没有$mem。- 盯三种反模板:组合块异步写、整组复位循环、多块跨时钟写——命中任何一个都改回同步模板。
- 复位循环要克制:BRAM 没有整体清零口,确需清零就在读出侧加旁路逻辑或分帧清,不要在 always 里 for 全数组。
- CI 加面积哨兵:对含大数组的模块固化
stat输出断言,$eq/$dff计数突增直接报警,把这种静默失败变成显式失败。
实验环境
- 机器/系统:geek-eda:0.2 docker 镜像(Linux 容器)
- 工具版本:Yosys 0.33 (git sha1 2584903a060)
- 日期:2026-10-29
- 完整命令:
yosys -Q -T -p 'read_verilog ram_bad.v; proc; memory_collect; stat'(反例复现);yosys -Q -T -p 'read_verilog ram_good.v; proc; memory_collect; stat'(修复后复验)
代码获取
复现用源码(ram_bad.v 反例、ram_good.v 修复版)在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/yosys-memory-not-inferred。装好 Yosys 后按 README 里的两条 yosys -p 命令分别跑反例与正例,对比 stat 输出的 cell 构成即可复现本篇全部数字。
这一篇在知识体系里的位置
本篇是 EDA 报错排查系列中 Yosys 综合类问题的一员,处理「不报错但结果错」的静默失败:memory 没推断上导致面积爆炸看本篇;显式报错类见 ERROR: Module not declared、No top module selected;位宽类告警见 Yosys width mismatch。开源工具链报错总入口在 开源 EDA 报错排查导航,整个系列目录见 EDA 报错排查系列,覆盖仿真、lint、综合、形式验证各阶段的高发故障。
总结
Yosys memory not inferred 的典型指纹是前端告警 Replacing memory \mem with list of registers 加上 stat 里没有 $mem_v2、cell 数却上万;根因是写法不合推断模板——最常见组合块异步写,其次整组复位循环与多块跨时钟写;修复就是回到标准同步 RAM 模板(写读都在 always @(posedge clk)),复跑确认 cell 总数从 67332 收敛到 8、$mem_v2 出现,后端才有映射 BRAM 的机会。