yosys synth 命令逐段解读:一个宏展开成哪些子命令,分段单跑对照 stat
更新时间:2026-11-03。理论篇 综合是什么 讲过综合内部的「翻译 → 优化 → 工艺映射」三步走,本篇把镜头推近到 Yosys 的命令行:大多数教程让新手敲的
yosys -p 'synth -top demo'里,synth其实不是一个算法,而是一段打包好的脚本。本篇逐段拆开它,并在 Yosys 0.33 上用同一个小设计真跑对照。映射阶段的内部机制另见 Yosys techmap 递归映射循环,第一次上手的最小闭环可配合 Yosys 综合流程报告 一起看。
本文要回答的问题
synth -top demo这一行到底让 Yosys 依次执行了哪些命令?- help 里的
begin、coarse、fine、check四个标签各自负责什么,网表在哪一段发生质变? -top、-flatten、-nofsm、-run这些参数什么时候用,不加会怎样?- 怎样证明「宏 = 子命令脚本」,并且在综合结果异常时只重跑其中一段?
先给一张阶段总览卡,四段的职责与产物都在上面:
读这张卡抓住一条主线:单元类型在四段之间不断「降粒度」——begin 段还保留着 always(process)和 RTL 存储器,coarse 段变成 $mux、$alu、$mem_v2 这类粗粒度中间单元,fine 段才落到 $_DFF_P_、$_MUX_ 等内部门级单元,check 段不再改网表,只做统计和体检。
一、这一步在开源 EDA 流程里的位置
synth 位于「读前端」与「面向具体目标的输出」之间。它吃的是 read_verilog 解析后的设计,吐的是 Yosys 内部通用门级表示,本身不读任何工艺库、不知道时钟频率:
读图注意右端的分叉:通用 synth 跑到 abc -fast 就结束了,终点是 Yosys 自带的十几种通用门($_AND_、$_DFF_P_ 等),不是任何一家 foundry 的标准单元。做 ASIC 要在后面接 abc -liberty your.lib,做 FPGA 通常直接换用 synth_ice40 这类厂商宏,它们在同一位置插入 memory_libmap、dfflegalize 等专用段。换句话说,本篇拆的是所有 synth_* 宏共用的「默认内核」。
二、逐段解读:help synth 给出的权威展开清单
在容器内执行 yosys -p 'help synth',帮助文本末尾直接列出这个宏会执行的全部命令。这是 Yosys 0.33 的真实输出:
清单按四个标签分组,括号里的 (if ...) 是条件开关。逐段解释如下:
| 段 | 代表命令 | 对网表做了什么 |
|---|---|---|
| begin | hierarchy -check -top demo | 标记顶层、沿例化关系拉入子模块、检查悬空例化;此时代码里的 always 与数组存储器原封不动 |
| coarse | proc、opt_expr、fsm、wreduce、alumacc、share、memory -nomap | proc 把 always 过程拆成多路选择器和触发器;fsm 提取状态机;alumacc/share 归并算术与资源共享;memory -nomap 只优化、不归化,存储器收成 $mem_v2 |
| fine | memory_map、techmap、abc -fast | memory_map 把存储器摊成触发器阵列;techmap 用内置模板把粗粒度单元降到门级;abc 做门级工艺映射 |
| check | hierarchy -check、stat、check | 复查层次、打印统计、检查多驱动/组合环等明显问题,不再改动电路 |
任务卡上常见的 fflibmap、memlibmap 两个名字不属于通用 synth:它们出现在厂商/ASIC 脚本中——例如同一镜像里 help synth_ice40 的 map_ram 段就是 memory_libmap -lib +/ice40/brams.txt ...,作用是把存储器优先收进 Block RAM 而不是摊成触发器;fflibmap(需要 liberty 支持的构建)则把触发器映射到标准单元库里的具体 DFF。通用宏用 memory_map 走「全摊平」路线,这是下文 128 个 DFF 的来历。
常用参数一张表:
| 参数 | 不加会怎样 / 什么时候加 |
|---|---|
-top <模块名> | 不指定时默认顶层叫 top,多模块设计容易选错顶;顶层名不叫 top 就必须显式给,或用 -auto-top 自动判定 |
-flatten | 默认保留层次;加上后在 coarse 段执行 flatten,所有子模块被摊平进顶层,事后 stat 只剩一个模块 |
-nofsm | 默认会跑 fsm 提取并重组状态机;想手工控制编码、或前端状态机被误判时关掉 |
-noabc / -lut k | -noabc 跳过 ABC;-lut 4 改走 k 输入 LUT 架构(插入 cmp2lut/gate2lut 两段 techmap),面向 FPGA LUT |
-nordff | 禁止把触发器合并进存储器读口,排查读写时序时使用 |
-run <from>:<to> | 只执行两个标签之间的命令,还能在同一进程里续跑,是分段定位的关键开关 |
三、输入输出文件长什么样
输入只有一份可综合 RTL。本例为了让每一段都有东西可做,用了计数器加一个 16×8 同步存储器:
module demo (
input clk, input rst_n, input we,
input [3:0] addr, input [7:0] wdata,
output reg [7:0] rdata, output reg [2:0] cnt
);
reg [7:0] mem [0:15];
always @(posedge clk) begin
if (we) mem[addr] <= wdata;
rdata <= mem[addr];
if (!rst_n) cnt <= 3'd0;
else cnt <= cnt + 3'd1;
end
endmodule跑完宏之后用 write_verilog -noattr demo_netlist.v 落盘。需要特别说明这个产物的形态:文件头是 /* Generated by Yosys 0.33 */,组合逻辑被写成 166 条 assign,而 139 个触发器被写成 always @(posedge clk) 块——这是后端为了让网表仍可仿真,把内部 $_DFF_* 单元还原成行为描述,不代表综合没做。真正面向硬件时,厂商宏会输出 SB_DFF 之类的真实原语实例。除网表外,stat 的统计只进终端日志,不产生文件。
四、常见错与检查点
| 现象 | 检查 | 纠正 |
|---|---|---|
| 多个模块时综合的不是想要的顶,或报找不到顶层 | 看日志 hierarchy 段的 Top module: 行 | 显式加 -top 模块名;相关报错形态见 synth 未指定顶层 |
| 优化把想看的逻辑消掉了,却不知道是哪一 pass 干的 | 不要整跑黑盒 | 用 -run begin:coarse、-run coarse:fine 分段,段间插 stat 对比 |
以为 synth 直接产出 ASIC 门级网表 | stat 里全是 $_ 开头的内部单元 | 通用宏不含库映射;ASIC 流程另接 liberty,FPGA 流程用 synth_ice40 等厂商宏 |
| 想保留 RAM 宏单元,结果 128 个小触发器堆出来 | stat 出现大量 $_DFFE_* | 通用宏的 memory_map 必然摊平;要保留存储器改用厂商宏的 memory_libmap 段 |
加了 -flatten 后层次探针、模块名全没了 | coarse 段日志出现 flatten 记录 | 需要保留层次做增量设计时不要加,或只对局部模块 flatten |
五、真实跑一遍:整跑、分段跑、手工展开三路对照
实验设计了三种跑法。整跑是 read_verilog demo.v; synth -top demo; stat; check;分段跑利用 -run 标签在同一个 Yosys 进程里续跑:先 -run begin:coarse,再 -run coarse:fine,最后 -run fine:,每段之后插 stat;手工展开则把帮助清单里的命令逐条写进脚本,不经 synth。
分段跑的前两个断点最能说明各段的产物。begin 段结束时,过程和存储器都还在:
=== demo ===
Number of memories: 1
Number of memory bits: 128
Number of processes: 1
Number of cells: 3
$add 1
$logic_not 1
$memrd 1这 3 个单元只是前端为数组读和 cnt+1 自动生成的中间表示,always 本身还没被拆(process=1)。coarse 段结束时,过程归零、存储器被收成一个粗粒度单元:
=== demo ===
Number of memories: 0
Number of memory bits: 0
Number of processes: 0
Number of cells: 6
$alu 1
$mem_v2 1
$mux 3
$sdff 1fine 段结束后的最终统计如下,整跑、-run 分段续跑、手工逐条展开三路输出完全相同:
三路结果汇总:
| 跑法 | begin 后 | coarse 后 | fine 后 | check 问题数 |
|---|---|---|---|---|
整跑 synth -top demo | — | — | 303 | 0 |
-run 三段续跑 | 3(含 1 process、128 bit 存储器) | 6 | 303 | 0 |
| 手工逐条命令 | 3 | 6 | 303 | 0 |
303 这个数字可以逐项对上设计:$_DFFE_PP_ 128 个就是 16×8 的 mem 被 memory_map 摊成的带使能触发器;$_DFF_P_ 8 个对应 rdata;3 个 $_SDFF_PN0_ 对应带同步复位的 cnt;120 个 $_MUX_ 是读口用 4 位地址从 16 行里选一行搭出的选择树,其余是控制用的与或门。手工展开与整跑在每个断点逐位一致,这就证明了:synth 宏没有任何隐藏魔法,它就是一张按标签顺序执行的命令清单——理解了每段子命令,综合结果对你就不再是黑盒。
实验环境
- 机器/系统:Mac mini / macOS,Docker 容器内运行。
- 工具版本:Yosys 0.33 (git sha1 2584903a060),
geek-eda:0.3-sta镜像(yosys -V实测输出)。 - 日期:2026-11-03。
- 完整命令:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta yosys -p 'help synth';yosys -p 'read_verilog demo.v; synth -top demo; stat; check';yosys stages.ys(-run三段续跑);yosys manual.ys(手工逐条展开);write_verilog -noattr demo_netlist.v。容器内执行sh record.sh可一次性复现全部对照。
代码获取
完整源码、Yosys 脚本与录制脚本位于公开仓 experiments/flow-yosys-synth-command-anatomy。在本目录执行 bash run.sh 即可在 geek-eda:0.3-sta 容器中复现 help 清单、整跑、-run 分段与手工展开四路输出。
这一篇在知识体系里的位置
- 想先建立综合的整体概念(翻译、优化、工艺映射三步走)→ 综合是什么;
- 想看一次完整综合的报告怎么读、面积为何显示 0 → Yosys 综合流程报告;
- 想深入 fine 段
techmap的终止条件与自定义模板 → Yosys techmap 递归映射循环; - 顶层没选对导致的具体报错 → synth 未指定顶层。
总结
yosys synth 命令逐段解读的结论是:synth -top demo 是一个按 begin、coarse、fine、check 四个标签编排的宏,help synth 直接列出全部子命令——begin 用 hierarchy 定顶,coarse 用 proc/fsm/alumacc/share/memory -nomap 把行为沉降为 $mux、$mem_v2 等粗粒度单元,fine 用 memory_map/techmap/abc 摊平存储器并降到 $_DFF_P_ 等内部门级单元,check 段只做 stat/check 体检;Yosys 0.33 实测同一计数器加 16×8 RAM 设计,整跑、-run 三段续跑、手工逐条展开三路在各断点统计完全一致(3 → 6 → 303 个单元、0 问题),其中 128 个带使能触发器正是 RAM 被 memory_map 摊平的结果;通用宏不含任何工艺库映射,ASIC 要后接 liberty、FPGA 要换 synth_ice40 等厂商宏(memory_libmap、fflibmap 就在那一层插入),而 -run 标签是分段定位优化问题的关键开关。