第二章
与Verilog相比,SystemVerilog提供了很多改进的数据结构
SystemVerilog引进了一些新的数据类型,它们具有如下优点:
- 双状态数据类型:更好的性能、更低的内存消耗
- 队列、动态和关联数组:减少内存消耗,自带搜索和分类功能
- 类和结构:支持抽象数据类型
- 联合和合并结构:允许对同一数据有多种视图(view)
- 字符串:支持内建的字符序列
- 美剧类型:方便代码编写,增加可读性
内建数据类型
Verilog-1995有两种基本的数据类型:变量和线网(net)
它们各自都可以有四种取值:0,1,Z,X
RTL代码使用变量来存放组合和时序值,变量可以是单比特或多比特的无符号数(reg7:0m),32比特的有符号数(integer),64比特的无符号数(time)或浮点数(real)
若干变量可以被一起存放到定宽数组里
所有的存储都是静态的,意味着所有的变量在整个仿真过程中都是存活的,子程序(routine)不能通过堆栈来保存形式参数和局部变量
线网可以用来连接设计当中的不同部分,例如门和模块示例。
尽管线网有很多用法,但是大多数设计者还是使用标量或矢量wire来连接各个设计模块的端口
逻辑(logic)类型
在Verilig中,初学者经常分不清reg和wire两者的区别?应该使用它们中哪一个来驱动端口?连接不同模块时又该如何做?
SystemVerilog对经典的reg 数据类型进行了改进,使得它除了作为一个变量以外,还可以被连续赋值、门单元、模块所驱动
为了与寄存器类型相区别,这种改进的数据类型被称为logic,任何使用线网的地方均可以使用logic,但要求logic不能有多个结构性的驱动,例如在对双向总线建模的时候。此时需要使用线网类型,例如wire
SystemVerilog会对多个数据来源进行解析以后确定最终值
//logic类型的使用
module logic_data_type(input logic rst_h);
parameter CYCLE=20;
logic q,q_l,d,clk,rst_l;
initial begin
clk=0; //过程赋值
forever #(CYCLE/2) clk=~clk;
end
assign rst_l=~rst_h; //连续赋值
not n1(q_l,q); //q_l被门驱动
my_dff d1(q,d,clk,rst_l); //q被模块驱动
endmodule
由于logic类型只能有一个驱动,所以你可以使用它来查找网单中的漏洞
把你所有的信号都声明为logic而不是reg或wire,如果存在多个驱动,那么编译时就会出现错误。当然,有些信号你本来就希望它有多个驱动,例如双向总线,这些信号就需要被定义成线网类型,例如wire
双状态数据类型
相比四状态数据类型,SystemVerilog引入的双状态数据类型有利于提高仿真器的性能并减少内存的使用量
最简单的双状态数据类型是bit,它是无符号的。另四种带符号的双状态数据类型是byte,shortint,int和longint
-
bit
-
byte
-
shortint
-
int
-
longint
//带符号的数据类型
bit b; //双状态,单比特
bit[31:0] b32; //双状态,32比特无符号数
int unsigned ui; //双状态,32比特无符号数
int i; //双状态,32比特无符号数
byte b8; //双状态,8比特无符号数
shortint s; //双状态,16比特无符号数
longint l; //双状态,64比特无符号数
integer i4; //四状态,32比特无符号数
time t; //四状态,64比特无符号数
real r; //双状态,双精度浮点数
可能会喜欢用 byte 替代 logic[7:0],因为写起来更短
但是 :byte 是有符号 类型,范围是 -128 ~ 127,而不是 0 ~ 255
byte b = 255; // 实际值是 -1,不是 255!
如果你需要一个 0~255 的无符号字节,可以写 byte unsigned。
但仔细想想:byte unsigned 其实比 bit[7:0] 还要多打几个字,简洁性优势荡然无存
建议 :除非你明确需要有符号运算,否则表示无符号数据时优先使用**bit[7:0] 或 logic[7:0]**
随机化中的隐患
有符号变量在随机化时可能产生意想不到的结果
例如,约束一个 byte 变量大于 0,你可能会得到 127 以下的合法值,但如果你没意识到它是有符号的,某些"正值"约束可能表现异常。
在把双状态变量连接到被测设计,尤其是被测设计的输出时务必要小心。如果被测设计试图产生X或Z,这些值就会被转换成双状态值,而测试代码可能永远页察觉不了。这些值被转换成0还是1并不必要,重要的是随时检查未知值的传播。使用($isunknown)操作符,可以在表达式的任意位出现X或Z时返回1
//对四状态值的检查 if($isunknown(iport)==1) $display("@%0t:4-state value detected on iport %b",$time,iport);使用格式符%0t和参数time可以打印出当前的仿真时间,打印的格式在timeformat()子程序中指定
双状态变量与四状态值的危险转换
SystemVerilog 引入了双状态 数据类型(bit、byte、int 等),只有 0/1。
而硬件世界是四状态 的(logic、reg、wire 等),可以有 0/1/X/Z。
当你把四状态值 (来自 DUT 输出)赋值给双状态变量时:
-
X 或 Z 会被自动转换成 0 或 1
-
转换规则可能是 0,也可能是 1(取决于工具)
-
测试代码完全察觉不到曾经存在 X 或 Z
这会导致一个严重问题:
设计中可能出现了 X 传播(未知值),但你的测试平台把它悄悄"抹平"了,误以为一切正常。
正确做法
连接 DUT 输出时,尽量使用四状态变量 (logic),而不是双状态变量。
如果必须使用双状态变量,或者你想主动检查是否存在 X/Z,可以使用系统函数 $isunknown()
$isunknown():检测未知值
if ($isunknown(iport) == 1)
$display("@%0t: 4-state value detected on iport %b",
$time, iport);
输出格式说明
%0t:打印时间值,不显示多余的空格
$time:返回当前仿真时间
$timeformat():可以预设时间打印格式
| 类型 | 有无符号 | 取值范围 | 状态数 | 注意 |
|---|---|---|---|---|
bit[7:0] |
无符号 | 0 ~ 255 | 双状态 | 推荐用于无符号数据 |
byte |
有符号 | -128 ~ 127 | 双状态 | 随机化时易出错 |
byte unsigned |
无符号 | 0 ~ 255 | 双状态 | 比 bit[7:0] 还啰嗦 |
logic[7:0] |
无符号 | 0 ~ 255 | 四状态 | 连接 DUT 输出时首选 |
两条黄金法则
-
表示无符号数据时,优先用
bit[7:0]而非byte -
连接 DUT 输出时,用
logic(四状态),不要用bit(双状态) ,除非你主动用$isunknown()检测 X/Z