Yosys techmap 递归映射循环:自定义模板自引用与不收敛修复
更新时间:2026-10-30。本文是 Yosys techmap 递归映射循环所在的开源 EDA 排错中心 的映射模板节点。
techmap会用自定义模块替换网表单元,并继续处理替换产生的新单元;如果$not的模板又生成同参数$not,映射就没有终点。Yosys 0.33 在本例中没有给出专门的循环诊断,而是停在Continuing TECHMAP pass,最终由外部超时终止。
本文要回答的问题
- 自引用模板在 Yosys 0.33 中留下什么日志,退出码是多少?
techmap什么时候停止展开,为什么单元数量不变也可能不收敛?- 怎样区分模板未命中、参数错误和真正的递归映射?
- 如何拆分目标单元与替代单元,并证明修复后已经收敛?
这张漏斗的关键是最后两层:日志停在 techmap 只能说明展开阶段未结束,检查模板产物中出现什么单元,才能把“工具慢”与“映射关系成环”分开。
一、完整现场:Yosys techmap 递归映射循环由超时截断
最小顶层只有一个按位取反,读入后形成粗粒度 $not 单元。坏模板的模块名也是 $not,内部却再次实例化 $not,并传递 A_WIDTH、Y_WIDTH、A_SIGNED 三个参数。实验使用 geek-eda:0.2 容器中的 Yosys 0.33 (git sha1 2584903a060),坏例限制 256 MB 地址空间和 3 秒运行时间,避免失控映射占满机器。
日志里 Verilog 前端与 hierarchy 均已完成,最后一个阶段是 3.2. Continuing TECHMAP pass。随后没有 No more expansions possible,也没有正常网表统计;# bad-exit-code: 137 是 timeout -s KILL 3s 终止进程的结果,不是 Yosys 自己识别循环后返回的错误码。原始坏例输出只有 48 行、2324 字节,因为它在持续展开时没有逐层打印可用来计数的递归深度,不能凭这份日志虚构深度数值。
这里也不能把退出码 137 当成所有环境的固定诊断。换用内存限制、不同超时信号或交互终端,表现可能变成内存耗尽、进程被系统终止或长时间无响应。稳定可复现的是“模板自引用导致展开不终止”,外部限制只是保护实验环境。
二、根因:替换产物仍然是待映射目标
techmap 的终止条件不是“所有单元数量不再增长”,而是没有还能按当前映射规则展开的单元。一次替换即使只把一个 $not 换成另一个 $not,总数仍为 1,新单元依旧命中同一模板;下一轮重复同样动作,形成无限序列。
module \$not (A, Y);
parameter A_WIDTH = 1;
parameter Y_WIDTH = 1;
parameter A_SIGNED = 0;
input [A_WIDTH-1:0] A;
output [Y_WIDTH-1:0] Y;
\$not #(
.A_WIDTH(A_WIDTH), .Y_WIDTH(Y_WIDTH), .A_SIGNED(A_SIGNED)
) u_loop (.A(A), .Y(Y));
endmodule参数原样下传尤其值得注意:这不只是名字碰巧相同,而是新单元在类型与参数上都继续满足原映射条件。下面红色表示循环根因,蓝色表示检查动作,橙色表示替代映射,绿色表示可终止状态。
读图时要把“未命中”单独排除:例如网表当前是 $not,模板却只定义 $_NOT_,工具会很快结束,但目标单元没有被替换。那属于映射覆盖率问题,不是递归循环。真正的不收敛要求模板先命中,再在产物里反复制造可命中的目标。
三、排查清单:沿单元依赖边找环
自定义映射较多时,不必先猜是哪份文件。把每个模板写成“目标类型 → 产物类型集合”的依赖边,重点检查回边;再辅以最小设计和受控运行。
| 检查项 | 证据或现象 | 处置 |
|---|---|---|
| 实际单元类型 | stat 显示 $not,模板却是 $_NOT_ | 先修正目标名称,不当作循环 |
| 参数派生 | 报 Can't find object for defparam | 补齐模板参数,这是另一类前端错误 |
| 直接自引用 | 目标与产物同名,参数继续下传 | 改用更低级或不同的替代单元 |
| 间接成环 | A 模板生成 B,B 模板又生成 A | 拆开映射层级,明确最终落地类型 |
| 表达式再生成 | 模板中的运算又综合成当前目标 | 查看展开结果,显式写目标库单元 |
多模板互相生成同类型单元也会造成 A→B→A,并不限于单个模块直接实例化自己。相反,只要产物最终落到本组规则不再处理的类型,即使一次生成多个门,仍可在有限轮次结束;因此“单元变多”本身不是死循环判据。
设计模板库时,可以先列一张类型分层:粗粒度运算单元只降到门级单元,门级单元只落到具体工艺库单元,工艺库单元不再作为本组模板的输入。每一轮都让类型沿这个方向前进,映射关系就形成有向无环图。若必须让同一种类型经历多次变换,应让参数或属性单调变化,并确保下一轮的匹配条件最终不再成立;仅改实例名、线网名或单元编号不会打破循环。
还要区分“替换目标”和“实现目标”的名字。某些工艺库单元恰好与中间单元同名,如果直接读入同一份映射规则,即使源码看似只实例化库单元,也会再次被模板吃掉。此时应拆分映射脚本、限定选择集合,或给中间表示与最终库单元使用不同类型,并在每一步后用 stat 检查实际落点。
四、修复代码与复跑 PASS
本例希望把粗粒度 $not 降到门级单元,就在模板中显式实例化不同的 $_NOT_,不要再用 assign Y = ~A 让前端重新形成粗粒度取反。
module \$not (A, Y);
parameter A_WIDTH = 1;
parameter Y_WIDTH = 1;
parameter A_SIGNED = 0;
input [A_WIDTH-1:0] A;
output [Y_WIDTH-1:0] Y;
\$_NOT_ u_fixed (.A(A), .Y(Y));
endmodule修复例执行 yosys -p 'read_verilog top.v; hierarchy -top top; techmap -map loop_fixed.v; stat; check'。下面保留展开结束、单元统计和最终退出码:
3.2. Continuing TECHMAP pass.
Using template $paramod$eb1d493a4d5af9c0d5d55affff786b701054b835\$not for cells of type $not.
No more expansions possible.
<suppressed ~19 debug messages>
4. Printing statistics.
=== top ===
Number of wires: 5
Number of wire bits: 5
Number of public wires: 2
Number of public wire bits: 2
Number of memories: 0
Number of memory bits: 0
Number of processes: 0
Number of cells: 1
$_NOT_ 1
5. Executing CHECK pass (checking for obvious problems).
Checking module top...
Found and reported 0 problems.
End of script. Logfile hash: 38fca4498c, CPU: user 0.00s system 0.00s, MEM: 9.00 MB peak
Yosys 0.33 (git sha1 2584903a060)
Time spent: 45% 3x read_verilog (0 sec), 36% 1x techmap (0 sec), ...
# fixed-exit-code: 0
RESULT: PASS (bad stopped with exit 137; fixed passed with exit 0)No more expansions possible 直接证明映射循环已经终止;stat 中唯一的单元是 $_NOT_,说明原目标已被替代;check 报 0 个问题且修复例退出 0。这里的 PASS 只覆盖映射收敛和网表结构检查,没有运行功能仿真或形式等价,不能据此声称替换前后功能等价。
代码获取
完整源码、运行脚本和真实日志位于公开仓 experiments/ts-yosys-techmap-loop。在本目录执行 bash run.sh 即可复现;脚本会给坏例加地址空间与强制超时限制,再运行修复例的 techmap/stat/check。
实验环境
- 机器/系统:Mac mini M4 / macOS,Docker 容器内运行。
- 工具版本:Yosys 0.33 (git sha1 2584903a060),
geek-eda:0.2镜像,GNUtimeout。 - 日期:2026-10-30。
- 完整命令:
timeout -s KILL 3s yosys -p 'read_verilog top.v; hierarchy -top top; techmap -map loop_bad.v';yosys -p 'read_verilog top.v; hierarchy -top top; techmap -map loop_fixed.v; stat; check'。坏例子进程另设ulimit -v 262144,全部标准输出与标准错误合并记录。
这一篇在知识体系里的位置
本篇处理映射规则自身的终止性,不是组合电路里的信号环。Yosys techmap 递归映射循环之外的参数派生错误关注模板参数和 defparam 的前端约束,通常在展开前就能给出明确报错;本文则面对已经命中模板、却持续再生目标单元的情况。
总结
Yosys techmap 递归映射循环的根因是替换产物仍能被同一组规则继续展开,直接自引用和多个模板间接成环都可能触发。实测 Yosys 0.33 对 $not → $not 没有给出专门循环诊断,3 秒强制超时以 137 终止;把替代单元改为显式 $_NOT_ 后,日志出现 No more expansions possible,统计只剩 1 个门级单元,check 为 0 问题并退出 0。排查时沿“目标类型 → 产物类型”找环,而不是只看单元总数。