yosys + verilator 一日上手实录:一个七段译码器走完综合、lint、仿真、波形四步
更新时间:2026-10-25。本文是「EDA 实验室 · 仿真」入门层的一篇实验文。同站的 yosys synth 综合步骤详解 逐 pass 拆解了综合流程,verilator 二进制仿真 讲了 verilator 的 C++ 编译仿真路线——两篇都是「单工具深挖」;本篇换一个角度:把 yosys、verilator、iverilog 串成一条最小流水线,同一个设计一件接一件地过,让读者在一天之内亲眼看到「一段 Verilog 从源码走到网表统计、静态检查、功能仿真、波形文件」的完整旅程。所有数字来自文末可下载的真实日志。
本文要回答的问题
- 开源前端的最小流水线是哪几步,每步各自回答什么问题?
- yosys
stat报告里的 66 个单元、4 个 DFF 是怎么从几行 RTL 变出来的? - verilator
--lint-only与 iverilog 仿真是什么关系,能不能互相替代? - VCD 波形文件里到底存了什么,为什么 32 拍仿真只有 2889 字节?
先给结论卡,四个数字全部来自后文真实运行日志:
读这张卡抓一条主线:同一份 RTL 经过四个工具,每个工具交出一种形态的「证据」——综合给结构账(66 个单元)、lint 给静态清白(0 告警)、仿真给功能账(32 拍零错误)、波形给可逐拍复核的原始记录。四步各有分工,缺一不可。
一、流水线总览:每步回答一个问题
本次实验的 DUT 是一个七段译码演示模块 seg7_demo:4 位计数器每拍加一,case 表把计数值译成七段数码管的段码输出。选它是因为一个模块同时含时序逻辑(计数器)与组合逻辑(译码表),足够让每个工具都有活干。四步流水线的分工用一张图摆清楚:
读这张图抓颜色分工:蓝色是输入(同一份 RTL 喂给三条支路),紫色是结构变换(综合把行为描述变成门级单元),绿色是静态判定(不改设计、只挑毛病),橙色是动态执行(真把时间推进起来),灰色是产物归档。三条支路共用一份 RTL 是刻意的安排——结构、静态、功能三种视角回答的是完全不同的问题,任何一个绿了都不代表另外俩不用跑。
一个自然的问题是顺序能不能乱:工程上 lint 其实最该先跑(成本最低、抓低级笔误最快),本篇把 yosys 放第一步只是因为演示动线从「看得见的结构变化」讲起更直观。真实项目里推荐 lint → 仿真 → 综合的顺序,问题按发现成本从低到高暴露。
四步各自的输入输出与典型拦截的问题,用一张表汇总,复现时可以直接当 checklist 用:
| 步骤 | 工具与命令 | 产出物 | 能拦住的问题 |
|---|---|---|---|
| 1 综合 | yosys -p '...; synth; stat' | 单元统计、网表(可加 write_verilog) | 推断出 latch、多驱动、不可综合写法 |
| 2 静态检查 | verilator --lint-only | 告警清单(exit 码) | 位宽不匹配、case 缺分支、未用信号 |
| 3 仿真 | iverilog + vvp + TB | PASS/FAIL、比对明细 | 功能错误、边界条件、复位行为 |
| 4 波形 | $dumpvars 自动落盘 | VCD 文件 | 逐拍复盘任意窗口的信号行为 |
注意这张表里每一步拦的问题没有交集——这就是为什么「lint 绿了所以功能一定对」是错觉:lint 只管写法规矩,仿真才管行为正确;反过来仿真全 PASS 也不保证综合不出 latch。四个绿灯要分别拿到,没有捷径。
二、STEP 1:yosys 综合,66 个单元的来历
第一步命令是 yosys -p 'read_verilog seg7_demo.v; synth -top seg7_demo; stat'——读入 RTL、跑 synth 通用综合脚本、打印统计。synth 一个命令内部其实串了二十多个 pass(proc 展开、优化、FSM 提取、techmap、ABC 工艺映射),完整过程在 yosys synth 综合步骤详解 里逐条讲过,本篇只盯最终的 stat 总账。真实执行实录摘关键段:
读这份实录抓三行。Number of cells: 66 是综合后的单元总数;其下 $_DFF_PN0_ 4 是 4 个带异步低有效复位的 D 触发器——正好对应 RTL 里 4 位计数器 cnt(PN0 即 posedge 时钟、negedge 复位,与 posedge clk or negedge rst_n 逐字对应);剩下 62 个全部是 $_ANDNOT_/$_AOI3_/$_OAI4_ 这类组合门,来自译码 case 表被 ABC 映射成的与或非网络。RTL 里那张 16 行的 case 表,综合后一张真值表门电路——7 位输出每一位是 4 位输入的组合函数,66 个单元就是它的体积。
这段日志还有两个值得记住的旁证:一是综合中段 WREDUCE pass 报告「Removed top 3 bits (of 4) from port B」,说明工具发现加法另一端是常量 1、把位宽砍掉了——综合器做位宽收缩是常态,不必惊慌;二是结尾 CHECK pass ... found and reported 0 problems,yosys 自检干净。想逐字段读懂这类报告,对照 yosys stat 报告怎么读。
统计表下方还有一行容易被翻过去的性能注脚:CPU: user 0.08s system 0.00s, MEM: 17.96 MB total 与 Time spent: 24% 2x techmap, 17% 14x opt_clean——综合 66 个单元只花了 0.08 秒 CPU、峰值内存不到 18 MB,时间大头在 techmap(工艺映射)与 opt_clean(清理无用单元)两类 pass。小设计里这些数字没有实际意义,但它给出了一个参照基线:哪天你的设计综合跑了十分钟,翻回这行就知道热点该往 techmap 还是 FSM 提取去查——synth 之后追加 stat 与 tee 日志归档的习惯,就是从这种小实验开始养成的。
三、STEP 2/3:lint 零告警与 32 拍功能仿真
第二步 verilator --lint-only seg7_demo.v 只做静态检查、不产仿真二进制,日志干净到只有一行:lint exit: 0 (0 = clean)。这个设计的位宽、case 覆盖、复位写法全部规矩,所以零告警——真实项目里这一步最常见的收获是 WIDTH(位宽不匹配)与 CASEINCOMPLETE(case 没写 default)两类告警,遇到时的处理见 yosys 位宽不匹配告警 的同类思路。lint 的价值在「编译期挑毛病」,它和仿真的关系是互补:lint 抓得出「写法可疑」,抓不出「行为错误」。
零告警的项目里还有一个值得养成的动作:显式开启 -Wall 再跑一遍。默认 lint 只报高置信问题,-Wall 会额外报告未用信号(UNUSED)、未声明位宽(DECLFILENAME)这类「不报也能跑、但迟早坑人」的提示。本篇设计在默认档位下 exit 0,读者复现时可以自己加 -Wall 对比输出差异——对七段译码器这种干净设计应当仍然零告警,这本身就是「代码规范」的一种验证。
第三步把功能问题交给仿真:iverilog 编译 DUT 加测试平台 tb_seg7.v,vvp 运行。测试平台在内部维护一张参考译码表,计数器滚动两轮共检查 32 拍,每拍比对 DUT 输出的段码与参考表。从编译到判定的关键段原样摘录:
+ iverilog -g2012 -o sim.vvp /tmp/evidence-src/exp-yosys-verilator-quickstart/seg7_demo.v /tmp/evidence-src/exp-yosys-verilator-quickstart/tb_seg7.v
+ vvp sim.vvp
+ tee step3_sim.log
VCD info: dumpfile seg7_demo.vcd opened for output.
checked 32 clocks, errors=0
RESULT: PASS
+ grep -q 'RESULT: PASS' step3_sim.log这段摘录里 checked 32 clocks, errors=0 是功能总账:4 位计数器从 0 滚到 F 整整两轮,每一拍段码输出都与参考表逐项相等。VCD info 那一行说明波形捕获从仿真一开始就挂上了——不是事后补录,而是 $dumpvars 在时刻 0 就打开文件,这保证了波形里包含复位拉低的初始段,逐拍复核时能看到完整的冷启动过程。
为什么要滚动两轮而不是一轮?一轮 16 拍只能证明「每个码值译对一次」,第二轮验证了计数器从 F 回绕到 0 之后译码仍然正确——回绕沿是计数器最容易藏 off-by-one 错误的地方。写测试平台时「让激励越过边界条件至少一次」是花最小成本换最大覆盖的习惯。
这里顺带说清一个常被问起的关系:verilator 也能仿真,为什么本篇仿真用 iverilog? verilator 是把 Verilog 编译成 C++ 再编成原生二进制,长回归速度极快,但它要求测试平台也用 C++/SystemC 写(或走 verilator 5 的时序支持),且只支持可综合子集附近的行为语法;iverilog 是解释型事件仿真器,原生支持 # 延时、$dumpvars 这类行为级 TB 写法。对本篇这种「行为级 TB + 小规模 DUT」的场景,iverilog 零额外代码即可跑;换成万拍级长回归再切 verilator 二进制路线(写法见 verilator 二进制仿真)。而第二步的 --lint-only 只借 verilator 的前端做静态检查、不产仿真,与这个选型不冲突。
四、STEP 4:波形落盘,2889 字节里有什么
第四步没有新命令,只是清点仿真产物:seg7_demo.vcd 共 2889 字节。VCD(Value Change Dump)是纯文本格式,开头一段头部原样摘录:
$date
Sun Oct 25 02:09:15 2026
$end
$version
Icarus Verilog
$end
$timescale
1ps
$end
$scope module tb_seg7 $end
$var wire 7 ! seg [6:0] $end
$var reg 1 " clk $end
$var reg 1 # rst_n $end读这段头部抓三件事:$timescale 1ps 声明时间精度;$var 行把每个信号映射成一字符短码(seg 是 !、clk 是 "),正文里每行值变事件就记成「时刻 + 短码 + 新值」——这就是它小得下来的原因:只记变化、不记快照,32 拍、5 个信号,全文件不到 3 KB。头部往下还有 $scope module dut $end 一层作用域嵌套,dut.cnt 这类 TB 内部探针信号也在被记录之列——这正是 $dumpvars(0, tb_seg7) 里第一个参数 0 的含义:递归记录指定层次以下所有信号。想打开看,GTKWave 直接加载即可;看重图形界面之外想批量处理,VCD 纯文本本质也意味着 grep/awk 都能当分析工具用——比如想确认复位释放的确切时刻,搜 # 开头的时刻行配 rst_n 的短码即可。
波形之外,本步还有一个容易被忽略的工程价值:VCD 文件是可存档的回归产物。把每次回归的 VCD 按提交号归档,日后某天发现一个 corner case 出错,可以翻回历史波形对比「从哪次提交开始行为变了」,比在 git 里二分重跑仿真快得多。当然代价是体积——大设计的 VCD 轻松上 GB,工程上常用 FSDB(商业格式)或只抓关键窗口来缓解,那是后话。
五、一日流水线的经验与复现建议
四个步骤串起来,总墙钟不到一秒(yosys 自述 CPU user 0.08s,仿真 32 拍瞬时完成),但它搭出了开源前端工作流的完整骨架:改 RTL → lint 抓笔误 → 仿真抓行为 → 综合看结构 → 波形复盘。这条链上的工具全部开源、全部脚本化,意味着它可以原样搬进 CI——每次提交自动跑这四步,红灯即回滚,这就是最小可用的「硬件持续集成」。
动手复现建议做两个修改实验:一是把译码表里某一行的段码故意改错一位,重跑看测试平台打出 DECODE ERROR cnt=? seg=? expect=?——亲眼见一次 FAIL,就知道 32 拍逐项比对不是摆设;二是把 stat 换成 stat -tech cmos 或改跑 synth -flatten,对比单元数变化,建立「优化开关 ↔ 结构账」的直觉。往深走一步,同目录的 iverilog 第一次仿真 与 verilator 二进制仿真 分别把第三、二步的单个工具展开讲透。
这一篇在知识体系里的位置
- 综合深挖:yosys synth 综合步骤详解 与 yosys stat 报告怎么读(本篇 STEP 1 的逐 pass 展开);
- 仿真深挖:iverilog 第一次仿真、verilator 二进制仿真(本篇 STEP 2/3 的单工具版);
- 排错对照:yosys 位宽不匹配告警(lint/综合阶段高频告警的处理范式)。
一句话总结
开源前端一日流水线就是「yosys 综合看结构账、verilator lint 看静态清白、iverilog 仿真看功能总账、VCD 波形留逐拍证据」——本篇用同一个七段译码器实测:综合 66 单元(4 DFF + 62 组合门)、lint 零告警、32 拍两轮滚动零错误、2889 字节波形落盘,全程墙钟不到一秒,四步全脚本化即可搬进 CI。
实验环境
- 机器/系统:云主机 S6.4XLARGE32 16C32G / Ubuntu 22.04(eda-golden-v1 镜像)
- 工具版本:Yosys 0.9(日志
Yosys 0.9 (git sha1 1979e0b))/ Icarus Verilog(-g2012,VCD 头部$version Icarus Verilog)/ Verilator(eda-golden-v1 镜像自带,--lint-onlyexit 0) - 日期:2026-10-25
- 完整命令:
bash run.sh(内部依次执行:yosys -p 'read_verilog seg7_demo.v; synth -top seg7_demo; stat';verilator --lint-only seg7_demo.v;iverilog -g2012 -o sim.vvp seg7_demo.v tb_seg7.v后vvp sim.vvp;最后清点并拷贝seg7_demo.vcd,逐步 grep 判定 PASS)
代码获取
完整 DUT、测试平台与运行脚本已同步到公开仓库:geek-doc.cn/experiments/exp-yosys-verilator-quickstart,克隆后进入该目录执行 bash run.sh 即可复现本文全部四步结果(需预装 yosys、verilator、iverilog)。