Yosys 信号被优化看不到速查:netlist 与波形里找不到信号名
更新时间:2026-10-23。FAQ 速查页,面向用 Yosys 做开源综合、再用
show看图或把 netlist 喂给仿真/形式验证的场景。症状很统一:RTL 里明明声明了的信号,综合完用show画图找不到节点、dump/select按名搜不到、或者把综合后 netlist 拿回仿真器里跑波形时信号名整个消失。九成不是 Yosys 出了 bug,而是opt_clean、proc、hierarchy这些 pass 按规则合法地把它清掉或改名了。本文按真实输出风格出发逐条排查;用 Quartus 的同学请转 Quartus 信号被优化掉速查,Vivado 场景见 Vivado 信号被优化速查,综合结果疑似陈旧不更新的问题见 Yosys 构建缓存陈旧速查。
本文要回答的问题
show或select找不到信号,对应综合脚本里哪几个 pass 干的?- 信号还在但波形/网表里名字变了,是
hierarchy展平还是proc转换导致的? (* keep *)和(* keep_hierarchy *)写在哪一级、管到什么程度?- 不想动 RTL,能不能临时关掉部分 opt pass 把信号留下来?
- 想看「综合前」的信号名,应该在哪一步 dump netlist?
一、典型现象与一句话根因
以下输出按 Yosys 常见日志风格整理,模块名与信号名均已占位:
1. show 或 select 按 RTL 名字找不到节点:
ERROR: Selection `debug_cnt' is empty!
Warning: Selection for "u_sub/debug_cnt" is empty.2. 综合日志里信号被点名清理(opt_clean 的典型输出):
2.1. Executing OPT_CLEAN pass (remove unused cells and wires).
Removing unused cells or wires.
Removed 12 unused cells and 34 unused wires.3. 常量折叠相关的日志线索:
3.5. Executing OPT_EXPR pass (perform const folding).
Optimizing module top.
Folded $not$rtl.v:42$5 to constant 1'0.一句话根因:Yosys 的每个 pass 都在为「更小的 netlist」服务——opt_clean 裁掉没有负载的 wire 和 cell、opt_expr 把恒值逻辑折叠成常量、proc 把 always 块翻译成 DFF 后原始的过程内信号不复存在、hierarchy -top 展平后中间层级信号被改写成扁平名。你在综合终点拿 RTL 原名去找,自然找不到。 排查总原则只有一句:先在综合脚本里定位是哪个 pass 之后信号消失的,再决定是加 keep 属性、临时关 pass,还是提前 dump 中间 netlist。
二、排查决策图
读图方式:红色是入口现象,蓝色是排查动作,绿色是判定通过,橙色是修复动作,紫色是防复发收尾。核心顺序是「先对日志定性,再动属性」——不看 pass 日志就直接糊 (* keep *),大概率保住了一堆本该裁掉的逻辑还找不到病根。
这张图对应三条根因轴:信号真没了(被裁/被折叠)走属性或改 RTL;信号还在但名字变了(展平/转换)走中间 netlist 或 keep;判定「正常/通过」只有一个——重跑后 select 能命中、show 能出图。注意「proc/hierarchy 后改名」分支优先指向 dump 中间 netlist 而不是硬保——很多时候你要的只是一份带原名的观测视图,不是让最终 netlist 变胖。
三、病因处置表
| 报错/现象 | 最可能原因 | 快速验证 | 处置 |
|---|---|---|---|
select 按 RTL 名完全搜不到 | 信号无负载,被 opt_clean 裁掉 | 日志搜 Removed ... unused wires | 给真实负载,或加 (* keep *) |
show 图里没有该节点 | 同上,或 cell 被合并 | 在 opt_clean 前 select 探名 | 观测信号加 (* keep *) |
| 信号能搜到但恒为 0/1 | opt_expr 常量折叠 | 日志搜 const folding、Folded | 查复位/使能是否恒无效 |
| 中间层级信号名消失 | hierarchy -top 展平改名 | netlist 里搜扁平化新名 | 用扁平名,或加 (* keep_hierarchy *) |
| always 块内的临时 reg 没了 | proc 转成 DFF/多路器后原信号消解 | 在 proc 前 write_ilang 对比 | 看中间 netlist,或把信号提出到端口级 |
| keep 加了还是被优化 | 属性挂在 wire 上但优化点在 cell | dump 看属性落在哪个对象 | 属性直接写在 reg/信号声明上 |
| 脚本里没写 opt 但信号还是没了 | synth 全家桶内含 opt 系列 | 把 synth 拆成 synth -noopt 等分步 | 用 -noopt 或分步跑观察 |
| 仿真波形里层级路径对不上 | 波形 dump 选了综合后扁平名 | 对比综合前后 write_ilang 的模块树 | 测试台改引用扁平名或综合前 netlist |
四、三个最高频根因的修法
根因一:信号无负载,被 opt_clean 合法裁掉。 最高频——声明了一个只用于观察的计数器或调试总线,输出没连到任何端口、存储器或下游逻辑,opt_clean 按规则连根清掉,日志里只有一行汇总的 Removed N unused wires,连信号名都不点。两种修法按目的选:信号本该有功能就回去补 RTL(那是真 bug);纯观测信号用属性强制保留:
(* keep *) reg [15:0] debug_cnt;
(* keep *) wire [7:0] dbg_bus;(* keep *) 告诉所有优化 pass「这个对象不许删」,不管它对输出有没有贡献。注意属性要直接写在信号声明上;挂在中间 assign 的 wire 上、而寄存器本体没标,优化点落在寄存器时照样消失。
synth 全家桶的坑:直接跑 synth 或 synth_ice40 这类宏命令时,内部自带 opt_clean、opt_expr 一整串 opt pass,你以为脚本里「没写优化」,其实每一轮都在裁。调试期想把信号尽量留下来,用 synth -noopt(各家目标族都有类似开关)或把宏命令拆成分步手动跑,观察每一步的信号存活情况。
根因二:常量折叠,信号活着但永远是定值。 症状是 select 能命中、波形能抓,但抓回来一根直线。典型剧本是使能条件写错导致某段逻辑恒不执行,opt_expr 向前推导出寄存器恒为复位值,把整条驱动链折叠成常量,日志里留 Folded ... to constant。这时加 (* keep *) 没有意义——保住一个恒为 0 的信号对调试毫无帮助,真正要修的是让使能/复位逻辑动起来。快速判别法:在 opt_expr 前后各插一句 dump debug_cnt,如果前一步还有真实驱动逻辑、后一步只剩常量连接,就是折叠干的。
根因三:proc 与 hierarchy 改名,名字对不上。 proc pass 的职责是把 always 块翻译成 DFF、多路器和同步逻辑,翻译完成后过程内的临时 reg、case 临时信号会被消解或改写成 $procdff$ 这类内部名;hierarchy -top top 展平后,子模块信号会被改写成 u_sub.debug_cnt 之类的扁平路径名,按 RTL 原名自然搜不到。处置分两条路:只想看综合前的样子,在脚本里目标 pass 之前插一句 write_ilang pre_opt.il,把中间 netlist 存下来用文本工具查原名;想让信号活到综合终点,加层级保留:
(* keep_hierarchy *) module u_sub (...);keep_hierarchy 保住模块边界不被展平,跨模块优化会变弱、面积时序可能变差,调完记得去掉。还有一个隐蔽场景:把综合后 netlist 喂给 iverilog 跑后仿真时,测试台里用层级路径引用 dut.u_sub.debug_cnt,综合后这条路径对应的模块已被展平,仿真直接报找不到层级——这时要么改引用扁平后的新路径,要么在测试台里改观测综合前的 RTL 层级,要么给 u_sub 加 keep_hierarchy 让路径存活到终点。一句话说清两个属性:(* keep *) 保单个信号,(* keep_hierarchy *) 保模块边界;日常观测用前者足够,后者只在名字对不上时临时用。
五、修复后验证与防复发
修完不要只看「这次搜到了」。先 select -assert-count 1 debug_cnt 让脚本在命中失败时直接报错(把检查写进脚本而不是靠眼睛),再完整重跑综合,最后把 netlist 喂回仿真或 show 出图闭环。防复发三件套:观测信号的 (* keep *) 直接写在 RTL 源码里随代码入库,不要只在临时调试脚本里糊;团队约定观测信号统一 dbg_ 前缀,code review 一眼可辨;综合脚本里固定在每个关键 pass 后插 stat 或 select 探针,把「信号被优化」从「仿真才发现」提前到「综合当步就发现」。综合结果不更新、怀疑跑出的是旧 netlist 时,另见 Yosys 构建缓存陈旧速查。
一句话总结
Yosys 信号被优化看不到的本质是各 pass 合法删减与命名视图错位的叠加:opt_clean 裁无负载信号、opt_expr 折叠恒值逻辑、proc 消解过程内信号、hierarchy 展平改名;先用分步跑脚本定位信号消失的 pass(日志搜 Removed/Folded 三分支),再对号入座——纯观测信号加 (* keep *)、恒值信号回去修使能、名字对不上时提前 write_ilang 存中间 netlist 或临时加 (* keep_hierarchy *),最后用 select -assert-count 把命中检查固化进脚本防复发。