Yosys proc_dlatch 推断出锁存器:Latch inferred for signal 排查与修复
更新时间:2026-10-29。属于开源 EDA 排错系列,总入口见 开源 EDA 报错排错中心。用 Yosys 综合一段看似无害的组合逻辑,
proc阶段悄悄打出一行Latch inferred for signal ...——综合器在你的设计里造了一把锁存器。这行提示不带 Warning 前缀、不中断流程,极其容易被滚屏日志淹没,但后果实打实:本想写纯组合逻辑,网表里却多出$dlatch单元,下游时序分析、形式验证全都跟着变味。它和 Yosys Warning: width mismatch 那种「逻辑还在、宽度不对」的问题不同——锁存器推断是电路结构层面的误判,RTL 作者的意图被综合器「善意补全」成了另一样东西。
本文要回答的问题
Latch inferred for signal这行输出出在哪个 pass、怎么读?- 为什么分支不全的
always @(*)会被推断成锁存器? - 怎么用
stat确认网表里真的出现了$dlatch单元? - 修复只是补一个
else吗?default赋值、完整 case 各是什么角色? - 修完之后用什么证据确认锁存器真的消失了?
先给结论路径:proc 输出锁存器推断 → 拿进程行号回 RTL 定位 → 找到缺失的分支 → 补全赋值 → 复跑确认 $dlatch 归零。
一、报错原文与读法
复现设计是一个 8 位使能通路:en 为 1 时 q = d,en 为 0 时什么都不写——作者的本意是「组合逻辑选通」,代码里也没出现任何 latch 字样。
// dlatch_bad.v:always 块分支不全
module dlatch_bad (
input wire en,
input wire [7:0] d,
output reg [7:0] q
);
always @(*) begin
if (en)
q = d; // en 为 0 时 q 没有任何赋值
end
endmodule跑 yosys -p 'read_verilog dlatch_bad.v; hierarchy -top dlatch_bad; proc; stat',proc 阶段的输出如下。
读这段输出抓三个点。第一,推断发生在 3.8. Executing PROC_DLATCH pass:proc 不是一道工序而是一串子 pass,前面 PROC_MUX 先把决策树转成多路选择器,PROC_DLATCH 再检查「有没有信号在某些路径上没被赋值」——有,就造锁存器保持旧值。第二,输出行带完整定位信息:Latch inferred for signal '\dlatch_bad.\q' from process '$proc$dlatch_bad.v:9$1',信号名 q 和源文件行号 dlatch_bad.v:9 都在里面,拿到行号回 RTL 一看就是那个缺 else 的 always 块。第三,stat 表格给出实锤:Number of cells: 2 里有一个 $dlatch,网表层面的锁存器已经真实存在——这不是「风格建议」,是电路结构。
二、根本原因:组合逻辑里「保持旧值」只能用存储元件实现
根本原因是语义层面的:你声明的是组合逻辑(always @(*)),却要求电路在某个条件下「什么都不做」。什么都不做意味着输出保持上一次的值,而纯组合电路没有记忆能力——输出只能是输入的函数,en 变 0 的瞬间 q 必须有确定值。要满足「保持」,综合器唯一的选择就是插入一把电平敏感的锁存器:en 高电平透明直通,低电平锁存。
所以 Latch inferred 严格来说不是 Yosys 的「报错」,而是它忠实地把你的 RTL 语义翻译成了电路——问题在 RTL 语义本身就不是你以为的组合逻辑。这也是为什么这行提示不带 Warning、流程照常走完:综合器认为自己干的是正事。
那为什么大家都把意外锁存器当缺陷?因为它和触发器(FF)不是一回事:锁存器是电平敏感器件,使能电平期间输入直通输出,毛刺和路径延迟都能穿过去,时序借用规则复杂、STA 很难收干净;而同步设计默认的 DFF 只在时钟沿采样,行为规整可控。意外混入的锁存器还会拖累 DFT(扫描链通常只针对 DFF 设计)、让形式验证的等价点变多,FPGA 流程里甚至根本没有原生锁存器资源、要靠 LUT 反馈环硬凑。所以「能综合」不等于「能用」,看到 $dlatch 就该回头改 RTL。三类最常见的触发写法:
| 触发写法 | 典型形态 | 被推断的信号 |
|---|---|---|
if 无 else | if (en) q = d; 结束 | 所有分支里没赋值的输出 |
case 无 default | 分支项没覆盖全部编码 | 未覆盖路径上的输出 |
| 多输出赋值不齐 | if 里赋了 a 忘了 b | 只在部分分支赋值的那些信号 |
表里的共性一句话:只要存在「某条路径上信号无赋值」,锁存器就会被推断出来。反过来说,如果这条路径在真实设计里确实走不到,也应该显式写出赋值(哪怕是赋 0),把「不可能发生」变成电路可见的事实,而不是留给综合器去猜。
三、排查清单:从日志到 RTL 的四步
大工程里日志动辄上万行,按这四步机械执行即可,不用通读:
- 日志里捞推断行:
grep -i "latch inferred" synth.log,一条都没有才算干净;有就记下信号全名和$proc$<文件>:<行号>$1里的行号。 - stat 里对单元数:
stat输出按模块打印 cells,哪个模块出现$dlatch/$sr这类存储单元,问题就在哪个模块——与第 1 步的信号全名交叉确认归属。 - 回 RTL 找缺失分支:按行号定位 always 块,逐个输出信号核对「是否每条 if/case 路径都赋值」;多个输出的块尤其要查「if 里赋了 a 忘了 b」这种不齐。
- 区分有意与无意:确实要锁存器的设计(极少数,如某些总线保持场景)应单独评审并加注释说明;其余一律按下一节修复。判断标准很朴素——作者画原理图时有没有刻意画这把锁存器,没有就是意外。
走完四步手上应该有一张「信号 → 文件行号 → 缺失分支」的清单,修复就是对清单逐条补赋值。
四、修复:补全分支,复跑用 stat 实锤归零
修复原则是「每条路径都有赋值」,两种等价写法任选:给 if 补 else,或者在 always 块开头给所有输出一行默认赋值、后面分支只做覆盖。本例补 else 分支:
// dlatch_fixed.v:补全 else 分支
module dlatch_fixed (
input wire en,
input wire [7:0] d,
output reg [7:0] q
);
always @(*) begin
if (en)
q = d;
else
q = 8'h00; // en 为 0 时 q 有确定值
end
endmodule复跑同一条命令(脚本里的设计名换成 dlatch_fixed),输出如下。
两个确认点缺一不可。一是 PROC_DLATCH pass 明确打印 No latch inferred for signal '\dlatch_fixed.\q'——同一个信号、同一个 pass,这次干净通过;二是 stat 里 cells 从 2 降到 1,唯一的单元是 $mux,$dlatch 彻底消失。修完后的电路正是一个纯组合的多路选择器:en 做选择端,d 与 8'h00 做两路输入——和作者最初的意图完全对齐。注意复跑时两个设计都走了 Marked 1 switch rules as full_case 的 RMDEAD 提示,这是 Yosys 对「分支已覆盖全集」的判定记录,修复版里它与无锁存器互为印证。
两个配套习惯能把这类问题挡在源头:一是 case 语句永远写 default 分支(哪怕里面只赋默认值),二是把 proc; stat 挂进 lint 脚本,stat 输出里 grep 到 $dlatch 就 fail——锁存器推断不报警告,只能靠单元计数兜底。规模大的设计里 stat 按模块分层打印,哪一层冒出 $dlatch 一目了然。
代码获取
本文实验代码已同步到公开仓:experiments/yosys-proc-dlatch-inferred,含 dlatch_bad.v、dlatch_fixed.v 与完整运行说明,装好 Yosys 后执行 README 里的单条命令即可复现两段输出。
实验环境
- 机器/系统:Mac mini M4 / macOS(docker 容器内跑 Yosys)
- 工具版本:Yosys 0.33 (git sha1 2584903a060)(geek-eda:0.2 docker 镜像)
- 日期:2026-10-29
- 完整命令:
yosys -p 'read_verilog dlatch_bad.v; hierarchy -top dlatch_bad; proc; stat';修复后yosys -p 'read_verilog dlatch_fixed.v; hierarchy -top dlatch_fixed; proc; stat'
这一篇在知识体系里的位置
本篇是开源 EDA 排错簇中「Yosys proc 阶段」节点的一页:proc 把 always 进程转成网表,锁存器推断正是这一阶段最隐蔽的产物。同簇还有 Yosys Warning: width mismatch(赋值宽度问题)与 Yosys ERROR: no top module(层次入口问题),更多工具报错从 开源 EDA 报错排错中心 按入口分流。
总结
Yosys 报 Latch inferred for signal 的含义是:always 块存在分支不全,en 为 0 时信号无赋值,综合器只能造 $dlatch 锁存器来「保持旧值」——它不带 Warning 不中断流程,实锤靠 stat 里的 $dlatch 单元计数。修复就是补全分支(else 或 default 或块首默认赋值),让每条路径都有确定赋值;复跑看到 No latch inferred 且 cells 里只剩 $mux,锁存器才算真正消失。case 恒写 default、lint 脚本 grep $dlatch 非零即 fail,这类隐患就没有藏身之处。