Yosys 模块重复定义:Re-definition of module 排查与修复
更新时间:2026-10-30。FAQ 速查页,属于 EDA 报错排查系列。Yosys 模块重复定义通常出现在多文件读入阶段:第二个文件声明了设计中已经存在的
module,Yosys 0.33 直接报Re-definition of module并退出。其他工具的名字相似但处理口径不同,可对照 Verilator MODDUP 同名模块重复定义排查,不要照搬其警告选项。
首屏答案:同一个文件被传了两次,修文件列表或命令行,让输入只出现一次;两个文件包含不同实现,就不能靠排序去重,应删除旧副本,或给仍需保留的一套模块及例化同步改名。read_verilog -defer 在本例仍报错;-lib 会替换已有黑盒,不是用来合并两份可综合实现的开关。
本文要回答的问题
- 完整报错指向哪份文件、哪个模块,为什么看不到
hierarchy输出? - 重复传文件与两份实现撞名,怎样用最少命令区分?
- 去重和 rename 分别怎么改,哪些引用必须一起更新?
-lib与-defer为什么不能当通用修复?- 修复后怎样用层次检查和综合确认闭环?
读这张对比卡只需看「路径与内容」两项证据。同名不等于同一份代码,同名也不等于都该保留;修复对象是构建输入或设计名字,而不是错误文本。
一、完整报错:第二份 pulse 定义触发退出
最小设计有顶层 top,以及 pulse_a.v、pulse_b.v 两份同名 pulse。前者实现 a & b,后者实现 a | b。一条 read_verilog 同时读入三个文件,足以复现:
yosys -p 'read_verilog top.v pulse_a.v pulse_b.v; hierarchy -check -top top; check -assert'Yosys 0.33 已生成 top 和第一份 pulse 的 RTLIL 表示,读第二份时在解析阶段停止。RTLIL 是 Yosys 内部的寄存器传输级中间表示,可理解为读入源码后的设计数据库。
关键原文是 pulse_b.v:1: ERROR: Re-definition of module \pulse'!,随后 exit=1。这里只报告第二次定义的位置,没有像某些 linter 那样补出第一次定义行号;因此要根据读取顺序回查前一个 pulse_a.v。错误发生在 read_verilog,后面的 hierarchy和check` 尚未执行。
二、根因与动线:设计名字必须唯一
一次 Yosys 读入过程中,module pulse 是设计单元的名字,不是可以按文件分别存放的局部变量。顶层写 pulse u_pulse (...) 时,需要唯一实现供后续 hierarchy 绑定。普通前端遇到第二个具体实现会立即拒绝,不会自动比较文本,也不会按文件修改时间挑新版本。
红色是报错入口,蓝色用于定位与分类,橙色是真正改变输入或 RTL 的修复,绿色表示复验。两条橙色分支不能互换:两份不同实现不能随意删一个,同一文件重复传入也没必要修改模块名。
三、排查清单:把定义位置和输入展开对上
先查模块声明,再查文件列表。只搜 pulse.v 会漏掉 pulse_a.v 这种文件名与模块名不同的情况,也会漏掉生成目录中的副本。
rg -n '^\s*module\s+pulse\b' .
rg -n 'pulse_a\.v|pulse_b\.v' --glob '*.f' --glob '*.ys' --glob 'Makefile*' .
diff -u pulse_a.v pulse_b.v如果工程没有 rg,可用递归 grep 替代。还要检查 shell 变量、通配符和 `include:源文件被文本包含后,又作为独立输入读取,同样会形成第二份定义。
| 观察结果 | 根因判断 | 处理决定 |
|---|---|---|
| 同一路径出现两次 | 命令行或文件列表重复展开 | 输入去重 |
不同路径但 diff 无差异 | 源目录、生成目录或旧副本混入 | 保留权威副本 |
| 不同路径且逻辑不同 | 新旧实现或两个 IP 撞名 | 淘汰其一或 rename |
| 第二次定义来自 include | 包含与独立编译叠加 | 两种引入方式只留一种 |
实测把 pulse_a.v 原样传两次,仍在该文件第 1 行报同一个 Re-definition,退出码也是 1。这证明 Yosys 不会因为「内容看起来相同」自动消重。
四、修复代码:去重与 rename 各自闭环
本例若选择与门实现,输入从三个文件改为两个,RTL 完全不动:
-read_verilog top.v pulse_a.v pulse_b.v
+read_verilog top.v pulse_a.v若或门也是独立实现、必须同时保留,则给第二份设计改成 pulse_or,并让选择它的顶层例化同步改名。只改磁盘文件名没有用,冲突比较的是 module 后的标识符。
module pulse_or (
input wire a,
input wire b,
output wire y
);
assign y = a | b;
endmodule- pulse u_pulse (
+ pulse_or u_pulse (
.a(a), .b(b), .y(y)
);rename 后还应全库搜索旧名字,更新其他例化、层次路径、约束和验证环境引用。若旧名字仍被别处使用,要按语义决定保留原实现还是改用新实现,不能做一次机械全局替换后只看综合是否结束。
五、参数边界:-lib 与 -defer 都不是消重开关
read_verilog -defer 的作用是推迟生成 RTLIL,先保存抽象语法树,常用于后续参数化派生。它不改变模块命名规则。本例的延迟读取仍在 pulse_b.v:1 报错,只是模块名显示为 $abstract\pulse,退出码仍是 1。
read_verilog -lib 用于只保留黑盒和端口信息。两份同名文件在该模式下返回 0,但日志明确写出替换,而不是合并:
2. Executing Verilog-2005 frontend: pulse_b.v
Parsing Verilog input from `pulse_b.v' to AST representation.
Replacing existing blackbox module `\pulse' at pulse_b.v:1.1-7.10.
Generating RTLIL representation for module `\pulse'.
Successfully finished Verilog frontend.
End of script. Logfile hash: eb5fcbda80, CPU: user 0.01s system 0.00s, MEM: 10.22 MB peak
Yosys 0.33 (git sha1 2584903a060)
Time spent: 100% 3x read_verilog (0 sec)
exit=0这里的零退出码不能当修复证据:后读文件替换了已有黑盒,且本例没有读入顶层、没有完成实现综合。-lib 适合刻意加载库声明的场景,不适合让两份真实实现共存;-defer 适合延迟展开,也不负责去重。
六、复跑 PASS:两种修复都经过完整综合
去重版读取 top.v pulse_a.v,改名版读取 top_fixed.v pulse_a.v pulse_or.v。两者都执行:
yosys -p 'read_verilog top_fixed.v pulse_a.v pulse_or.v; hierarchy -check -top top; synth -noabc -top top; check -assert'改名版综合后顶层包含一个或门单元,check 报告零问题;去重版对应一个与门单元,同样退出码为 0。两处逻辑差异也说明复跑不仅检查了名字,还实际综合了各自选择的实现。
6. Executing CHECK pass (checking for obvious problems).
Checking module pulse_or...
Checking module top...
Found and reported 0 problems.
End of script. Logfile hash: 348316bb97, CPU: user 0.07s system 0.01s, MEM: 12.12 MB peak
Yosys 0.33 (git sha1 2584903a060)
Time spent: 22% 5x read_verilog (0 sec), 22% 12x opt_expr (0 sec), ...
exit=0
PASS: duplicate errors reproduced; dedup and rename designs synthesized successfully复验要保留原有顶层与约束,不要把输入缩减到只剩子模块来「通过」。hierarchy -check 确认实例能绑定,synth 走完实现处理,check -assert 把检查发现的问题转成失败;三者都完成且退出码为 0,才是本例的闭环标准。
实验环境
- 机器/系统:Linux 容器环境(Docker 镜像
geek-eda:0.2) - 工具版本:Yosys 0.33 (git sha1 2584903a060)
- 日期:2026-10-30
- 完整命令:
bash run.sh;脚本依次验证同名异实现、同文件重复传入、-defer、-lib、输入去重和模块改名,并逐项核对退出码。
代码获取
最小 RTL、六组复跑命令与完整记录位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-yosys-duplicate-module-definition。进入该目录执行 bash run.sh,可复现三处错误并验证去重与改名版本综合通过。
这一篇在知识体系里的位置
本篇处理 Yosys 读入阶段的设计名字冲突。Verilator MODDUP 排查是另一个工具的同类入口,但告警级别与参数不可互套;Yosys read_verilog -sv 不支持语法排查则发生在语法解析而非模块命名层面。返回 EDA 报错排查系列 可继续按工具与阶段定位。
总结
Yosys 模块重复定义先看第二次报错位置,再回查同一 read_verilog 的输入来源。同文件重复就去重,旧副本混入就保留权威版本,两份不同实现都要保留时同步 rename 定义与例化。-defer 仍拒绝重名,-lib 只是替换黑盒;修复后以原顶层完成 hierarchy -check、synth、check -assert,退出码为 0 才算解决。