Yosys 缓存陈旧行为怪异:改了 RTL 综合结果没变速查
更新时间:2026-10-23。FAQ 速查页,面向已经用 Yosys + nextpnr 搭过开源 FPGA 流程的中级读者。先建立一个正确预期:Yosys 本身是一个"读脚本、干活、退出"的命令行工具,它自己不维护任何跨次运行的缓存——每次启动都是一张白纸。所以"Yosys 缓存陈旧"这个表象,病根几乎全在它周围:Makefile 的依赖判定、脚本里
read_verilog的读取顺序、上一趟流程留在 build 目录里的中间产物、以及下游 nextpnr 直接捡起上一次的.json继续跑。你以为是 Yosys 记性好,其实是流程的某个环节在你看不见的地方复用了旧东西。语法层面的报错(那种直接告诉你第几行错了的)是另一个方向,见 Yosys 语法/解析报错速查。
本文要回答的问题
- RTL 明明改了也保存了,重跑
make后综合出来的电路为什么和之前一模一样? - 综合日志里的 cell 数、wire 数纹丝不动,怎么判断是不是吃了旧产物?
read_verilog读的文件明明在磁盘上是新的,Yosys 为什么像没看见?- build 目录里一堆
.json、.ilang、blif中间文件,哪个陈旧的会害人? - nextpnr 报的结果和 Yosys 日志对不上,是谁拿了旧输出?
- 怎么一劳永逸地防住这类"改了等于没改"?
一、典型现象:三种长相,同一个病根
长相一:make 秒过,什么都没干。 改了 RTL 里一个参数,敲 make,终端一闪而过只打印了 nextpnr 或烧录步骤,Yosys 那一步根本没出现。这是 Makefile 依赖没写全的典型信号——make 比对时间戳后认为"综合输出比所有已知输入都新",直接跳过。真实日志长这样:
$ make
nextpnr-ice40 --hx8k --package ct256 --json build/top.json \
--pcf io.pcf --asc build/top.asc
Info: Importing module top
...
$ ls -l build/top.json rtl/top.v
-rw-r--r-- 1 user user 42183 Oct 23 09:12 build/top.json
-rw-r--r-- 1 user user 8204 Oct 23 09:47 rtl/top.v源文件 09:47 比综合产物 09:12 还新,make 却没重跑 Yosys——因为它的依赖清单里压根没有 rtl/top.v。
长相二:Yosys 跑了,但读的是旧定义。 工程里同一模块存在两份(比如 rtl/uart.v 和 legacy/uart.v),脚本里 read_verilog 把两份都读了,后读的那份静默覆盖先读的。你改的是 A 份,生效的是 B 份。日志里 module 列表照常打印,cell 数也对得上"某个版本",只是不是你以为的那个版本:
yosys> read_verilog rtl/uart.v
1. Executing Verilog-2005 frontend: rtl/uart.v
yosys> read_verilog legacy/uart.v
2. Executing Verilog-2005 frontend: legacy/uart.v长相三:全流程绿灯,行为纹丝不动。 Yosys 重新综合了、nextpnr 也跑了、比特流也下载了,板上表现和改动前一致。往回一查:Yosys 输出写到了 build/top.json,nextpnr 命令行里指的却是 out/top.json——上一轮留在旧路径的产物被下游无缝接走。没有任何报错,因为每个工具都"成功"了。
二、原因速查表
按下表先分诊,命中哪一行先验证哪一行,不要上来就 make clean 全删——全删能治好病,但治一次要等一次全量综合,先定位再动手更省时间。
| 现象 | 根因 | 快速验证 | 处置 |
|---|---|---|---|
改 RTL 后 make 秒过、跳过 Yosys | Makefile 目标依赖没列全源文件 | make -n 或 make --dry-run 看实际会执行哪些步骤 | 把全部 .v 源文件列进 .json 目标的依赖,或用通配符收集 |
| Yosys 跑了但改动不生效 | 脚本里 read_verilog 读入两份同名模块,后读覆盖先读 | 综合日志搜模块名,看它被哪个文件定义、定义了几次 | 删掉多余的读取行;同名模块在工程里只留一份 |
| 日志 cell 数与上轮一字不差 | 综合输出路径与下游读取路径不一致,下游吃旧产物 | 对比 Yosys write_json 路径与 nextpnr --json 路径 | 统一输出/输入路径;删旧路径残留文件 |
| 偶发性"有时新有时旧" | abc(综合后端的工艺映射工具)临时文件残留或并发写撞车 | 看 abc 输出是否带旧时间戳;是否并行 make -j | 串行跑关键段;清 abc 临时目录;升级 Yosys |
| 改过宏定义/参数结果不变 | 宏在命令行 -D 与文件 define 两处定义,命令行那份是旧的 | grep 宏名,对比 Makefile 传参与 RTL 内定义 | 宏只在一个地方定义;Makefile 改动后要触发重综合 |
| 仿真新、综合旧(或反过来) | 仿真文件列表与综合 read_verilog 列表各维护一份 | 对比仿真脚本文件清单与 Yosys 脚本 | 同一份文件列表变量供两处引用 |
| 换分支后行为怪异 | build 目录里留着上一分支的中间产物,make 误判"已最新" | git status 干净但 build 产物时间早于切分支时刻 | 切分支后 make clean;产物目录加入 .gitignore 并定期清 |
三、排查决策图
先分清"Yosys 根本没跑"和"Yosys 跑了但读错/写错",两条线的处置完全不同。
颜色按角色区分:红色是问题入口,蓝色是三步排查动作,橙色是修复手段,绿色是验证通过的收尾,紫色是长期预防。读这张图抓住一条主线:先回答"Yosys 到底跑没跑",再回答"跑的输入对不对"——Makefile 依赖缺失让 Yosys 根本没出场的情况,在真实工单里比 Yosys 自身任何问题都多,直接翻 Yosys 手册查缓存行为是南辕北辙。
四、逐条排查
第一步,确认 Yosys 真的重跑了。 最省事的办法是 make clean && make,但如果想先定位:敲 make -n(或 --dry-run),make 会把"将要执行的命令"打印出来而不真跑。如果输出里没有 yosys 字样,病根就在 Makefile 依赖。一个常见的缺陷写法是 .json 目标只依赖顶层文件:
# 缺陷:改了 rtl/uart.v 不会触发重综合
build/top.json: rtl/top.v
yosys -p "read_verilog rtl/top.v rtl/uart.v; synth_ice40 -json $@"修法是把所有读入的源文件都列进依赖,或者用通配符自动收集:
SRCS := $(wildcard rtl/*.v)
build/top.json: $(SRCS)
yosys -p "read_verilog $(SRCS); synth_ice40 -json $@"注意 Makefile 里 $@、$(SRCS) 这类变量展开写法;同时警惕另一种坑:宏参数从命令行传入(yosys -D WIDTH=16)时,Makefile 自身的改动不会触发依赖更新,改了 -D 参数记得手动 clean 或把参数写进一个被依赖的文件。
第二步,确认读入的是你以为的那份文件。 把综合日志里 Executing Verilog-2005 frontend: 后面的路径全部列出来,挨个检查:有没有同一个模块被两个文件定义(后读覆盖先读,Yosys 只在特定配置下才报 duplicate module 警告,静默覆盖是常态);路径是相对哪个目录解析的——Yosys 的相对路径相对启动 yosys 时的当前目录,不是脚本文件所在目录,在子目录里手跑脚本和在仓库根 make 跑,读到的可能不是同一份文件。
第三步,核对中间产物的接力棒。 开源流程是典型的"管道接力":Yosys 出 .json → nextpnr 出 .asc → icepack 出 .bin。每一棒的输入路径必须接上一棒的输出路径。用 ls -l 排一排 build 目录里各产物的时间戳,正常全流程应该时间递增且都新于你最后一次改 RTL。哪个文件时间戳"掉队"了,断点就在哪一棒。read_verilog 顺序与层次问题导致的"读到旧定义"属于更细的语法语义层,可配合 Yosys 语法/解析报错速查 一起看。
第四步,清理 abc 与并发的残留。 Yosys 综合过程中会调用外部的 abc 做工艺映射,默认在临时目录起落脚文件;异常中断(Ctrl-C、断电、磁盘写满)可能留下残留,个别版本在并发 make -j 多目标同时综合时也会互相踩。表现是"有时好有时怪"。处置:改串行跑一遍验证、清空系统临时目录里 abc 相关残留、升级到较新的 Yosys 版本。如果你的问题是"综合一直跑不完"而非结果陈旧,方向不同,见 Yosys 运行卡死/超时速查。
第五步,验证改动真的被吃进去了。 三个可操作的检查:看综合日志末尾的统计表——Number of cells、Number of wires 应与改动相称地变化(改了个状态机数字纹丝不动,就是没吃进去);stat 对比源文件 mtime 与 .json 产物时间,产物必须更新;对 .json 算 md5sum,改 RTL 前后各算一次,哈希相同即铁证。
五、防复发:四条纪律
- 依赖清单自动化。 别手工维护 Makefile 的源文件列表,
$(wildcard rtl/*.v)或让 Yosys 脚本与 Makefile 共用一份变量;新增文件时依赖自动跟上。这是"改了不重编"类问题的根。 - 同名模块工程内唯一。 遗留代码、第三方核、自己的重写版,同一模块名只留一份进综合流程;确实要并存的,改模块名或加
read_verilog -lib按需显式例化。 - 产物目录隔离且可一键清空。 所有中间产物集中进
build/,make clean就是rm -rf build/;build/进.gitignore,切分支后随手 clean。nextpnr 拿旧.json这种事故,多半是旧产物在多目录间流浪。 - 改宏/参数后强制重综合。 命令行
-D参数不进 make 依赖图,养成"改了 Makefile 传参就make clean"的肌肉记忆;或把参数写进一个defines.vh头文件并纳入依赖,让改动自动传导。
这一篇在知识体系里的位置
本篇是 FPGA 开源工具链排错速查簇中"构建产物一致性"节点在 Yosys 侧的 FAQ 页。与它相邻的两个方向:读入阶段的语法/解析硬错误走 Yosys 语法/解析报错速查,综合过程跑不完、卡在某一步走 Yosys 运行卡死/超时速查。三篇合起来覆盖 Yosys 日常使用的高频抱怨:"结果不对"看本篇,"读不进来"看语法篇,"跑不出来"看卡死篇。
一句话总结
Yosys 缓存陈旧行为怪异,病根几乎都在 Yosys 周围的流程而非工具自身:Makefile 依赖没列全导致 make 跳过综合、脚本里 read_verilog 读了两份同名模块后读覆盖先读、Yosys 输出与 nextpnr 输入路径不一致让下游吃旧产物、abc 临时文件残留、命令行宏参数改了但不触发重综合、切分支后 build 目录留着旧中间产物;排查先用 make -n 确认 Yosys 跑没跑,再核对日志里读入的文件列表与产物路径时间戳,最后 make clean 全量重建并比对 cell 统计与哈希确认改动生效;长期靠依赖通配自动收集、同名模块唯一、产物目录隔离入 ignore、改参数即 clean 四条纪律防复发。