SV数据类型

四状态类型

逻辑类型

  • logic:推荐替代wirereg的类型

    • reg:Verilog遗留类型,过程赋值中存储值
    • wire:Verilog遗留类型,连续赋值或端口连接
  • 可表示四种状态:0、1、X(未知)、Z(高阻)

  • 可用于线网连接,也可用于过程赋值,编译器根据上下文自行判断

  • 只能有一个驱动,否则编译报错

    • 如双向总线inout应该使用线网类型wiretri

整数类型

  • integer:带符号的四值整数,不指定明确位宽
    • 位宽由实现定义,通常是32位
    • 主要用于循环控制、通用计算,不用于硬件建模
  • time t:存储仿真时间值的无符号四值整数,不指定明确位宽
    • timeunit 1ns;用于指定仿真中#延迟
    • timeprecision 1ps; 用于指定$time单位

双状态类型

相比于四值类型,引入双值类型有利于提高仿真器性能并减少内存的使用量

整数类型

  • bit:最常用的二值类型,无符号整数
  • bit [31:0]:32比特无符号整数
  • unsigned int:32比特无符号整数
  • int:32比特有符号整数
  • byte :8比特有符号整数,范围是 [128,127][-128, 127]
  • shortint:16比特有符号整数
  • longint:64比特有符号整数

注意:当把双状态变量连接到被测设计输出时,若输出X或Z态会被转换成双状态值而测试代码无法察觉。使用$isunknown操作符,在表达式任意位出现X或Z态时返回1

1
2
if ($isunknown(iport) == 1)
$display("@%t: 4-state value detected on iport %b", $time, iport);

类型转换

静态转换

  • 语法:目标类型'(表达式)
  • 强制改变表达式类型,不进行越界检查
1
2
3
int i = int'(10.0 - 0.1);           // 浮点转整型(截断小数)
bit [7:0] byte_val = unsigned'(-1); // 有符号转无符号(结果为255)
void'(function_call()); // 忽略函数返回值

动态转换

  • 语法:$cast(目标变量, 源表达式)
  • 返回值:1表示转换成功,0表示转换失败
  • 应用场景
    • 非枚举值赋给枚举变量时需显式检查,避免引入非法值
    • 当父类句柄指向子类对象时,将父类句柄转换为子类句柄
1
2
3
4
5
6
7
8
9
10
11
12
// 枚举转换
typedef enum {RED, BLUE} color_e; // RED=0, BLUE=1
int c = 2;
color_e color;
if (!$cast(color, c)) $error("越界"); // c=2超出枚举范围

// 类向下转型
class Parent; endclass
class Child extends Parent; endclass
Parent p = new Child();
Child c;
$cast(c, p); // 合法向下转型

隐式转换

  • 由编译器自动完成,无需额外语法
    • 不同位宽/符号类型:自动扩展/截断、补零或符号扩展
    • 可能引入风险
1
2
logic [3:0] x_vec = 'b11x0; 
bit [2:0] b_vec = x_vec; // 风险:b_vec='b010(x被转为0),四值->二值,可能隐蔽错误

特殊类型

  • 浮点数类型
    • real:双精度浮点数,等效于IEEE 754标准的64位浮点
    • shortreal:单精度浮点数,32位
  • 空类型
    • void声明无返回值的函数或任务
  • 句柄类型
    • chandle存储由C/C++通过DPI传递的指针
  • 事件类型
    • event用于声明同步时间,无数据,仅作为同步信号标识
    • 通过操作符->触发事件,@wait()等待事件
  • 字符串
    • 用于存储动态文本字符串,本质是字符队列

字符串

特性

  • 动态长度:运行时自动调整长度,无需预定义大小
  • ASCII存储:每个字符以字节形式存储 (ASCII 编码)
  • 空终止符:自动维护结尾的\0字符,与C语言兼容
  • 索引访问:支持str[i]访问字符
  • 支持转移序列:\n \t \\ \" \0 \xHH (十六进制)
1
2
3
4
string msg = "Hello SV!";         // 初始化
string empty_str; // 默认值 ""
string path = "dir/file.txt"; // 路径字符串
string hex_str = "A\x42C"; // 包含十六进制字符 (ABC)

内建方法

长度操作

  • len()length():返回字符串长度
  • putc(int i, byte c):替换位置i的的字符为c,索引从0开始
  • getc(int i):获取位置i的字符
1
2
3
4
string word = "test";
int length = word.len(); // length = 4
word.putc(length-1, '!'); // 修改为 "tes!"
byte last_char = word.getc(3); // 返回 '!' (ASCII 33)

大小写转换

  • toupper():转为全大写
  • tolower():转为全小写

大写:Uppercase Letter;小写:Lowercase Letter

子串操作

  • substr(int s, int e):提取子串[s,e]
  • delete(int s, int e):删除子串
1
2
string full = "SystemVerilog";
string sub = full.substr(6, 8); // "Ver"

比较与搜索

  • compare(string s):区分大小写比较,相等时返回0,大于时返回1,小于时返回-1
  • icompare(string s):不区分大小写比较
1
2
3
string a = "apple", b = "Apple";
int cmp1 = a.compare(b); // 非零值 (区分大小写)
int cmp2 = a.icompare(b); // 0 (不区分大小写)
  • find(string substr):正向(从左往右)查找字符串位置
    • 大小写敏感
    • 返回子串第一次出现的起始索引或-1
    • 用于提取前缀、解析命令
  • rfind(string substr):反向(从右往左)查找子串位置
    • 大小写敏感
    • 返回子串最后一次出现的起始索引或-1
    • 用于提取后缀、处理文件扩展名或路径
1
2
3
4
5
6
7
8
9
10
11
string s = "apple,orange,apple";
string file_path = "/home/user/report_2023.pdf";
int pos1 = s.find("apple"); // 返回 0 (第一个"apple")
int pos2 = s.rfind("apple"); // 返回 13 (最后一个"apple")

int dot_pos = file_path.rfind("."); // 查找最后一个点号的位置

if(dot_pos != -1) {
string extension = file_path.substr(dot_pos, file_path.len()-1);
$display("Extension: %s", extension); // 输出 ".pdf"
}

类型转换

  • atoi():字符串->十进制整数
  • atohex():字符串->十六进制整数
  • atobin():字符串->二进制整数
  • atoreal():字符串->实数
  • hextoa(int i):十六进制整数->字符串
    • SV不直接提供itoa(),需先使用$sformatf
  • bintoa(int i):二进制整数->字符串
    • 需先使用$sformatf
1
2
int value = "7F".atohex();  // 十六进制转整数 → 127
real pi = "3.1416".atoreal(); // 字符串转实数

格式化

  • $sformatf:格式化字符串生成器
    • 优先使用此函数代替拼接
    • SV不直接提供itoa(),需使用$sformatf
    • 尽管大多数仿真支持$psprintf函数,功能也和$sformatf相同,但并不是SV的原生函数
1
2
3
4
5
6
int data = 255;
string hex_str = $sformatf("%h", data); // 默认格式 → "ff"
string hex_fmt = $sformatf("0x%h", data); // 带前缀 → "0xff"
string hex_auto = $sformatf("%0h", data); // "ff" (推荐)
string hex_4bit = $sformatf("%4h", value); // " ff" (空格填充)
string hex_04bit = $sformatf("%04h", value); // "00ff" (零填充)

结构体

类型重命名

  • typedef语句为现有的数据类型创建新名称(别名)
    • 提高代码的可读性、复用性和可维护性
    • 约定俗成:一般所有用户自定义类型都带后缀_t
    • typedef定义的类型与原始类型完全等效
  • 定义数组类型的语法:typedef <元素类型> <新类型> [<维度>];
1
2
3
4
5
6
7
8
9
10
11
12
13
// Verilog风格
`define OPSIZE 8
`define OPREG reg [`OPSIZE-1:0]
`OPREG op_a, op_b;

//SystemVerilog风格
parameter OPSIZE = 8;
typedef reg [OPSIZE-1:0] opreg_t;
opreg_t op_a op_b;

//定义数组类型
typedef bit [7:0] byte_array [64];
byte_array buffer;

  • 可以把parametertypedef语句放到一个程序包(package)里,使之能被整个设计和测试平台使用
    • 包允许在模块、包、程序和接口间共享声明
  • 使用import语句从包里导入符号
    • 引用的模块优先使用自己索引路径中的符号,若没有才去包里寻找
    • 使用范围操作符::导入指定的符号
  • 包是完全独立的,可以被放到任何需要的地方
    • 包只能看到包内部定义的符号,或者包自己导入的包
    • 不能层次化引用来自包外部的符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ABC总线的包
package ABC;
parameter int abc_data_width = 32;
typedef logic [abc_data_width-1:0] abc_data_t;
parameter time timeout = 100ns;
string message = "ABC done";
endpackage

// 导入包
module test;
import ABC::*; // 导入包中所有符号

abc_data_t data; // 来自包ABC的符号
string message = "Test timed out"; // 本地的message隐藏了包里的message符号
//若确实需要使用ABC中的messge,则使用ABC::message指定

initial begin
#(timeout); // 来自包ABC的符号
$display("Timeout - %s", message);
$finish;
end
endmodule

创建结构体

  • struct语句把若干变量组合到一个结构中
    • 封装数据,便于模块间传递(可综合)
    • 约定俗成:一般用户自定义类型带后缀_s
1
2
3
4
5
6
typedef struct {
bit [7:0] r, g, b; // 8位红、绿、蓝通道
} pixel_s; // 定义结构体类型 pixel_s

pixel_s my_pixel; // 声明变量
my_pixel = '{'h10, 'h20, 'h30}; // 结构体赋值
  • packed关键字将结构体中的数据合并到尽可能小的空间中
    • 若需经常对整个结构体进行复制,使用合并结构效率更高
    • 若需经常对结构内的个体进行操作,使用非合并结构效率更高
1
2
3
4
// 上例中的pixel_s占用了三个长字的存储空间,即使实际只需三个字节
typedef struct packed { // 使用packed进行压缩
bit [7:0] r, g, b; // 8位红、绿、蓝通道
} pixel_s_p;

流操作符

  • 流操作符用于数据打包和解包,简化数据的序列化操作
    • >>(从左至右打包或解包):将数据打包成比特流,或从比特流中解包提取数据
    • <<(从右至左打包或解包):反序打包数据,或反序解包比特流
1
2
3
4
5
6
7
// 打包(Pack):将多个变量组合成比特流,如总线数据传输
type_packed = { >> {var1, var2, ...} }; // 从左至右打包
type_packed = { << {var1, var2, ...} }; // 从右至左打包

// 解包(Unpack):从比特流中提取数据,如DUT输出数据的解析
{ >> {var1, var2, ...} } = type_packed; // 从左至右解包
{ << {var1, var2, ...} } = type_packed; // 从右至左解包
  • 支持数据结构,使用与结构体、数组等复杂类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct {
logic [7:0] addr;
logic [31:0] data;
} packet_t;

// 打包:将结构体打包成比特流
packet_t tx_packet;
logic [39:0] packed_stream; // 总宽度:8-bit + 32-bit = 40-bit
tx_packet.addr = 8'hA5;
tx_packet.data = 32'h1234_5678;
packed_stream = { >> {tx_packet.addr, tx_packet.data} };
// 从左至右打包:addr在前,data在后

// 解包:从比特流中提取数据
packet_t rx_packet;
{ >> {rx_packet.addr, rx_packet.data} } = packed_stream;
// 解包后:rx_packet.addr = 8'hA5, rx_packet.data = 32'h1234_5678
  • 可指定片段宽度,再将数据按照指定宽度分段后再打包或解包
  • 比特流结果不能直接赋值给非合并数组
    • 这是因为非合并数组相邻元素间可能存在间隙,如定宽数组使用字边界
    • 需在赋值表达式的左边使用流操作符把比特流拆分到非合并数组中
1
2
3
4
5
6
7
8
bit [31:0] stream = 32'hA5A5_A5A5;
int arr [0:3]; // 非合并数组

// 直接赋值违反语言规则
arr = stream; // 编译报错
// 正确:左侧流操作符拆分比特流
{ >> {arr} } = stream; // 等价于按32比特分段,未被赋值的部分自动填充默认值0
{ >> byte {arr} } = stream; // 按照指定片段宽度解包

枚举类型

定义与特性

  • 定义

    • 枚举类型enum定义一组命名常量集合
    • 增强可读性和安全性,避免手动赋值繁琐易出错
  • 特性

    • 默认首标签为0,后续标签自动递增

      • 也可以显式指定任意数值,未赋值的标签继承上一个标签值+1
    • 默认基类为int,支持自定义基类,需显式声明宽度

      • 例如enum bit {TRUE=1, FALSE=0} Boolean;
    • 标签值在同一枚举类型中必须唯一

    • 标签名在同一模块中必须唯一,并且不能以数字开头

    • 支持范围语法批量生成标签

1
2
3
4
5
6
7
8
9
10
11
enum {RED, YELLOW, GREEN} light;  // 默认标签:RED=0, YELLOW=1, GREEN=2
enum {R, Y, G} color; // 默认标签:R=0, Y=1, G=2
//标签值重复:虽然RED和R默认都为0,但属于两个不同的枚举类型,相互独立并不冲突
//也可使用显示赋值,避免打印时混淆

//标签名称重复:同一模块内不同枚举类型的标签名称重复会导致编译错误,哪怕值不同
enum {R = 10, Y, G} color_e; // 默认标签:R=10, Y=11, G=12
//应对方法:使用唯一的标签名;在不同的模块中使用
//或使用typedef定义枚举类型,为每个枚举类型创建独立类型,减少名称污染风险

enum {RESET, S[5], W[6:9]} state; // 生成 RESET, S0-S4, W6-W9

自定义枚举类型

  • 使用typedef定义可复用的枚举类型
    • 支持包导入,需显式导入包import ABC::*;*也可以是指定的枚举类型
1
2
3
4
5
6
7
8
9
10
11
typedef enum {INIT, DECODE, IDLE} fsm_state_e; //使用后缀_e表示枚举类型
fsm_state_e st, nst;

initial begin
case (st)
IDLE: nst = INIT;
INIT: nst = DECODE;
default: nst = IDLE;
endcase
$display("Next state is %s", nst.name());
end

枚举类型子程序

  • first()first返回第一个枚举常量
  • last()返回最后一个枚举常量
  • next()返回下一个枚举常量
  • next(N)返回第N个枚举常量
  • prev()返回前一个枚举常量
  • prev(N)返回前第N个枚举常量

当到达枚举常量列表的头或尾时,函数nextprev会自动以环形方式绕回

1
2
3
4
5
6
7
8
9
10
//遍历所有枚举成员
typedef enum {RED, BLUE, GREEN} color_e;
color_e color;
color = color.first;
do
begin
$display("Color = %0d/%s", color, color.name());
color = color.next;
end
while (color != color.first)