Yosys flatten 取舍:什么时候展平层次,什么时候保留 hierarchy
更新时间:2026-11-04。综合是什么 讲过 RTL 会被翻译成门级网表,但你的 RTL 通常不是一个大模块,而是顶层例化若干子模块的层次结构。Yosys 默认保留这层结构,
flatten这个 pass 则负责把它拍平:用子模块的内部实现替换每一个实例。本篇讲正常流程里这个动作的取舍——它对 stat 报告的读数口径、调试时的信号名、形式验证和后端工具分别意味着什么。
本文要回答的问题
flatten到底做了什么,跑与不跑,电路本身变不变?- 展平前后
stat的模块小节、cell 计数口径有什么差异? - 为什么 flatten 之后
stat sub报找不到模块,网表里的名字变成\g[0].u_inst.q? - 默认
synth会不会展平?synth -flatten在流程的哪一步插入? - 什么场景该保留 hierarchy,什么场景才值得展平?
先给一张左右对照卡,全篇的结论都在这张卡里:
这张卡的读法是:左栏每一条都对应「子模块还是一个独立容器」,右栏对应「容器被拆掉、内容倒进顶层」。最下面那行是全篇最重要的事实——展平不增删任何电路,下面逐段用真实输出验证。
一、这一步在流程里的位置
read_verilog 读进来的设计只是一堆互相独立的模块文本;hierarchy pass 顺着例化关系自顶向下整理,确定顶层、删除没被用到的模块,但保留每个被用到的子模块。flatten 是可选的分水岭:它用每个实例对应模块的内部实现替换该实例,最后删除无子实例的子模块。展平之后,fsm、memory、opt 等优化 pass 看到的就是一个无边界的单一模块。
读这张图抓住一点:橙色的 flatten 不是必经之路,而是插在「RTL 级处理」与「深度优化/映射」之间的一个开关。默认 synth 不插它,层次一直留到流程末尾;加 -flatten 才在 hierarchy 之后立刻展平,让后面所有优化都在拍平的网表上进行。
二、命令逐段解读
单独执行展平的最小脚本如下,四个 pass 各司其职:
yosys -p "read_verilog sub.v top.v; hierarchy -top top; proc; opt; flatten; stat"| 片段 | 作用 | 不写会怎样 |
|---|---|---|
read_verilog sub.v top.v | 读入子模块与顶层两个文件 | 没有设计对象,后续全部空转 |
hierarchy -top top | 建立例化树、定顶层、清理未用模块 | 多模块时顶层靠猜,未用模块混在报告里 |
proc; opt | 把 always 展开成 dff/mux 并做基本优化 | 直接 flatten 写出的网表仍带进程,阅读价值低 |
flatten | 用实现体替换所有子模块实例,删除空模块 | 层次保留,stat 仍按模块分小节 |
stat | 按当前口径打印统计 | 看不到展平效果 |
flatten 的完整签名是 flatten [options] [selection],常用法只有两点。一是它支持选择器,默认对整个设计生效,也可以只展平某一层;二是两个例外属性:带 keep_hierarchy 属性的模块或 cell 不会被展平,带 whitebox 属性的实现默认被当作白盒保留,只有加 -wb 才连白盒一起展平。想让某个硬核或 IP 边界在展平后依然存在,就在 RTL 里给它标 (* keep_hierarchy *)。
另一种触发方式是 synth -top top -flatten:它不改默认行为,只是在通用流程的 hierarchy(3.1)之后紧接着插入一次 FLATTEN(3.3)。注意默认 synth 结尾只有 hierarchy 检查、没有 FLATTEN,这是新手最容易搞反的一点。
三、输入输出:stat 口径与网表结构怎么变
实验设计很小但层次完整:sub.v 是一个带使能和异步复位的 4 位累加器(内含加法器与使能触发器),top.v 用 generate 例化 4 个 sub。先看保留层次时 hierarchy; proc; opt; stat 的真实输出:
这份报告有三个统计小节。sub 小节是单个实例的账本:2 个 cell(一个 $add 加法器、一个 $adffe 带使能异步复位触发器)。top 小节只数顶层本层的东西:4 个 cell,但它们的类型就是 sub——四个实例引用,顶层本身看不到触发器和加法器。最后的 design hierarchy 汇总才把整棵树摊开:wires 29、wire bits 98、cells 8($add ×4、$adffe ×4)。stat 字段含义在上一篇逐行讲过,这里关注的是口径:保留层次时,一个物理触发器只在 sub 小节出现,顶层贡献为零。
同一份设计加上 flatten 后再 stat:
日志先明确交代动作:Deleting now unused module sub.,随后报告只剩 === top === 一个小节,而数字与之前的汇总分毫不差:wires 29、wire bits 98、cells 8($add ×4、$adffe ×4)。这就是卡上那条结论的出处——展平前后电路完全相同,变化的只是「数字记在谁的名下」。这对面积归因有直接影响:配 liberty 跑 stat -liberty 时,保留层次能分别看到每个子模块的 Chip area,展平后只剩顶层一个总数,无法再按模块归属面积。
write_verilog 吐出的网表结构差异同样直观,下表全部来自本次实跑的真实输出:
| 对照项 | 保留层次(net_hier.v) | flatten 后(net_flat.v) |
|---|---|---|
| 模块声明 | module sub(...)(第 3 行)与 module top(...)(第 21 行) | 只有 module top(...)(第 3 行) |
| 例化语句 | sub \g[0].u_inst (...) 等 4 行 | 无,实例已被实现体替换 |
| 内部线网名 | 短名 q、en,归属于 sub | 转义长名 \g[0].u_inst.q、\g[1].u_inst.en |
注意右栏的名字:层次没了,但 Yosys 仍用原例化路径给信号起名(反斜杠是 Verilog 转义标识符),所以调试时波形里看到 \g[2].u_inst.q 不要以为是乱码,它就是「第 3 个 generate 分支里 u_inst 的 q」。这种名字在波形工具和后仿真里能用,但在 写网表给后端时,按层次做 floorplan、功耗区域划分的流程就失去了分组依据。
四、常见错与检查点
| 现象 | 先查什么 | 纠正 |
|---|---|---|
flatten 后 stat sub 报 Selection "sub" did not match any module | 子模块是否已被当空模块删除 | 这是正常结果;要按模块检查就把 stat sub 放到 flatten 之前,或用 keep_hierarchy 保留边界 |
网表/波形里的信号变成 \g[0].u_inst.q 一类转义长名 | 是否已经过 flatten | 需要在波形里按短名调试,就先在层次版网表上仿真;展平后只能沿例化路径认名 |
| 明明没写 flatten,网表却是平的 | 是否用了 synth -flatten 或脚本前半段有 flatten | 默认 synth 保留层次;去掉该选项,并确认检查 pass 的插入位置 |
| 期望优化器消除跨模块冗余,结果没动静 | 优化 pass 是否被层次边界挡住 | 在 proc; opt 之后显式 flatten,再跑后续 opt/map,边界消失后优化器才能跨例化重组 |
最后一条值得展开:层次边界对优化是约束。本次实跑 synth -flatten 的日志里,FLATTEN 之后的 opt 处理对象名字全部带上了 $flatten\g[0].u_inst. 前缀,紧接着对四个加法器逐个建立 macc/alu 模型——它们现在是同一个模块里的平级 cell,可以被合并、共享常数、跨边界重写;而保留层次时每个 sub 各自优化,顶层无法动子模块内部。代价就是前三行所说:可读性、可验证性、物理层次全部让渡。
五、真实跑一遍:两种 synth 的流程位置对照
对同一设计分别跑默认 synth 与 synth -flatten,抽取流程中与层次相关的阶段横幅,差异落在一个编号上:默认流程只在 3.1 和 3.24 出现 HIERARCHY,全程没有 FLATTEN;加 -flatten 后 3.1 HIERARCHY、3.3 立刻 FLATTEN,之后的优化全在顶层进行。
图里 Adding EN signal on $flatten\g[3].u_inst... 这几行是最硬的证据:优化器操作的归属模块已经是 top,对象名里只剩路径、没有模块边界。对照上一篇 stat 报告用的默认 synth,那份输出的结尾仍有完整的 design hierarchy 树。跑一遍实验只需两个源文件加一条 make run,八段对照输出与本文引用完全一致。
保留还是展平,按下游决定:形式验证要在子模块边界挂 assume/assert/cover、后端要按层次做物理规划、调试要在波形里按短名追信号、IP 硬核必须留边界——这四种情况保留 hierarchy,需要时用 keep_hierarchy 防误展平;追求最后一轮面积/时序收敛、要让优化器跨实例合并重组、或下游明确要求单一模块网表(部分扁平网表流程和等价性检查脚本),才在优化前 flatten。两种做法产出的电路等价,差别全在工程可用性。
实验环境
- 机器/系统:macOS(Darwin 25.5,Apple Silicon),实验在 geek-eda:0.4-flow Linux 容器中运行。
- 工具版本:Yosys 0.33 (git sha1 2584903a060)。
- 日期:2026-11-04。
- 完整命令:在实验目录执行
make run(即docker run --rm -v "$PWD":/work -w /work geek-eda:0.4-flow bash run.sh),脚本依次输出保留层次时的 stat 与网表清单、按子模块名 stat、flatten 后的 stat 与网表清单、flatten 后再按名 stat,以及默认synth与synth -flatten的阶段横幅对照。
代码获取
两个 RTL 源文件、八段对照脚本与一键复现 Makefile 已同步到公开仓库:geek-doc.cn/experiments/flow-yosys-flatten-hierarchy,克隆后进入该目录执行 make run(需要本机有 geek-eda:0.4-flow 镜像)即可复现本文引用的全部输出。
这一篇在知识体系里的位置
- 想先搞懂 flatten 前后 stat 每一行字段怎么读:读 Yosys stat 报告怎么读,本篇讲「展平这个动作」,那篇讲「数字本身」;
- flatten 在完整综合流程的哪一步、默认 synth 还有哪些阶段:读 拆解 Yosys synth 命令与第一次跑 Yosys 综合;
- 展平后的网表如何交给后端、为什么有些流程必须保留层次:读 Yosys write_verilog 导出网表;
- 展平后仍想确认电路没有被改坏:用 Yosys check 命令做无驱动、多驱动检查。
总结
Yosys flatten 的取舍可以一句话概括:它把所有子模块实例用实现体就地替换并删除空模块,电路一个 cell 不变(本例展平前后都是 wires 29、wire bits 98、$add ×4 加 $adffe ×4 共 8 个 cell),变的是工程口径——stat 从按模块分小节变成顶层一笔总账、网表从两个 module 加例化语句变成单个 module 加 \g[i].u_inst.xxx 转义长名、stat sub 从此匹配不到模块。默认 synth 保留层次,只有显式 flatten 或 synth -flatten 才在 3.3 节展平并放开跨边界优化;形式验证、物理后端、波形调试与硬核 IP 场景保留 hierarchy(必要时标 keep_hierarchy),追求面积时序收敛或下游要求扁平网表时才展平。