2、网表格式解析:EDIF、Verilog网表、SPICE网表的格式对比与解析工具
说实话,做硬件逆向这些年,我打交道最多的就是各种网表文件。
你拿到一个芯片的版图,提取出门级电路,最后落到手里的,就是一堆网表。但网表也有好几种「方言」——EDIF、Verilog网表、SPICE网表。不懂它们的脾气,解析起来能让你怀疑人生。
今天我就带你把这三种格式掰开揉碎,看看它们到底长什么样,以及怎么用工具搞定它们。
2.1 三种网表格式的「出身」与「性格」
先说说它们各自的来历。我个人习惯把网表分成两类:一类是给EDA工具看的,一类是给仿真器看的。EDIF和Verilog网表属于前者,SPICE网表属于后者。
| 特性 | EDIF | Verilog网表 | SPICE网表 |
|---|---|---|---|
| 全称 | Electronic Design Interchange Format | Verilog HDL 的门级描述 | Simulation Program with Integrated Circuit Emphasis |
| 主要用途 | EDA工具间数据交换 | 数字综合后的门级网表 | 模拟/混合信号电路仿真 |
| 抽象层级 | 逻辑门级 | 逻辑门级 + 寄存器传输级 | 晶体管级(电阻、电容、MOS管) |
| 可读性 | 差(括号嵌套地狱) | 中等(类C语法) | 较好(文本行式) |
| 解析难度 | 高 | 中 | 低 |
嗯,这里要注意:EDIF是上世纪80年代的产物,设计初衷是为了让不同厂商的EDA工具能互相读懂对方的数据。结果呢?它自己成了最难看懂的格式。我在项目中遇到过好几次,拿到客户的EDIF文件,打开一看,满屏的括号,头皮发麻。
2.2 EDIF格式解析:括号里的「天书」
EDIF的核心语法就一个字:括号。左括号开头,右括号结尾,层层嵌套。说白了,它就是Lisp语言的亲戚。
一个典型的EDIF网表长这样:
(edif hello-world
(edifVersion 2 0 0)
(edifLevel 0)
(keywordMap (keywordLevel 0))
(status
(written
(timeStamp 2024 01 15 10 30 00)
(program "Synopsys")
(version "2023.12")
)
)
(library my_lib
(edifLevel 0)
(technology (numberDefinition (scale 1 (unit distance) (e 6))))
(cell AND2
(cellType GENERIC)
(view netlist
(viewType NETLIST)
(interface
(port A (direction INPUT))
(port B (direction INPUT))
(port Z (direction OUTPUT))
)
(contents
(instance I1 (cellRef AND2 (libraryRef my_lib)))
(net N1
(joined
(portRef A)
(portRef I1 A)
)
)
)
)
)
)
(design hello-world
(cellRef hello-world (libraryRef my_lib))
)
)
看到没?一个简单的AND2门,写了快30行。我刚开始做逆向时,对着这种格式硬啃,眼睛都快瞎了。
pyedif库就挺好用,能帮你把嵌套结构转成字典。
2.3 Verilog网表解析:相对「友好」的门级描述
Verilog网表就好多了。它本质上是把RTL代码综合后的门级实现,用Verilog语法写出来。你想想看,如果你会写Verilog,那读网表基本没障碍。
一个典型的Verilog网表片段:
module top (clk, rst, data_in, data_out);
input clk, rst;
input [7:0] data_in;
output [7:0] data_out;
wire [7:0] w1, w2;
wire n1, n2;
// 实例化一个与门
AND2X1 U1 (.A(data_in[0]), .B(data_in[1]), .Z(w1[0]));
// 实例化一个D触发器
DFFX1 U2 (.D(w1[0]), .CK(clk), .Q(data_out[0]));
// 组合逻辑
assign n1 = ~(data_in[2] & data_in[3]);
assign n2 = n1 | data_in[4];
endmodule
这里有个坑,我曾经踩过:综合工具生成的Verilog网表,单元名称是跟工艺库绑定的。比如上面的AND2X1,不同工艺库可能叫AND2、AN2甚至A2。解析时一定要建立单元映射表。
2.4 SPICE网表解析:晶体管级的「硬核」描述
SPICE网表是三种格式里最「底层」的。它直接描述晶体管、电阻、电容这些物理器件。做硬件安全分析时,如果你要逆向的是模拟电路或者混合信号芯片,SPICE网表是绕不开的。
一个简单的CMOS反相器SPICE网表:
* CMOS Inverter Netlist
.SUBCKT inv A VDD VSS Z
M1 Z A VDD VDD PMOS W=2u L=0.18u
M2 Z A VSS VSS NMOS W=1u L=0.18u
.ENDS inv
* Top level
.SUBCKT top VDD VSS A Z
X1 A VDD VSS Z inv
.ENDS top
* Simulation commands
VDD VDD 0 DC 1.8V
VIN A 0 PULSE(0 1.8 0 1n 1n 10n 20n)
.TRAN 0.1n 100n
.END
你看,SPICE网表的结构很直白:.SUBCKT定义子电路,M定义MOS管,W和L是宽长比。解析起来其实不难,难的是后续的电路识别——你要从几万个晶体管里找出哪些是反相器、哪些是与非门。
关键点: SPICE网表的解析核心是「器件-节点」关系图。我建议用图数据库(比如Neo4j)来存储解析结果,这样后续做子图匹配会快很多。
2.5 解析工具对比:选对工具,事半功倍
工具选得好,下班回家早。我这些年用过不少解析工具,挑几个有代表性的说说:
| 工具名称 | 支持格式 | 语言 | 优点 | 缺点 |
|---|---|---|---|---|
| Yosys | Verilog网表 | C++ / Python | 开源、功能强、支持综合与优化 | 对EDIF支持有限 |
| pyedif | EDIF | Python | 轻量、易用、适合快速解析 | 不支持写EDIF |
| ngspice | SPICE | C | 开源仿真器、解析准确 | 只做仿真,不做逆向分析 |
| Verilator | Verilog网表 | C++ / Perl | 高性能、支持Lint检查 | 学习曲线陡峭 |
| 自定义脚本 | 全格式 | Python / Perl | 灵活、可定制 | 开发周期长 |
我个人习惯是:Verilog网表用Yosys,EDIF用pyedif,SPICE网表直接写Python脚本解析。为什么?因为SPICE格式虽然简单,但各家EDA工具生成的SPICE网表在注释、缩进、参数顺序上都有细微差别,通用工具反而容易出错。
2.6 三种格式的解析流程对比
下面这张SVG图,是我自己总结的三种网表解析流程对比。你看一眼就能明白,它们的解析路径其实大同小异,只是中间的处理细节不同。
从这张图你能看出来,EDIF的解析最「绕」,因为它本质上是树形结构,你得先把括号匹配好,才能提取出有效信息。Verilog网表相对直接,但要注意模块实例化的层次展开。SPICE网表最简单,逐行读就行,但后续的图构建反而最复杂。
2.7 实战建议:从零开始搭一个解析器
如果你真想学会网表解析,我建议你动手写一个最小化的解析器。别怕,不用写几千行,几百行就够了。
以SPICE网表为例,核心代码其实就三步:
# 伪代码:SPICE网表最小解析器
def parse_spice(netlist_file):
devices = []
subckts = {}
current_subckt = None
for line in netlist_file:
line = line.strip()
if not line or line.startswith('*'):
continue # 跳过空行和注释
if line.startswith('.SUBCKT'):
# 开始一个子电路定义
parts = line.split()
current_subckt = parts[1]
subckts[current_subckt] = []
elif line.startswith('.ENDS'):
current_subckt = None
elif line.startswith('M'):
# MOS管: M name drain gate source bulk model W= L=
parts = line.split()
device = {
'type': 'MOSFET',
'name': parts[0],
'drain': parts[1],
'gate': parts[2],
'source': parts[3],
'bulk': parts[4],
'model': parts[5],
'params': parts[6:]
}
if current_subckt:
subckts[current_subckt].append(device)
else:
devices.append(device)
# ... 其他器件类型类似处理
return devices, subckts
你看,核心逻辑就这么几行。剩下的就是处理各种边界情况——比如续行符、参数顺序变化、注释格式不统一等等。这些坑,我当年一个一个踩过来的。
好了,网表格式解析这块,核心内容就这些。记住一句话:格式是死的,工具是活的。多动手写脚本,多积累经验,你也能成为网表解析的老手。
公众号:蓝海资料掘金营,微信deep3321