Yosys 偶发不确定失败速查:同一设计跑两遍结果不一样
更新时间:2026-10-23。FAQ 速查页,属于 FPGA 工具报错排错系列。Yosys 最让人摸不着头脑的现象不是稳定报错,而是「飘」:同一份 RTL、同一条 yosys 脚本,这次综合过了、下次挂;或者两次都过但 cell 统计 diff 出来一大堆,下游 P&R 结果跟着漂移。本篇先给典型现象原文,再按「先复现、再分流、后定位」的顺序逐条排查。如果你遇到的是编译过程长时间无输出,先看 Yosys 卡死无响应速查;商业工具侧的同类问题可对照 Quartus 编译结果时好时坏速查,思路相通但病因轴不同。
本文要回答的问题
- 两次综合的 cell 统计不一样,是 Yosys 有 bug 还是我的用法有问题?
- abc 子进程偶发超时或崩溃,跟随机种子有什么关系?
read_verilog *.v的通配符展开顺序为什么会影响结果?- CI 上偶发失败、本地怎么都复现不了,差异到底出在哪一层?
- 用什么纪律把「玄学 flaky」收敛成可复现、可签核的流程?
一、现象:先认脸再开药
Yosys 偶发不确定失败在临床上有几张熟面孔。最典型的一种是两次综合都「成功」,但统计对不上:
$ yosys -p "read_verilog rtl/*.v; synth -top top; stat" -q > run1.log
$ yosys -p "read_verilog rtl/*.v; synth -top top; stat" -q > run2.log
$ diff run1.log run2.log
< Number of cells: 4812
> Number of cells: 4857
< $lut 1290
> $lut 1318另一种是 abc 子进程偶发抽风,同一脚本十次里挂一两次:
ABC: ERROR: The network is not a logic network after mapping.
ABC: command line: source -x <tmp>/abc.script
ERROR: ABC: execution of command "..." failed: return code 134.还有一类是「换台机器就变」:本地稳稳通过,CI 容器里偶发失败,或者 glibc 升级后 cell 名单悄悄变了。这三种现象的共同点是没有稳定的错误信息可查,所以排查的第一步不是改代码,而是把「随机」变成「可复现」。
二、原因速查表
| 现象 | 最常见原因 | 处置 |
|---|---|---|
| 两次综合 cell 数差几十个 | abc 内部布局/映射带随机性,默认种子随运行环境变 | 用 abc -script 固定脚本,或设 ABC_SEED(版本支持时)固定种子复现 |
| abc 偶发超时/return code 134 | 大模块在临界规模上,abc 启发式走到坏角落;内存压力放大不稳定 | 固定种子复现;把大模块 hierarchy 拆开分块综合;给 CI 容器加内存 |
| 换文件列表顺序结果就变 | read_verilog rtl/*.v 靠 shell 通配符展开,顺序受 readdir/locale 影响 | 显式列文件清单或 `ls rtl/*.v |
| 同代码换机器/换容器结果不同 | 哈希表迭代顺序随指针地址(ASLR)、glibc 版本、并行度变化 | 签核固定容器镜像与 yosys 版本;结果以签核环境为准 |
| formal(sat/miter)结果偶尔飘 | RTL 有未初始化寄存器或多驱动竞争,初值不同结论不同 | 修 RTL:显式复位、消除多重驱动,与工具无关 |
| 同种子同环境仍偶发崩 | Yosys/abc 版本自身的非确定 bug(迭代序未排序) | 升级或回退到稳定版本;用最小复现去上游 issue 库比对 |
| 增量复用上次 build 产物后结果突变 | 缓存陈旧,旧 .ilang/.blif 混入新流程 | 清缓存做一次干净综合,对照 Yosys 构建缓存陈旧速查 的同类处置思想 |
三、排查决策流程
分诊只问一个问题:固定了种子和文件顺序之后,结果还飘不飘。答案把现场切成「工具链路非确定」和「RTL/环境带病」两条通道:
读这张图抓住主线:红色是入口现象与三类病根,蓝色是排查动作,橙色是修复,绿色是验证收口,紫色是防复发纪律。 蓝框「复现化」是整篇的地基——不复现化就直接改 RTL 或换版本,等于蒙眼修表。
四、逐条排查展开
第一步:固定 abc 种子与脚本。 abc 是 Yosys 调起来做逻辑优化与映射的外部子进程,它的启发式搜索带随机性,不少构建里默认种子取自时间或运行环境。把综合脚本里的裸 abc 换成显式脚本,种子相关的命令写死:
abc -script +strash\;&get\;&fraig\;&rewrite\;&balance\;&map不同版本固定种子的入口不一样(有的构建认 ABC_SEED 环境变量),用 yosys -V 确认版本后查对应 release note。固定后连跑三次,统计必须逐字节一致——这是诊断基线,不是修复,种子恰好通过不代表设计健康。
第二步:固定读入顺序。 read_verilog rtl/*.v 的文件顺序由 shell 通配符展开决定,而展开顺序依赖文件系统的 readdir 顺序与 locale;模块多了以后,读入顺序会影响匿名生成块的命名与哈希桶填充顺序,最终渗进 cell 名单。Makefile 里改成显式排序:
SRCS := $(shell ls rtl/*.v | LC_ALL=C sort)
yosys -p "read_verilog $(SRCS); synth -top top; stat"注意 LC_ALL=C——不同 locale 下 sort 对大小写与下划线的排序不同,这本身就是一类「换台机器结果变」的隐形来源。
第三步:同环境复跑,隔离环境轴。 「本地过、CI 挂」时先对齐三层:yosys/abc 版本(yosys -V 连提交号一起对)、容器或系统镜像(glibc 版本影响哈希与 locale 行为)、资源限制(容器内存打满时 abc 更容易走到崩溃路径)。签核纪律是固定容器镜像 digest,而不是 latest。CI 偶发失败务必把失败现场的完整 yosys 日志和 abc 临时脚本留存下来,否则下一轮永远从零猜起。
第四步:查 RTL 自身的非确定性。 如果固定了种子、顺序、环境,结果仍然飘,问题多半不在工具:未初始化的寄存器在仿真与 formal(sat、miter)里会拿到不同初值,结论随初值飘;同一信号多处驱动(多重 assign、inout 误用)在整形阶段的取舍可能依赖处理顺序。用 yosys -p "read_verilog ...; check" 先跑内建检查,多驱动和宽度不匹配的警告一条都别放过——宽度类告警的排查可对照 Yosys 位宽类型不匹配速查。RTL 修到 check 零告警,再谈工具非确定性。
第五步:最小复现与版本二分。 确认是 Yosys/abc 版本自身 bug(同一输入同种子同环境仍偶发崩)时,把设计逐层 hierarchy -purge_lib 裁到能复现的最小模块,拿这个复现去上游 issue 库搜关键词,命中过就用对应补丁版本;没命中就按版本二分定位引入 commit。最小复现也是给上游报 bug 的硬通货。
五、防复发与纪律
- 种子入脚本、入版本库:综合脚本固定 abc 脚本与种子,和 RTL 一起提交,签核结果可逐字节复现。
- 文件清单显式化:禁止裸
*.v通配符直接进 read_verilog,清单经LC_ALL=C sort生成。 - 签核环境锁定:固定容器镜像 digest 与 yosys 版本,升级版本必须重签核,不允许「顺手升一下」。
- CI 失败留现场:偶发失败的完整日志、abc 临时脚本、cell 统计 diff 全部归档,不许只看退出码重跑。
- RTL 零告警门槛:
check通过、无多重驱动、无未初始化寄存器,作为综合前置条件。 - 缓存洁净:流程改动后清理中间产物做一次干净综合,避免陈旧缓存伪装成「结果漂移」。
这一篇在知识体系里的位置
本篇是 FPGA 排错速查簇中「Yosys 结果非确定」节点的 FAQ 页:进程卡死的排查见 Yosys 卡死无响应速查,语法层面直接报错的入口见 Yosys 语法解析失败速查,商业工具侧的同构问题(seed 波动与约束缺失)见 Quartus 编译结果时好时坏速查。形式验证与静态检查的原理性内容建议回到 FPGA 系列的验证基础章节系统补齐。
一句话总结
Yosys 偶发不确定失败先把「随机」变「可复现」:固定 abc 种子与脚本、用 LC_ALL=C sort 固定读入顺序、锁定容器镜像与版本,连跑三次确认基线;波动在映射阶段就拆大模块降临界规模,固定后仍飘就查 RTL 未初始化寄存器与多驱动竞争,换环境才变就对齐 glibc、版本与内存限制——根治靠种子、文件清单、镜像三件套随工程入库的签核纪律。