Yosys 第一次综合完整流程:read_verilog 到 techmap 六步走通
更新时间:2026-11-03。概念篇 综合是什么 讲过「翻译 → 优化 → 工艺映射」三步走,本篇解决更具体的问题:第一次用开源的 Yosys,一条脚本里该按什么顺序敲哪些命令,每敲一条电路变成了什么。本文不碰报错排查,读文件失败这类问题见 yosys Can't open input file,全簇索引在 EDA 知识目录。
本文要回答的问题
- 一份 RTL 喂给 Yosys,到吐出门级网表,中间经过哪几条命令?
proc、opt、techmap各自把电路变成什么?顺序为什么不能乱?stat打印的 wires、cells、processes 数字怎么读?- 最后
write_verilog出来的网表长什么样,和 RTL 差多远?
先给一张总览卡,整条流程就是六步:
读这张卡抓住一条主线:前三步解决「读懂设计」,后两步解决「变成门」,stat 只看不改。Yosys 自带的 synth 宏命令内部也是这个顺序,本篇做的事就是把它拆开、慢动作放一遍。
一、这一步在数字设计流程里的位置
综合位于「前仿真通过之后、布局布线之前」。上游给你的是若干 .v RTL 文件,Yosys 这条最小流水线产出的是一张与具体工艺无关的通用门网表;它还不能直接拿去布局布线,要再经 abc 加工艺库映射成标准单元网表,才进入后端。数据流如下:
图里绿色是输入输出、蓝色是读入与化简、橙色是粒度下推、灰色是本篇之外的下游。注意「工艺映射」在本文只做到通用门这一档:techmap 输出的 $_AND_、$_DFFE_PN0P_ 是 Yosys 自带的理想原语,不带任何 foundry 的面积时延信息;真正绑到标准单元要读 liberty 库再跑 abc,那是下一篇的内容。
本次的设计是一个 4 位带使能计数器,RTL 核心只有两段:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) q <= 4'd0;
else if (en) q <= q + 4'd1;
end
assign carry = en & (q == 4'd15);电路虽小,却同时包含时序逻辑(带异步复位和使能的寄存器)、算术(+1)和比较(==15)三类构件,刚好能在一份 stat 里看到所有关键单元的演变。
二、脚本逐段解读:六条命令在做什么
把流程写成 run.ys 脚本,每个阶段后插一条 stat,一行一条顺序执行:
read_verilog counter4.v
hierarchy -top counter4
stat
proc
stat
opt
stat
techmap
stat
opt_clean
stat
write_verilog -noattr netlist.v各命令的参数、作用以及漏掉它的后果如下:
| 命令 | 关键参数 | 作用 | 不跑会怎样 |
|---|---|---|---|
read_verilog | -sv 按 SystemVerilog 解析;-defer 只读不立即精化 | Verilog 前端,把源码翻译成 RTLIL | 默认只认 Verilog-2005,logic、always_ff 直接报错 |
hierarchy | -top <名> 指定顶层;-check 检查例化完整性 | 定顶层、做参数展开、删除未被引用的模块 | 多模块工程里顶层靠自动猜,可能选错后逻辑被整体优化掉 |
proc | 无参数,内部是一串 proc_* 子过程 | 把 always 决策树展开成多路器和触发器 | 设计里残留 process,后续优化与映射都无法处理 |
opt | -fast 只跑便宜的几轮;-purge 清空内部线网 | 常量折叠、合并相同单元、死逻辑消除 | 残留冗余 mux 与反相器,门数虚高 |
techmap | -map <file.v> 指定自定义映射模板 | 把 $add、$eq 等粗粒度单元展开成通用门 | 网表仍是 RTLIL 算子,除 Yosys 外没有工具认识 |
stat | -liberty <库> 可顺带估面积 | 只打印统计,不改变设计 | 不影响结果,只影响你能不能看见电路 |
write_verilog | -noattr 去掉注释属性 | 把当前设计导出成 Verilog 网表 | 没有产物文件可交付 |
顺序的内在逻辑有两条:其一,proc 必须在 opt 前面——优化器只认门和多路器,always 块不展开,后面什么化简都做不了;其二,opt 必须在 techmap 前面——先在加法器、比较器这种粗粒度上化简,再展开成门,展开后的网表才干净;顺序反过来等于先把表达式拆成几十个门再化简,又慢又差。末尾的 opt_clean 不是可选项而是经验件:techmap 展开模板会留下大量悬空的临时线网,需要再扫一轮,这与 synth 内部在映射后补跑 opt -fast 是同一个道理。
三、输入输出文件:吃什么、吐什么、长什么样
输入只有一份 17 行的 counter4.v。产物 netlist.v 同样是 Verilog,但形态已经完全不同:没有 always 描述的计数行为,只有线网声明、连续赋值和触发器原语。真实网表里的三段如下:
always @(posedge clk, negedge rst_n)
if (!rst_n) q[0] <= 1'h0;
else if (en) q[0] <= _07_[0];
assign _07_[1] = _06_[1] ^ _05_[0]; // 高位的行波进位链
assign carry = en & _03_; // 等于 15 的比较已化成或非树第一段是 $_DFFE_PN0P_ 原语(上升沿锁存、低电平异步复位、带使能)在 Verilog 后端里的等价写法——4 位寄存器就是四段这样的 always;第二段是 q+1 被拆成的异或加进位链;第三段的 q==15 比较器被化成一棵或非树再取反,_03_ 为高即表示四位全为 1。信号名都变成了 _00_、_07_ 这类编号,因为门级网表里已经没有「计数」这个语义,只有连接关系。
要强调:这份网表里找不到任何标准单元名,面积报告也会是 0——通用门不带工艺信息,这不是故障,原因与判据见 Yosys stat area 0 速查。
四、常见错与检查点
第一次手写这条流水线,高频卡点集中在四处,都能用 stat 自检:
| 现象 | 先查什么 | 纠正 |
|---|---|---|
跑完 techmap 报还有 process 未处理,或网表里行为块没消失 | stat 里 Number of processes 是否为 0 | 在 techmap 前补 proc,正常流程里 proc 之后该行永远是 0 |
| 没报 ERROR,但综合结果里整个模块几乎是空的 | hierarchy -top 给的名字是否拼写正确、是否与模块名一致 | 显式写全顶层名,机理见 synth 未指定 top 模块 与 Module is not part of the design |
| 门数比预期多一大截,线网位元数上千 | 是不是先 techmap 后 opt,或 techmap 后没做清理 | 调整顺序为 proc → opt → techmap → opt_clean |
write_verilog 产物里仍有 $add 这类美元单元 | techmap 是否真的执行、自定义 -map 模板是否自引用 | 确认日志出现 Executing TECHMAP pass;模板问题见 techmap 递归映射循环 |
一个贯穿性的检查习惯:每跑一条命令就看一次 stat,盯住 processes、cells 总数和单元种类三个量。电路形态的每次变化都会立刻反映在数字上,比通读命令文档更快定位问题。
五、真实跑一遍:stat 数字一路怎么变
用前面的脚本在 Yosys 0.33 上执行 make run(容器内即 yosys -s run.ys)。读入并定顶层后的第一份统计如下:
这份 stat 里最关键的是 Number of processes: 1——always 块原封不动地挂在设计上;4 个单元也都是「带语义的粗算子」:$add 是加一、$eq 是比较、$and 是进位门、$logic_not 是复位取反。此时设计离门级网表还很远。
proc 执行时日志会逐子过程显形:PROC_ARST 打出 Found async reset \rst_n,PROC_DFF 打出 created $adff cell ... positive edge clock and negative level reset——异步复位就是在这一步被识别并选对触发器型号的。随后 opt 干了两件漂亮事:OPT_DFF 把外层使能多路器折进触发器,单元升级为 $adffe(带使能端),OPT_CLEAN 顺手删掉 2 个无用单元和 5 根悬空线。最后的展开与清理由同一次运行的末尾两段 stat 给出:
五个阶段的数字汇总如下,全部逐行摘自同一份日志:
| 阶段 | processes | cells | wires / bits | 主要单元 |
|---|---|---|---|---|
| read + hierarchy | 1 | 4 | 10 / 19 | $add $and $eq $logic_not |
| proc 后 | 0 | 6 | 12 / 24 | 新增 $adff $mux |
| opt 后 | 0 | 4 | 7 / 13 | $adffe 接管使能,删 2 单元 5 线 |
| techmap 后 | 0 | 45 | 48 / 1216 | AND 11、XOR 13、OR 8、NOT 5、MUX 4、DFF 4 |
| opt_clean 后 | 0 | 38 | 18 / 43 | AND 7、XOR 12、OR 6、NOT 5、MUX 4、DFF 4 |
读这张表有两个反差点值得停下来看。第一,proc 之后单元反而从 4 变 6:展开 always 会先制造多路器和独立触发器,电路在化简前是「变胖」的,这是正常现象,别看到数字上涨就以为写错了。第二,techmap 后线网位元数冲到 1216:加法器模板内部有一条 1024 位的临时总线(网表文件里能看到 wire [1023:0]),展开完大部分输入悬空;紧跟的 opt_clean 删掉 7 个无用单元和 30 根线,最终稳定在 38 个通用门、43 个位元——这就是 4 位计数器在与工艺无关层面上的真实规模。复现时建议故意删掉 proc 或把 techmap 提到 opt 前面跑一次,再对照这张表看数字怎么乱掉,流程顺序就再也不会记错。
实验环境
- 机器/系统:macOS(Darwin 25.5,Apple Silicon),综合在 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 -s run.ys(等价于make run),随后yosys -V核对版本;五个阶段的 stat 全文在同一条运行日志中
代码获取
完整 RTL、Yosys 脚本与 Makefile 已同步到公开仓库:geek-doc.cn/experiments/flow-yosys-first-synth,克隆后进入该目录执行 make run 即可复现本文五段 stat 与门级网表,make clean 删除生成的 netlist.v;本机原生装有 Yosys 时可用 make run YOSYS="yosys" 直接运行。
这一篇在知识体系里的位置
- 想先理解「翻译 → 优化 → 工艺映射」为什么是这三步:读 综合是什么;
- 想知道通用门网表之后怎么落到真实芯片:顺着 EDA 知识目录 里的工艺库、STA、布局布线各篇往下读;
- 跑流程时文件读不进来:yosys Can't open input file;顶层选不出或选错:synth 未指定 top 模块、Module is not part of the design;
- stat 门数有了但面积是 0:Yosys stat area 0 速查,那是下一道「加 liberty 跑 abc」工序要解决的问题。
总结
Yosys 第一次综合完整流程就是六条命令一条线:read_verilog 把 RTL 读成 RTLIL,hierarchy -top 定住顶层,proc 把 always 展开成多路器与带异步复位的触发器(processes 归零),opt 做常量折叠与死逻辑消除并把使能折进 $adffe,techmap 把 $add/$eq 这些粗粒度算子下推成 AND/XOR/DFF 通用门原语,最后 stat 看数字、write_verilog 交付网表;顺序上 proc 必先于 opt、opt 必先于 techmap,techmap 后还要补一次 opt_clean 清掉展开模板留下的悬空线网——本文的 4 位计数器真实经历了 4 → 6 → 4 → 45 → 38 个单元的变化,最终得到一份不含任何工艺信息的通用门网表,下一步读 liberty 跑 abc 才是标准单元级的工艺映射。