4. 网表层次化分析:模块层次划分、跨层次信号追踪、层次化网表重构
网表这东西,说白了就是一坨门级连线。但真正干过逆向的人都知道,原始网表就像一团乱麻——成千上万个实例平铺在一起,信号名全是自动生成的_n1234。你盯着看三天,眼睛花了也看不出门道。
我个人习惯,拿到网表第一件事不是看细节,而是先做层次化分析。这就好比你要拆解一台发动机,总得先知道哪些零件属于气缸、哪些属于油路系统吧?
4.1 模块层次划分:从平铺到结构化
现代数字芯片设计,几乎都是层次化设计。顶层模块调用子模块,子模块再调用更小的单元。但综合后的网表,有时候会把层次拍平,有时候又会保留部分层次。
为什么要做层次划分?
- 降低复杂度:把几百万个门分成几十个功能块,每个块单独分析
- 定位关键路径:跨模块的路径往往是最难搞的
- 复用分析结果:同一个子模块出现多次,分析一次就够了
我在项目中遇到过一种情况:某款芯片的网表被拍平了,所有逻辑都在一个层级里。我花了整整两天才从几万个实例中找出一个计数器模块。后来我写了个脚本,根据信号命名规律和连接关系自动聚类,十分钟就搞定了。
层次划分的常用方法:
- 基于实例名称前缀:很多工具会保留实例的层次前缀,比如
u_core/u_alu/... - 基于信号扇入扇出:内部连接密集、外部连接稀疏的节点群,大概率是一个模块
- 基于时钟域:同一个时钟域的逻辑往往属于同一个功能块
- 基于功能单元库:比如标准单元库中的DFF、MUX等,可以辅助判断
4.2 跨层次信号追踪:顺着线找源头
层次划分完了,接下来就是追踪信号。这活儿看着简单,做起来真头疼。尤其是跨了三四层模块的信号,名字可能被重命名、被截断、甚至被优化掉。
信号追踪的典型场景:
- 一个顶层输入,经过三级模块后到达某个触发器的D端
- 一个内部信号,同时驱动多个子模块的输入
- 一个反馈信号,从底层模块绕回顶层再回到底层
嗯,这里要注意:跨层次信号追踪最怕的是信号名不一致。比如顶层叫data_in,到了子模块变成了din,再下一层变成了d[31:0]。你光靠名字匹配是找不到的。
我的追踪技巧:
- 用EDA工具的
trace命令,指定起点和终点,自动展开路径 - 写脚本遍历网表,建立信号名到实例引脚的映射表
- 对于总线信号,先追踪高位宽信号,再处理位宽转换点
我曾经踩过一个坑:追踪一个复位信号,发现它在某层被一个与门挡住了。我以为是设计问题,折腾了半天才发现,那个与门其实是测试模式下的隔离逻辑,正常工作模式下是直通的。所以追踪信号时,一定要留意测试逻辑和DFT插入的单元。
4.3 层次化网表重构:把平铺的网表变回层次结构
这是最核心的一步。很多时候我们拿到的网表是拍平的,所有实例都在一个层级里。我们需要把它重构回层次化的结构,才能进行后续的仿真验证。
重构的基本思路:
- 根据层次划分的结果,确定每个子模块的边界
- 提取子模块内部的实例和连接
- 生成子模块的端口列表(输入、输出、双向)
- 在顶层用子模块实例替换原来的平铺逻辑
说白了,就是把一团乱麻重新编成一根根绳子,每根绳子对应一个功能模块。
下面是我常用的一个重构流程示意图:
你想想看,重构后的网表有什么好处?最直接的好处就是:你可以对每个子模块单独做仿真验证。比如你怀疑某个加法器有问题,直接提取它的子网表,写个简单的testbench跑一下,比在整个芯片级别仿真快得多。
重构时的常见陷阱:
- 端口顺序:有些网表不按端口名匹配,而是按端口顺序。重构时一定要确认顺序是否正确
- 双向端口:inout类型的端口处理起来比较麻烦,需要额外注意驱动冲突
- 电源和地:很多网表把VDD/VSS作为普通信号处理,重构时要单独处理
4.4 实战案例:一个简单的层次化重构
假设我们有一个拍平的网表,里面包含两个子模块:一个计数器和一个译码器。平铺后的网表大概长这样:
// 平铺网表片段
DFF count_reg0 (.clk(clk), .d(n1), .q(count[0]));
DFF count_reg1 (.clk(clk), .d(n2), .q(count[1]));
AND2 u1 (.a(count[0]), .b(count[1]), .y(n3));
// ... 更多逻辑
经过层次划分,我们发现count_reg0、count_reg1、u1等实例属于计数器模块。于是我们重构出:
// 重构后的计数器子模块
module counter (
input clk,
output [1:0] count
);
wire n1, n2, n3;
DFF count_reg0 (.clk(clk), .d(n1), .q(count[0]));
DFF count_reg1 (.clk(clk), .d(n2), .q(count[1]));
AND2 u1 (.a(count[0]), .b(count[1]), .y(n3));
// ... 其他内部逻辑
endmodule
然后在顶层用实例化代替原来的平铺逻辑:
// 重构后的顶层
counter u_counter (.clk(clk), .count(count));
decoder u_decoder (.addr(addr), .sel(sel));
你看,这样一重构,整个网表的结构就清晰多了。哪个模块负责什么功能,一目了然。
自动化重构工具推荐:
- Python + pyverilog:解析Verilog网表,做层次化处理
- Tcl脚本:在Synopsys/Cadence工具内直接操作网表
- Yosys:开源综合工具,支持网表层次化操作
我个人习惯用Python写脚本做重构。因为Python处理字符串和数据结构非常灵活,而且可以方便地集成到自动化流程中。不过要注意,网表文件通常很大,几GB是常事,所以脚本的性能优化很重要。
好了,层次化分析这块就聊到这儿。记住一个原则:先分层,再追踪,后重构。这三步走下来,再复杂的网表也能被你拆解得明明白白。