Yosys check 组合逻辑环排查:found logic loop 报错定位与断环修复
更新时间:2026-10-29。FAQ 速查页,属于 EDA 报错排查系列。跑
yosys -p 'read_verilog a.v; proc; check'时撞见Warning: found logic loop,说的就是 Yosys check 组合逻辑环问题:一组 assign/always @* 互相依赖绕成圈。同一个环在 Verilator 里会报 UNOPTFLAT(仿真性能视角,见 %Warning-UNOPTFLAT 排查),而 Yosys check 是综合收敛视角——它直接判定这个结构没法综合成合法网表。
首屏答案:found logic loop 的意思是组合逻辑的输出绕一圈又回到自己的输入,比如 assign a = c | d; assign c = a & e;——a 依赖 c、c 又依赖 a。物理上这要么是一圈逻辑门首尾相接(反相奇数个就振荡),要么只是代码里互相引用的笔误。修法永远是断环:在环路上插一级寄存器把组合环变成时序路径,或者查出其中一个依赖本是笔误/译码器互斥条件写错,把它改掉。排查按「定位环路 → 判断环型 → 断环修复 → check 复验」四步走,不要用 (* keep *) 或忽略告警糊弄过去。
本文要回答的问题
found logic loop报错里列出的 cell 和 wire 清单怎么读?- 组合环有哪几种典型成因,哪种是笔误、哪种是真结构问题?
- 为什么仿真器遇到反相环会卡死、偶数环却像「隐存的锁存器」?
- 断环的标准修法是什么,修完怎么确认闭环?
一、报错原文与现象
最小复现只需要两条互相依赖的 assign:
module loop_bad(input wire d, input wire e, output wire y);
wire a, c;
assign a = c | d; // a 依赖 c
assign c = a & e; // c 又依赖 a —— 构成组合环
assign y = a ^ c;
endmodule跑 yosys -p 'read_verilog loop_bad.v; proc; check',check pass 抓到环,把环上的 cell 和 wire 全部列出来:
读这段输出抓三点。第一,Warning: found logic loop in module loop_bad 只是 Warning 级别,check 不会因此退出非零——它不妨碍后续 pass 跑,但综合结果不可信,别把「只是警告」当成「可以放过」。第二,check 把环的组成元件逐个钉出来:$or$loop_bad.v:4$1 对应第 4 行的 a = c | d,$and$loop_bad.v:5$2 对应第 5 行的 c = a & e,cell 名里的文件行号就是回查源码的入口。第三,列出的 $or..._Y、$and..._Y 是环上的内部网络——顺着这些 wire 在源码里找谁驱动它、谁读它,环的形状就出来了。
二、组合环的分诊:从告警收敛到根因
拿到告警后的排查像漏斗一样逐级收窄:先确认是不是真环,再判断环型,最后落到根因。下面这张漏斗卡是本文的主线:
读这张卡抓住一个分岔:第三层「判断环型」决定第四层的修法。环上有奇数个反相时,输出会在高低之间永远翻转——这就是环形振荡器,数字仿真器会在同一个时刻反复调度事件、仿真时间永远不动,表现为卡死;环上**偶数个反相(含零个)**时,环路像一对交叉耦合的门,构成一个「隐存的锁存器」——值取决于上电历史,综合器无法表达这种存储意图;而更多时候环根本不是设计意图,只是改代码时把某个依赖写回了旧信号名(比如译码器两个分支本该互斥,结果条件写重导致互相引用)。前两种必须插寄存器,第三种改对依赖即可。
三种环型的行为与修法对照如下,排查时先按这张表对号入座:
| 环型 | 判据 | 仿真行为 | 综合结果 | 修法 |
|---|---|---|---|---|
| 振荡环 | 环上反相数为奇数 | 零时延振荡,仿真时间不走、CPU 打满 | 无法收敛 | 插寄存器或删环 |
| 双稳态环 | 环上反相数为偶数(含零) | 能跑完,但值取决于初值,不可复现 | 综合器拒绝或拼出意外结构 | 明写触发器替代 |
| 笔误环 | 某条依赖本应是另一根信号 | 行为看似正常,功能悄悄错 | 同振荡/双稳态 | 改对依赖,不需加寄存器 |
表里第二行最隐蔽:双稳态环不报错、仿真也能跑完,只是每次上电「记住」的值可能不同——本质上它是用两条组合逻辑拼出来的锁存器,既躲过 lint 又躲过功能仿真的抽查,直到某次时序约束收紧或换综合器才暴雷。第一行最吵但最好找,仿真挂死本身就是定位信号。第三行占比最高,是 review 时该重点盯的形态:表达式右值里出现了本不该出现的信号名。
三、仿真侧的实锤:反相环卡死 vvp
组合环在仿真侧的危害可以亲眼看到。给环路加一个反相和使能门:
module loop_osc(input wire en, output wire y);
wire a, c;
assign a = ~(c & en); // 环内含一个反相
assign c = a;
assign y = a;
endmoduletestbench 先让 en=0 保持 100ns(此时环路稳定在 a=1、c=1),第 100ns 把 en 拉高——反相环立即开始零时延振荡,vvp 在同一个仿真时刻反复调度 a、c 的更新事件,仿真时间永远停在 100ns。3 秒看门狗强制终止后 log 里的记录:
== 步骤2: 仿真现象 — 反相组合环零时延振荡,vvp 卡死 ==
$ iverilog -o loop_osc.vvp loop_osc.v tb_osc.v && vvp loop_osc.vvp
vvp 未在 3 秒内结束,已强制终止(watchdog exit=124:en 拉高后环路零时延振荡,仿真时间永远停在 100ns)这就是组合环「仿真死循环」的具体形态:不是报错,是仿真时间不再前进,CPU 打满、波形永远画不出下一拍。跑这类演示务必套超时保护。对比之下本文第一节的 loop_bad(环上无反相)能仿真到 1000ns 正常结束,只是 a、c 的值由初值决定——这种「能跑但不可信」的环更危险,因为它不吭声。
四、修复与验证
标准修法是在环路上插一级寄存器,把「同一时刻互相决定」改成「这一拍用上一拍的值」:
module loop_fixed(input wire clk, input wire rst_n, input wire d, input wire e, output wire y);
wire a;
reg c_q;
assign a = c_q | d;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) c_q <= 1'b0;
else c_q <= a & e; // 原来的 c 变成寄存器输出,环被时序断开
end
assign y = a ^ c_q;
endmodule对修复版重跑同一条 check 命令,输出 Found and reported 0 problems.(log 步骤3),stat 显示网表里出现了 $adff 触发器——环确实被时序断开了。两点提醒:第一,插寄存器意味着语义改变——原本「瞬时」的依赖变成一个时钟周期的延迟,写之前先想清楚这个延迟在算法里是否允许;流水线、反馈累加这类结构允许,纯译码器不允许——译码器里出现环几乎肯定是条件写错,应该改条件而不是插寄存器。第二,如果确认是笔误环(某个依赖本应是另一根信号),直接把引用改对,一个寄存器都不需要加。
还有一个容易混淆的点值得说清:check 报环和后续 synth 卡住不是一回事。check 只做结构性拓扑检查,几毫秒就给出环的清单;而如果你跳过 check 直接跑完整综合,组合环可能在 opt 类 pass 里被反复展开优化,表现为综合时间异常拉长——先跑 proc; check 再进正式流程,等于花零成本先把这类结构性大病筛掉。这也是为什么本系列建议把 check 固化进每条综合脚本的原因。
五、预防清单
- 组合逻辑写完自查依赖方向:一个模块内的 assign 应该能排出「先算谁后算谁」的拓扑序,排不出来就是有环。
- 译码器/多路选择写互斥条件:分支条件两两取反验证一遍,防止两个分支互相引用对方输出。
- CI 里把
proc; check当门禁:check 只是 Warning 不会失败,要在 CI 脚本里 grepfound logic loop主动判失败。 - 仿真挂死先想组合环:仿真时间不走、CPU 打满,第一反应查最近改过的组合逻辑依赖。
- 要存储就明写触发器:别靠偶数反相环「碰巧记住值」,那是不可综合的隐存锁存器。
实验环境
- 机器/系统:Mac mini M4 / macOS 15(Yosys 经 geek-eda:0.2 docker 镜像运行)
- 工具版本:Yosys 0.33 (git sha1 2584903a060)、Icarus Verilog version 13.0 (stable)
- 日期:2026-10-29
- 完整命令:
yosys -p 'read_verilog loop_bad.v; proc; check'(报错复现);iverilog -o loop_osc.vvp loop_osc.v tb_osc.v && vvp loop_osc.vvp(振荡演示,3 秒看门狗强杀);yosys -p 'read_verilog loop_fixed.v; proc; check'(修复后复验)
代码获取
复现用源码(loop_bad.v 报错版、loop_osc.v 振荡演示、loop_fixed.v 修复版及两个 testbench)在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/yosys-check-combo-loop。装好 Yosys 与 Icarus Verilog 后按 README 三条命令依次复现报错、振荡与修复通过。
这一篇在知识体系里的位置
本篇是 EDA 报错排查系列中 Yosys 簇的一员:综合收敛视角的组合环看本篇;同一个环在 Verilator 里的仿真性能告警看 %Warning-UNOPTFLAT 排查;位宽不匹配类告警见 Yosys WIDTH 告警。开源工具链报错的总入口见 开源 EDA 报错排查导航,整个系列的目录在 EDA 报错排查系列。
总结
Yosys check 组合逻辑环(found logic loop)就是组合逻辑的输出绕一圈回到自己的输入,check 会把环上的 cell 与 wire 连同行号全部列出;按「定位环路 → 判断环型 → 断环修复 → check 复验」四步收敛——奇数反相环会让仿真器零时延振荡卡死、偶数环是不可信的隐存锁存器、笔误环改对依赖即可;标准修法是在环路上插一级寄存器把组合环变成时序路径,修完 Found and reported 0 problems. 才算闭环。