内建数据类型
Verilog将变量类型(reg,wire)区分得如此清楚是因为它作为硬件描述语言,倾向于设计人员自身懂得所描述的电路哪一些变量应实现为寄存器,哪一些变量被实现为线网类型。然而SV作为侧重于验证的语言,可以使用logic变量进行单纯的赋值操作,而这些变量只属于软件环境的构建。
数据类型有如下的分类:
四值逻辑:integer,reg,logic,net-type(wire,tri) 二值逻辑:byte,shortint,int,longint,bit
有符号类型:byte,shortint,int,longint,integer 无符号类型:bit,logic,reg,net-type
当有符号类型数据赋值到无符号类型数据时:
bit [8:0] result_vec;
byte [7:0] signed_vec=8'b1000_0000;
initial begin
result_vec=signed_vec;
$display("@1 result_vec='h%x",result_vec);
result_vec=unsigned'(signed_vec);
$display("@2 result_vec='h%x",result_vec);
end
仿真输出的结果为:
@1 result_vec='h180 @2 result_vec='h080
第一次赋值操作时result_vec=signed_vec,右侧有符号数值-128传递到左侧时,需要从八位扩展到九位,将8'h80扩展到9'h180
数组
定宽数组
常量数组,举例:int ascend[4]='{0,1,2,3};
合并数组与非合并数组:声明合并数组时,数组大小作为数据类型的一部分必须在变量名前面定义,数组大小定义的格式必须为[msb:lsb]而不是[size],例如:bit [3:0][7:0]bytes; //4个字节组成32比特,左边的数字代表高维而右边的代表低维,与非合并数组不同的是,它的存放方式是连续的比特集合,中间没有任何的闲置空间;
声明非合并数组,例如: int array1[0:7][0:3] ; int array2[8][4] //8为高维,4为低维; int [4]array3[8] //右边的数字8为高维; 字的低位用来存放数据,高位则不使用。
如何选择合并数组与非合并数组呢?当你需要和标量进行相互转换时,使用合并数组会非常方便;如果你需要等待数组中的变化,如使用操作符@,则必须使用合并数组。
动态数组
SV提供动态数组类型,在仿真时分配空间或者调整宽度,这样在仿真中可以减小使用最小的存储量。对于组合型数组(packed)数组初始化,同向量初始化一样;对于非组合型数组(unpacked),则要用'{}对数组每一个维度进行赋值
int dyn[],d2[];
initial
begin
dyn=new[5]; //分配5个元素
foreach(dyn[i]) dyn[i]=i; //元素进行初始化
d2=dyn; //复制一个动态数组,强调这里是复制,而不像python中地址的传递
dyn=new[20](dyn); //分配20个整数值并进行复制
dyn.delete(); //删除所有元素
end
logic [3:0][7:0]a=32'h0;
logic [3:0][7:0]b={16'hz,16'h0};
logic[3:0][7:0]c={16{2'b01}};
int d[0:1][0:3]='{'{7,3,0,5},'{2,0,2,1}};
//d[0][0]=7
//d[0][1]=3
//d[1][0]=2
...
队列
它结合了链表和数组的优点,既可以在一个队列之中任何地方增加或者删除元素,这类操作在性能上的损失比动态数组小得多,而且可以通过索引值实现对任一元素的访问,不像链表那样要遍历目标元素之前的所有元素。
队列的声明符号[$],队列元素编号从0到$,注意不要对队列使用构造函数new[]
int j=1;
q2[$]={3,4} //队列的常量不需要"'"
q[$]={0,2,5}
initial begin
q.insert(1,j); //{0,1,2,5}
q.insert(3,q2); //{0,1,2,3,4,5}
q.delete(1); //{0,2,3,4,5}
q.push_front(6); //{6,0,2,3,4,5}
j=q.pop_back; //{6,0,2,3,4},j=5
q.push_back(8); //{6,0,2,3,4,8}
j=q.pop_front; //{0,2,3,4,8},j=6
foreach(q[i])
$display(q[i]);
q.delete();
end
关联数组
如果你需要超大容量的数组,而处理器可能只访问用来存放可执行代码和数据的几百或者几千个字节,这时候,SV提供了关联数组类型用来保存稀疏矩阵的元素。也就是说,当你对一个非常大的地址空间进行寻址时,SV只为实际写入的元素分配空间。
关联数组采用方括号中放置数据类型的形式来进行声明,如[int],[packet]
bit [63:0] assoc[int];
idx=1;
repeat(64) begin
assoc[idx]=idx;
idx=idx<<1;
end
foreach(assoc[i])
display("assoc[%h]=%h",i,assoc[i]);
使用typedef创建新的类型
typedef int fixed_array5[5];
fixed_array5 f5;
initial begin
foreach(f5[i])
f[i]=i;
end
同时,也可以定义枚举类型
typedef enum{INIT,DECODE,IDLE} fsmstate_e;
fsmstate_e pstate,nstate;
initial begin
case(pstate)
IDLE:nstate=INIT;
INIT:nstate=DECODE;
default:nstate=IDLE;
endcase
$display("Next state is %s",nstate.name());
end
枚举值缺省为从0开始递增的整数,你也可以定义自己的枚举值,没有特别指出,枚举类型会被当成int类型存储,所以是从0开始。
SV提供了一些可以遍历枚举类型的函数:
(!) first()返回第一个枚举常量 //color=color.first
(2)last()返回最后一个枚举常量
(3)next()返回下一个枚举常量 //color=color.next
(4) next(N)返回第N个枚举常量
(5)prev()返回前一个枚举常量
(6)prev(N)返回以前第N个枚举变量
字符串
以下代码规范了与字符串相关的几种操作,函数getc(N)返回位置N上的字节,toupper返回一个所有字符大写的字符串,tolower返回一个全小写的字符串。putc(M,C)把字节C写在字符串的M位置上,substr(start,end)提取位置start到end之间所有的字节
string s;
initial begin
s="IEEE";
$display(s.getc(0)); //显示:73('I')
$display(s.tolower()); //ieee
s={s,"-P1800"}; //IEEE-P1800
$display(s.substr(2,5)); //EE-P
表达式位宽
表达式位宽是造成行为不可预知的主要源头之一。
bit[7:0]b8;
bit one=1'b1;
$display(one+one); //1+1=0
b8=one+one;
$display(b8); //1+1=2
$display(one+one+2'b0); //2,使用了常量
$display(2'(one)+one); //2,采用强制类型转换
要避免如上所示的数据溢出造成精度受损的情况,既可以采用临时变量b8,也可以采用强制转换
最后,SV提供了很多新的数据类型,队列适合应用于记分板,可以频繁地增加或者删除数据,动态数组允许程序在运行的时候再指定数组宽度,为测试平台提供灵活性,关联数组可以用于稀疏存储和一些只有单一索引的记分板,枚举类型通过创建具名常量列表而使你的代码便于读写。