4、网表结构分析:模块层次、实例化、信号连接、端口映射的识别方法

网表这东西,说白了就是芯片设计的「电路图纸」。你拿到一份网表,就像拿到了一栋大楼的施工蓝图——你得先看懂它的结构,才能知道哪里是承重墙、哪里是水电管线。

我个人习惯,拿到网表第一件事不是看细节,而是先搭框架。就像拼图一样,先把边角拼好,再往里面填。今天我就带你走一遍这个流程。

4.1 模块层次结构——从顶层到底层

一个数字芯片,通常是一个大模块套小模块。顶层模块就是芯片的「外壳」,里面一层层嵌套着各种功能单元。

举个例子,一个简单的 SoC 芯片,顶层可能是 top,里面套着 cpu_corememory_controlleruart 这些子模块。每个子模块又可能继续往下拆。

我在项目中遇到过一份网表,顶层模块下面直接挂了 200 多个子模块,密密麻麻的。我当时就意识到,如果不先把层次理清楚,后面根本没法干活。

识别层次结构的方法:

  • 看模块声明:网表里 module 关键字后面跟的就是模块名。顶层模块通常没有外部端口以外的引用。
  • 看实例化语句:子模块被调用时,会写成 module_name instance_name (...) 的形式。这个 instance_name 就是实例名。
  • 画树状图:我习惯用文本工具把模块名和实例名列出来,画成树状结构。一眼就能看出谁是谁的父模块。

核心要点:模块层次就是芯片的「家族树」。顶层是祖宗,底层是孙子。你只要找到根节点,顺着实例化关系往下捋,整个结构就清晰了。

4.2 实例化——模块的「复制粘贴」

实例化,说白了就是把一个模块「用」起来。就像你写了一个函数,然后在主程序里调用它。网表里每个实例化语句,都代表一个具体的硬件单元。

你想想看,一个芯片里可能有几十个相同的寄存器模块。设计者不会把同一个模块写几十遍,而是写一次,然后实例化几十次。这就是实例化的意义。

实例化语句长什么样?

// 一个典型的实例化语句
reg_file u_reg_file_0 (
    .clk        (clk),
    .rst_n      (rst_n),
    .wr_en      (wr_en_0),
    .rd_addr    (rd_addr_0),
    .rd_data    (rd_data_0)
);

reg_file u_reg_file_1 (
    .clk        (clk),
    .rst_n      (rst_n),
    .wr_en      (wr_en_1),
    .rd_addr    (rd_addr_1),
    .rd_data    (rd_data_1)
);

你看,reg_file 是模块名,u_reg_file_0u_reg_file_1 是实例名。两个实例用的是同一个模块,但连接的信号不同。

我的小技巧:分析网表时,我会先统计每个模块被实例化了多少次。如果某个模块被实例化了几十次,那它大概率是个基础单元(比如寄存器、加法器)。如果某个模块只出现一次,那它可能是控制核心。

4.3 信号连接——网表的「血管」

信号连接,就是模块之间的「电线」。没有这些连接,模块就是孤岛。网表里,信号连接通过 wirereg 声明,然后在实例化时映射到端口上。

我曾经接手过一个项目,网表里信号命名乱七八糟,什么 sig_1sig_2tmp_3 都有。我当时花了整整两天,才把信号连接关系理清楚。嗯,从那以后我养成了一个习惯——先看信号命名规范。

信号连接的识别方法:

  • 追踪信号名:一个信号从哪个模块输出,又输入到哪个模块。这就像追踪水流一样,找到源头和终点。
  • 看信号位宽wire [31:0] data_bus 表示这是一个 32 位的数据总线。位宽不匹配的地方,往往是 bug 高发区。
  • 注意多驱动:同一个信号被多个模块驱动,这在网表里是严重错误。我见过一次,查了三天才发现是两个模块同时写了一个寄存器。

避坑指南:我曾经在分析一个加密 IP 的网表时,发现一个关键信号被「悬空」了——它没有连接到任何地方。后来才发现,这是设计者故意留的「后门」。所以,信号连接分析不只是技术活,还是安全排查的关键步骤。

4.4 端口映射——模块的「接口协议」

端口映射,就是实例化时把模块的端口和外部信号连起来。格式是 .端口名(信号名)。端口名是模块内部定义的,信号名是外部实际连接的。

举个例子:

// 模块定义
module adder (
    input  [7:0] a,
    input  [7:0] b,
    output [8:0] sum
);

// 实例化时的端口映射
adder u_adder_0 (
    .a      (data_in_0),
    .b      (data_in_1),
    .sum    (result_out)
);

这里 absum 是端口名,data_in_0data_in_1result_out 是外部信号。端口映射就是把模块的「插头」插到对应的「插座」上。

端口映射的注意事项:

  • 端口名不能错:写错端口名,工具会报错。但有些网表里,端口名和信号名一模一样,这反而容易混淆。
  • 注意端口方向inputoutputinout。方向搞反了,信号就传不过去。
  • 未连接的端口:有些端口可能被显式地写成 .port() 空括号,表示悬空。这在安全分析时要特别留意。

4.5 知识体系总览

下面这张图,是我自己总结的网表结构分析框架。你照着这个思路走,基本不会迷路。

网表结构分析知识体系 网表结构分析 模块层次结构 实例化识别 信号连接分析 端口映射识别 顶层模块识别 子模块嵌套 实例化语句 实例名与模块名 信号追踪 位宽与多驱动 端口名与信号名 方向与悬空

4.6 实战技巧总结

说了这么多,我总结几条实战经验给你:

  1. 先宏观后微观:先看模块层次,再看实例化,最后看信号连接。别一上来就扎进细节里。
  2. 善用文本工具:用 grep 搜索模块名、实例名,用 awk 提取端口映射。命令行工具在网表分析时特别好用。
  3. 画图辅助理解:遇到复杂的连接关系,画个框图。我习惯用 Graphviz 自动生成层次图,省时省力。
  4. 注意异常模式:悬空端口、多驱动信号、命名不规范的模块——这些往往是安全漏洞或后门的藏身之处。

最后说一句:网表结构分析,说白了就是「拆解」和「重组」的过程。你把芯片拆成一个个模块,再理解它们怎么连起来的,整个芯片的架构就印在你脑子里了。这个能力,是硬件逆向和 IP 保护的基础中的基础。


公众号:蓝海资料掘金营,微信deep3321