3. SystemVerilog 验证基础(上):数据类型、数组、结构体、枚举、字符串、接口

各位好,欢迎来到验证基础的第一部分。说实话,很多刚入行的朋友觉得 SystemVerilog 就是 Verilog 加了个“System”前缀,没什么大不了的。但我在项目中吃过不少亏,才明白这些基础类型用不好,后期 debug 能让你怀疑人生。今天咱们就把这些地基打牢。

3.1 数据类型:从 wire/reg 到 logic 的进化

Verilog 时代,wire 和 reg 的区分让无数人头疼。你写个 reg 信号,综合出来可能连个寄存器都没有。SystemVerilog 引入了 logic 类型,彻底解决了这个尴尬。

核心原则:在验证环境中,90% 的场景用 logic 就够了。它既能当线网,也能当变量,综合工具会自动判断。

// 传统 Verilog 写法
wire        clk;
reg  [7:0]  data;
reg         valid;

// SystemVerilog 推荐写法
logic       clk;
logic [7:0] data;
logic       valid;

我个人习惯在顶层模块的端口声明里仍然用 wire,但在验证组件(driver、monitor)内部全部用 logic。这样代码更干净,也少了很多类型转换的麻烦。

注意:logic 不能有多个驱动源。如果你需要三态总线或多驱动场景,还是得用 wire。我曾经有个项目,实习生把双向总线全改成 logic,仿真直接 X 态满天飞。

3.2 数组:定宽、动态、队列与关联数组

SystemVerilog 的数组家族比 Verilog 丰富太多了。咱们一个一个说。

3.2.1 定宽数组与动态数组

定宽数组就是 Verilog 的老朋友,编译时大小就固定了。动态数组则可以在运行时调整大小,用 new[] 分配空间。

// 定宽数组
logic [7:0] fixed_arr [0:15];  // 16个元素,编译时确定

// 动态数组
logic [7:0] dyn_arr[];          // 声明时未分配空间
dyn_arr = new[16];              // 运行时分配16个元素
dyn_arr = new[32](dyn_arr);     // 重新分配为32个,保留前16个值

动态数组在验证环境里特别有用。比如你要模拟一个可变深度的 FIFO,用动态数组就比定宽数组灵活得多。

3.2.2 队列:我最喜欢的数据结构

队列(queue)结合了链表和数组的优点。它支持在头部和尾部快速插入/删除,而且可以像数组一样随机访问。

int q[$];          // 声明一个整数队列
q.push_back(10);   // 尾部插入
q.push_front(5);   // 头部插入
int val = q.pop_back();  // 尾部取出
int val = q.pop_front(); // 头部取出

我的经验:在 scoreboard 里做数据比对时,队列几乎是标配。把预期数据压入队列,DUT 输出一个就 pop 一个比对,逻辑清晰又高效。

3.2.3 关联数组

关联数组用索引来访问,索引可以是任意数据类型。说白了就是个哈希表。

// 以字符串为索引的关联数组
int score[string];
score["Alice"] = 95;
score["Bob"]   = 87;

// 检查是否存在
if (score.exists("Charlie"))
    $display("Found Charlie");

我在做寄存器模型验证时,经常用关联数组来存储所有寄存器的地址和期望值。这样查找起来比遍历数组快得多。

3.3 结构体与联合体

结构体(struct)让你能把相关的信号打包在一起。联合体(union)则让同一块内存有不同的解读方式。

// 结构体定义
typedef struct packed {
    logic        valid;
    logic [7:0]  addr;
    logic [31:0] data;
} packet_t;

packet_t pkt;
pkt.valid = 1'b1;
pkt.addr  = 8'hA0;
pkt.data  = 32'hDEAD_BEEF;

打包 vs 非打包:打包结构体(packed)在内存中是连续的,可以整体做位操作。非打包结构体每个成员独立存储,适合描述不同宽度的信号组合。

联合体我用的相对少,但在解析协议头时特别好用。比如一个 32 位的指令,你可以用联合体同时定义它的位域视图和原始值视图。

3.4 枚举:让状态机不再神秘

枚举类型给常量取个有意义的名字,代码可读性直接拉满。

typedef enum logic [1:0] {
    IDLE   = 2'b00,
    READ   = 2'b01,
    WRITE  = 2'b10,
    ERROR  = 2'b11
} state_t;

state_t current_state, next_state;

枚举的好处不止是好看。仿真器会检查枚举变量的赋值是否合法,防止你写出 current_state = 2'b11 这种硬编码。我曾经在 debug 一个状态机死锁问题时,就是靠枚举的合法性检查找到了一个非法状态跳转。

避坑指南:枚举默认是 int 类型,宽度 32 位。如果你要综合,记得显式指定宽度,比如 enum logic [1:0]。否则综合工具会报一堆警告。

3.5 字符串:不只是文本

SystemVerilog 的字符串类型比 Verilog 的 reg [8*N:1] 好用太多了。它支持动态长度、内建方法,还能直接比较。

string s1 = "Hello";
string s2 = "World";

// 拼接
string s3 = {s1, " ", s2};  // "Hello World"

// 方法
int len = s1.len();          // 5
string upper = s1.toupper(); // "HELLO"
string sub = s1.substr(1,3); // "ell"

// 比较
if (s1 == "Hello")
    $display("Match!");

我在写 UVM 环境的报告机制时,大量使用字符串来构建格式化的日志信息。配合 $sformatf 函数,可以像 C 语言一样格式化输出。

3.6 接口(Interface):模块间通信的利器

接口是 SystemVerilog 对 Verilog 最革命性的改进之一。它把一组相关的信号封装在一起,让模块间的连接变得清晰且可维护。

interface bus_if(input logic clk, rst_n);
    logic [31:0] addr;
    logic [31:0] wdata;
    logic [31:0] rdata;
    logic        we;
    logic        re;
    logic        ready;

    // 时钟块,控制时序
    modport master (
        input  clk, rst_n,
        output addr, wdata, we, re,
        input  rdata, ready
    );

    modport slave (
        input  clk, rst_n,
        input  addr, wdata, we, re,
        output rdata, ready
    );
endinterface

接口的核心价值:当你需要修改总线协议时,只需要改接口定义,所有使用该接口的模块自动更新。这在大型项目中能节省数天的联调时间。

我个人习惯在接口里加上断言(assertion)来检查协议时序。比如地址有效时 valid 信号必须拉高,这种检查放在接口里比分散在各个模块里要优雅得多。

3.7 知识体系总览

下面这张图把本章的核心知识点串了起来,方便你建立整体认知。

SystemVerilog 验证基础(上)知识体系 SV 验证基础(上) 数据类型 数组 结构体/联合体 枚举 字符串 接口(Interface) logic / wire / reg 二值/四值逻辑 有符号/无符号 定宽 / 动态 / 队列 关联数组(哈希表) 数组方法(find/sort) packed / unpacked typedef 自定义类型 联合体(union) 状态机编码 类型安全 内建方法(first/next) 动态长度 内建方法(len/substr) 格式化($sformatf) modport 方向控制 clocking 块 内嵌断言 掌握这些基础,验证代码的骨架就搭起来了

3.8 避坑总结

最后,我把这些年踩过的坑整理成一张表,你写代码时对照着检查,能省不少 debug 时间。

知识点 常见错误 正确做法
logic 类型 多驱动源使用 logic 三态总线用 wire
动态数组 未分配空间就访问 先 new[] 再使用
队列 用索引访问空队列 先检查 size() 或使用 exists()
枚举 未指定宽度导致综合警告 显式声明 enum logic [W-1:0]
接口 modport 方向定义错误 从 master/slave 视角分别定义