Yosys stat 报告怎么读:逐字段看懂综合后的单元统计
更新时间:2026-11-03。综合是什么 讲过 RTL 最终要变成门级网表,但综合器到底把你的
always变成了什么?在开源综合器 Yosys 里,回答这个问题的命令就是stat。本篇不排错,只讲正常流程:stat报告每一行是什么意思,以及怎样用计数器、FIFO、FSM 三个小设计的真实统计,判断综合结果是不是你想要的电路。
本文要回答的问题
- Yosys stat 报告怎么读,每一行计数的对象分别是什么?
$dff、$mux与$_DFFE_PN0P_这类单元名有什么区别,后缀怎么解?Chip area一行为什么经常没有,出现了又代表什么口径?- 怎样从 cell 构成看出触发器、锁存器和存储器有没有按预期被推断?
先给一张字段分区卡,读报告时按四个区逐块扫即可:
这张卡的用法是:线网区看规模量级,存储与进程区看抽象对象有没有被展开,单元计数区看电路构成,面积区只在你显式给了工艺口径时才有数。下面按流程顺序展开。
一、这一步在流程里的位置
stat 是观察命令,不改变设计:它随时可以插到任何 pass 后面,统计当前网表里有什么。典型的观察点有三个——proc; opt 之后看 RTL 级抽象单元,完整 synth 之后看门级原语,dfflibmap/abc -liberty 之后看工艺单元和面积。
读这张图抓住一条主线:蓝色的三个 stat 是同一份设计在不同阶段的「体检表」,单元名字会随着流程下移而越来越具体。很多新手只在最后敲一次 stat,看到陌生的门级名字就慌;其实在中间多插一次 stat,就能对照出每个 pass 干了什么。
二、命令逐段解读
最小命令只有一条,分号隔开的三个 pass 各自有分工:
yosys -p "read_verilog counter.v; synth -top counter; stat"| 片段 | 作用 | 不写会怎样 |
|---|---|---|
read_verilog counter.v | 读入 RTL 并生成内部表示,多文件就写多个 | 没有设计对象,后续全部空转 |
synth -top counter | 跑完整通用综合流程:展平、优化、FSM/存储器处理、门级映射 | 只剩原始 RTL,单元名停在 $add 一类抽象算子 |
stat | 打印当前设计统计;synth 结尾本来就会自动跑一次 | 看不到报告;显式再写一次便于和后面的选项组合 |
stat 自身的常用选项:-top <模块> 在多模块层次里指定统计根;-width 在单元类型后标注字宽,比如把 $add 显示成 $add_8;-json 输出机器可读的统计 JSON,适合回归脚本做资源对比;-tech cmos 给一个通用 CMOS 口径的晶体管数量估算;-liberty <库文件> 按给定单元库的面积权重计算 Chip area。注意后两者都不接触任何代工厂数据。
三、输入输出:stat 报告逐字段读
以下是 8 位使能计数器经完整 synth 后的真实报告,先整体看一遍:
Number of wires: 18与Number of wire bits: 39:前者数线网对象(一根总线算一个),后者按位展开计数(8 位总线算 8 位)。两者拉开差距说明有向量信号。public wires: 5 / public wire bits: 12:公开线网主要是模块端口,本例 5 个端口、共 12 位(8 位q加 4 个 1 位口)。memories: 0 / memory bits: 0:设计里没有以存储器对象保留的数组。processes: 0:所有always块都已被proc展开成显式单元。如果这里不是 0,说明你在proc之前就统计了,报告还不代表电路。Number of cells: 28及其逐行计数:这是报告的核心,下面专门讲。
stat 本身不逐线打印 readers/writers(谁驱动、谁使用某根线),要查线的读写关系,记住两个动作:一是紧接在综合流程里的 check pass,它会明确报出 wire has no driver(无驱动)和多驱动;二是用 select 系列表达式沿端口追连接。正常设计里 check 应给出 Found and reported 0 problems。
单元计数要分两级读。proc; opt 后的 RTL 级用单美元符号的算子名:计数器是 $add 1 / $adffe 1 / $and 1 / $eq 1,分别对应加一、带使能累加寄存器、进位与、比较。完整 synth 后变成双下划线门级原语,计数器为 8 个 $_DFFE_PN0P_ 加 20 个门。触发器后缀可用四个小设计实测的命名规律来解:$_DFF_P_ 是普通上升沿触发器;$_DFF_PN0_ 多出来的 N0 表示低有效异步复位、复位值 0;$_DFFE_PP_ 末尾的 P 表示高有效使能;$_DFFE_PN0P_ 即「上升沿、低有效异步复位到 0、高有效使能」。所以那 8 个单元正好对应 8 位寄存器 q,一个不多一个不少——这是判断综合质量的第一个锚点。
Chip area 默认不存在:门级原语不带面积属性。给口径的方式有两种,stat -tech cmos 打印通用 CMOS 估算,本例为 Estimated number of transistors: 150+;stat -liberty tiny.lib 才打印 Chip area for module '\counter': 76.000000。这里的 76 是教学库里反相器面积 1、触发器面积 5 这类相对权重的加权和,单位是库里的面积单位,不是任何工艺节点的平方微米,更不能拿去估算芯片成本。综合后面积为 0 的报错排查见 stat 面积为 0 专篇。
四、三个设计对照:从 cell 构成判断综合质量
把计数器、FIFO、FSM 放进同一套流程,再各加一个锁存器反例,统计差异立刻显出设计意图是否被正确理解:
| 设计 | 门级关键单元(完整 synth 后) | 读法 |
|---|---|---|
| 8 位计数器 | $_DFFE_PN0P_ ×8,门 ×20 | 8 个状态位全是带使能和异步复位的触发器,符合预期 |
| 三状态 FSM | $_DFFE_PN0P_ ×2,门 ×10 | 状态寄存器只用 2 位即可编码 3 个状态,FSM 已被提取重组 |
| 16x8 FIFO | $_DFFE_PP_ ×128、$_DFFE_PN0P_ ×21,门 ×381 | 128 = 16×8:存储数组被展开成了 128 个触发器 |
| 锁存器反例 | $_DLATCH_P_ ×1 | 电平敏感锁存器,不是触发器 |
FIFO 那行最值得停下来看。前端读入时就给出告警 Warning: Replacing memory \mem with list of registers,原因是读写与同一时钟过程耦合,Yosys 直接把数组按寄存器堆处理,报告里 memories 为 0、多出 128 个无复位使能触发器;另外 21 个复位触发器恰好等于写指针 4 位、读指针 4 位、计数 5 位、读数据 8 位之和。指针和数据寄存器数量与手算一致,说明展开本身是「正确地变贵」,而不是工具出错。这个展开机制的完整排查见 Yosys 数组展开成寄存器。
那存储器怎样才算「被保留」?把写口和同步读口拆到两个独立时钟过程、复位不碰数组(ram16x8.v),在 memory -nomap 之后统计,报告就变成 Number of cells: 4,其中有 1 个 $mem_v2——存储以抽象单元的形式活着。此时 Number of memories 字段显示 0,是因为收集后口径切到了 cell 计数,判断这一阶段要看 $mem_v2 这一行而不是 memories 字段。要注意通用 synth 结尾仍会执行 memory_map,把它展开成通用门;真正落到块 RAM 要靠目标器件流程,这正是 FPGA 综合脚本里存储器统计必须在映射前看的原因。
锁存器反例只有一行 if (en) q = d; 且缺 else,门级结果是 1 个 $_DLATCH_P_。锁存器不是天生错误,但它意味着「en 为低时保持」靠电平透明实现,时序分析和 DFT 都更麻烦;设计触发器时看到任何 $_DLATCH_ 都应当回到 RTL 检查是不是组合逻辑分支没写全,机制与告警见 推断出锁存器怎么办。基础概念见 触发器与锁存器。
五、常见错与检查点
| 现象 | 先查什么 | 纠正 |
|---|---|---|
报告里全是 $add/$mux,看不到门 | 是不是在 synth 之前就 stat 了 | 跑完完整 synth 再看,或明确自己要的就是 RTL 级统计 |
Chip area 行缺失 | 有没有给 -liberty 或 -tech | 默认原语无面积;给库或用 cmos 口径,别把缺行当工具故障 |
| 顶层不是自己想要的模块 | -top 是否指定、文件里有几个未例化模块 | 显式 synth -top <名字>,多顶层时统计会张冠李戴 |
出现 $_DLATCH_ 而你以为写的是时序逻辑 | 组合 always 分支是否完整、有没有时钟沿 | 补全 else 或改成 posedge clk 过程 |
六、真实跑一遍:映射到教学库看面积数字
实验目录里提供了只含功能与面积权重的最小教学库 tiny.lib(BUF/INV/NAND/NOR/AND/OR/XOR/XNOR/MUX 加四种触发器),对计数器执行 dfflibmap -liberty tiny.lib; abc -liberty tiny.lib; stat -liberty tiny.lib,真实结果如下:
单元名从 $_DFFE_PN0P_ 变成库里的 DFFRN,8 个触发器被合法化成 8 个异步复位触发器(使能折进了数据端选择),组合逻辑被 abc 映射为 INV/NAND2/NOR2/XOR2/XNOR2 各 4 个,面积 76 正好是 8×5 + 4×1 + 4×1 + 4×1 + 4×3 + 4×3 = 76 个相对面积单位。另外两个设计同口径的真实结果是:FIFO 共 670 个单元(128 个 DFF、21 个 DFFRN、140 个 MUX2),Chip area 1454.000000;三状态 FSM 共 18 个单元,Chip area 27.000000。这组数字的意义只在同一库内横向比较——比如 FIFO 因数组展开,面积是 FSM 的五十多倍——换一个库,绝对值全部重算。教学库只有十几种单元,产线流程必须替换为 PDK 提供的完整标准单元库。
实验环境
- 机器/系统:macOS(Darwin 25.5,Apple Silicon),实验在 geek-eda:0.3-sta Linux 容器中运行。
- 工具版本:Yosys 0.33 (git sha1 2584903a060)。
- 日期:2026-11-03。
- 完整命令:在实验目录执行
make run(即docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta bash run.sh),脚本依次输出计数器/FIFO/FSM 的 RTL 级与门级统计、锁存器统计、RAM 中间统计、stat -tech cmos晶体管估算,以及三个设计映射tiny.lib后的stat -liberty面积报告。
代码获取
五个 RTL 设计、教学库、Yosys 脚本与一键复现脚本已同步到公开仓库:geek-doc.cn/experiments/flow-yosys-stat-report-reading,克隆后进入该目录执行 make run 即可复现本文引用的全部统计输出。
这一篇在知识体系里的位置
- 想补综合全流程与「为什么同一份 RTL 结果不同」:读 综合是什么;
- 报告里每个状态位、每个指针对应什么电路:读 计数器与移位寄存器、有限状态机入门、FIFO 与仲裁器基础;
- 存储器展开、锁存器推断、面积为 0 这三类异常的专项排查,从 EDA 报错排查目录 进入对应专篇。
总结
Yosys stat 报告怎么读:先分四区扫——线网区看 wires 与 wire bits 的对象数和位数,存储与进程区确认 memories 是否保留、processes 是否归 0,单元计数区对照 RTL 级 $dff/$mux 与门级 $_DFFE_PN0P_ 两级命名,后缀按时钟沿、复位极性与值、使能极性解码;面积区默认空缺,stat -tech cmos 给通用晶体管估算,stat -liberty 的 Chip area 只是库内相对权重而非代工厂面积。用计数器的 8 个触发器、FIFO 展开后的 128 个存储触发器、FSM 的 2 位状态寄存器和锁存器反例四个锚点对照手算位数,就能快速判断综合结果是否符合设计意图。