4、网表解析入门:Verilog网表的结构解析

各位同学,今天我们来聊聊网表解析。说实话,我刚入行那会儿,觉得网表就是一堆乱糟糟的文本,看着就头疼。但后来我发现,搞硬件安全,尤其是木马检测,看不懂网表就等于瞎子摸象。

网表是什么?说白了,它就是芯片设计的“电路图纸”。只不过这张图纸是用文本描述的。你想想看,一个芯片里几百万个门,总不能真画出来吧?网表就是干这个的。

4.1 Verilog网表长什么样?

先看一个最简单的例子。我随便写个两输入与非门的网表:

module nand2 (A, B, Y);
  input A, B;
  output Y;
  nand (Y, A, B);
endmodule

嗯,就这么几行。但别小看它,这里面包含了网表的核心要素:

  • 模块(Module):用module...endmodule包裹起来,就像个黑盒子
  • 端口(Port):A、B是输入,Y是输出
  • 实例化(Instance):这里nand就是系统自带的门级原语
  • 连线(Wire):虽然没有显式声明,但端口之间默认就是wire连接

核心要点:网表解析的第一步,就是搞清楚“谁连了谁”。每个模块的端口,最终都会映射到具体的连线或引脚上。

4.2 模块与实例化的关系

我在项目中遇到过不少新手,搞不清模块和实例化的区别。其实很简单:

  • 模块是“模板”:就像做月饼的模具,定义了形状和功能
  • 实例化是“做月饼”:用模具做出一个个具体的月饼,每个月饼可以有不同的馅料

看个实际例子:

// 先定义模块(模板)
module dff (clk, d, q);
  input clk, d;
  output q;
  reg q;
  always @(posedge clk) q <= d;
endmodule

// 然后实例化(做月饼)
module top (clk, din, dout);
  input clk;
  input [1:0] din;
  output [1:0] dout;

  // 这里实例化了两个D触发器
  dff u1 (.clk(clk), .d(din[0]), .q(dout[0]));
  dff u2 (.clk(clk), .d(din[1]), .q(dout[1]));
endmodule

你发现没?u1和u2都是dff这个模块的实例。它们共享同一个“模板”,但连接的数据不同。在木马检测中,我们经常要追踪某个实例的输入输出,看看它是不是被“动了手脚”。

我的小技巧:解析网表时,我习惯先画个模块层级树。顶层模块是根,下面挂着一堆子模块实例。这样一眼就能看出哪些模块被复用了,哪些是“孤零零”的——后者往往是木马藏身的好地方。

4.3 端口与连线的识别

端口和连线,是网表里最基础也最容易混淆的两个概念。我简单总结一下:

类型 声明方式 作用 典型例子
input input [width-1:0] name; 从外部接收信号 时钟、复位、数据输入
output output [width-1:0] name; 向外部发送信号 数据输出、状态标志
inout inout [width-1:0] name; 双向信号(如I2C的SDA) 总线数据线
wire wire [width-1:0] name; 模块内部的连线 中间信号、模块间连接
reg reg [width-1:0] name; 存储单元(在always块中赋值) 触发器输出、状态寄存器

这里有个坑,我曾经踩过:端口默认是wire类型。也就是说,如果你写output q;,这个q默认就是wire。但如果你在always块里给q赋值,那就必须声明成output reg q;。否则综合工具会报错。

注意:在木马检测中,要特别留意那些“悬空”的端口或连线。比如一个模块的输入端口没接任何东西,或者输出端口没被使用——这可能是木马在“偷听”或者“注入”信号。

4.4 实战:解析一个简单的网表

咱们来动手解析一个实际网表。假设你拿到这样一个文件:

module counter (clk, rst, en, count);
  input clk, rst, en;
  output [3:0] count;
  reg [3:0] count;

  wire clk_gated;
  and (clk_gated, clk, en);

  always @(posedge clk_gated or posedge rst) begin
    if (rst)
      count <= 4'b0;
    else
      count <= count + 1;
  end
endmodule

我来带你一步步解析:

  1. 找模块边界:module counter ... endmodule,这就是一个完整的模块
  2. 识别端口:clk、rst、en是输入,count[3:0]是输出
  3. 找内部连线:clk_gated是一个wire,它由and门驱动
  4. 看实例化:这里and是系统原语,相当于实例化了与门
  5. 追踪数据流:clk和en经过与门变成clk_gated,然后驱动always块

嗯,到这里你应该能看出门道了。网表解析,本质上就是“找模块、认端口、追连线”。

4.5 知识体系总览

下面这张图,是我自己总结的网表解析知识框架。你把它记在心里,以后解析任何网表都不会迷路:

网表解析知识体系 Verilog网表文件 (.v) 模块 (Module) 实例化 (Instance) 端口与连线 模块要素 • 模块名与端口列表 • 内部信号声明 • 功能描述(always等) 实例化要素 • 实例名(如 u1, u2) • 端口映射(.port(net)) • 参数传递(#(...)) 端口与连线要素 • input / output / inout • wire / reg 类型 • 位宽与向量 实战应用:木马检测 追踪异常连线 → 识别未使用端口 → 发现隐藏逻辑 网表解析报告 / 木马检测结果

这张图把网表解析的整个流程串起来了。从最顶层的网表文件开始,往下拆成三大核心要素,再细化到具体的技术点,最后落到木马检测的实战应用上。我个人建议你把它打印出来贴在工位上,每次解析网表前看一眼,思路会清晰很多。

我的习惯:拿到一个陌生网表,我第一步不是看代码,而是先画模块层级图。用纸笔画也行,用工具画也行。画完之后,哪些模块是“叶子节点”(没有子模块),哪些是“中间节点”,一目了然。木马最喜欢藏在叶子节点里,因为那里检查的人少。

好了,网表解析入门就讲到这里。记住三个关键词:模块、实例化、端口连线。下次你拿到一个网表,先找模块边界,再看实例化关系,最后追踪端口连线。按这个顺序来,不会乱。


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