初学SystemVerilog我们在Verilog的基础之上提供了很多改进的数据结构。本章将分为几篇文章来讲述一下对验证很有用的数据结构。
2.1 Verilog中的数据类型
通常,在Verilog中我们有两种常见的数据类型:变量和线网。他们各自有0、1、Z、X这四种状态。其中最为常见的应用也就是reg型和wire型。
变量
- 无符号的数:reg
- 32比特的有符号数:integer
- 64比特的无符号数或浮点数:time
若干变量可以被一起存放在定宽数组里。所有的存储都是静态的,意味着所有的变量在整个仿真过程中都是存活的。
线网 - wire
线网通常用来连接设计中的不同部分。通常是连接设计模块的端口。
2.1.1逻辑(logic)类型
相对于通常的Verilog的reg类型,logic类型在其基础之上对其做了改进,使得它不仅可以作为一个变量,还可以被连续赋值、门单元和模块所驱动。任何使用线网类型的数据均可使用logic。logic不能有多个结构性的驱动,在定义双向总线的时候,就只能使用wire而不能使用logic。
2.1.2 双状态数据类型
相比于四态(0、1、X、Z)的数据类型,SystemVerilog引入了双态(0、1)的数据类型,这有利于提高仿真器的性能并减少内存的使用量。
无符号单比特的数据类型bit,带符号的数据类型是byte,shortint,int,longint。
- bit b,双状态,单比特,无符号
- bit [31:0] b32,双状态,32比特无符号整数
- int unsigned ui,双状态,32比特无符号整数
- int i,双状态,32位有符号整数
- byte b8,双状态,8比特有符号整数,取值范围为-128~127
- shortint s,双状态,16比特有符号整数
- longint l,双状态,64比特有符号整数
- integer i4,四状态,32比特有符号整数
- time t,四状态,64比特无符号整数
- real r,双状态,双精度浮点数
2.2确定宽度的数组
SystemVerilog提供了多个数据类型,功能上也大大增强。
2.2.1定宽数组的声明和初始化
所有的数组都是以0作为索引的起始点,所以SystemVerilog允许只给出数组宽度的便捷声明方式。
int i[0:15]; //16个整数[0] [1] ......[15]
int i_1[16]; //16个整数[0][1].......[15]
多维数组的定义:大小为8行4列
int array2[0:7][0:3]; //完整定义
int array3[8][4]; //紧凑的声明
array2[7][3]=1;//对最后一个元素设置为1
在SystemVerilog中,仿真器在存放数据元素时使用32比特的字边界,所以byte、shortint和int都放在一个字中,而longint则存放到两个字中。为了更加清楚地认识到这点,我们来看下面的例子。
bit [7:0] b_unpack[3]
由上图可以看出,实际上浪费了3/4的存储空间,为了避免这个问题,我们后面引入了合并数组。
2.2.2常量数组
一个单引号加大括号来初始化数组,注意这里的单引号不同于编译器指引或宏定义中的单引号。
int ascend [4]='{0,1,2,3}; //对4个元素进行初始化
int descend [5];
descend='{4,3,2,1,0}; //对5个元素进行初始化
descend[0:2]='{5,6,7};//对前三个元素赋值
ascend=‘’{4{8}};//四个值全部为8
descend ='{9,8,default:1};//{9,8,1,1,1}
2.2.3for和foreach
对数组进行操作的最常见的方式就是使用for或foreach循环。$size函数会自动返回数组的宽度。foreach循环只需要指定数组名称并在其后面的方括号中给出索引量,SystemVerilog便会自动遍历数组中的元素。
对一维数组的操作代码如下:
module test_enum();
initial
begin
bit[31:0] src[5],dst[5];
int i,j;
for(int i=0;i<$size(src);i++)
begin
src[i]=i;
$display("src[%0d]=%0d",i,src[i]);
end
foreach(dst[j])
begin
dst[j]=src[j]*2;
$display("dst[%0d]=%0d",j,dst[j]);
end
endmodule
其结果如下:
# src[0]=0
# src[1]=1
# src[2]=2
# src[3]=3
# src[4]=4
# dst[0]=0
# dst[1]=2
# dst[2]=4
# dst[3]=6
# dst[4]=8
对多维数组foreach循环方括号的下标并不是我们想象的[i][j],而是[i,j]。下面以两个例子来进行说明。
module test_enum();
int md[2][3]='{'{0,1,2},'{3,4,5}}; //对多维数组的赋初始值是在initial之外
initial
begin
// int i,j; //并不需要对i,j进行类型定义
foreach(md[i,j])
$display("md[%0d][%0d]=%0d",i,j,md[i][j]);
end
endmodule
结果为:
# md[0][0]=0
# md[0][1]=1
# md[0][2]=2
# md[1][0]=3
# md[1][1]=4
# md[1][2]=5
另一个例子:
module test_enum();
initial
begin
byte two[4][6];
foreach(two[i,j])
two[i][j]=i*10+j;
foreach(two[i])
begin
$write("%0d:",i);
foreach(two[,j])
$write("%3d",two[i][j]); //利用位宽来表示空格
$display;
end
end
endmodule
结果是:
# 0: 0 1 2 3 4 5
# 1: 10 11 12 13 14 15
# 2: 20 21 22 23 24 25
# 3: 30 31 32 33 34 35
能够对上述两个例子有一个深入的了解就能够掌握for和foreach的用法。
2.2.4基本的数组操作-------复制和比较
数组的复制和比较其实很简单,下面我就用一段代码来给大家讲述一下。
module test_enum();
bit[31:0] src[5]='{0,1,2,3,4},
dst[5]='{5,4,3,2,1}; //赋初始值放在外面
initial
begin
if(src==dst)
$display("src==dst");
else
$display("src!=dst");
dst=src;//将src赋给dst
src[0]=5;//将src的第一个元素赋值为5
$display("src %s dst",(src==dst)? "==":"!="); //以这种方式来比较
$display("src[1:4] %s dst[1:4]",(src[1:4]==dst[1:4])? "==":"!=");
end
endmodule
2.2.5数组的几种表示方式
-
同时使用位下标和数组下标
bit[31:0] src[5]='{5{5}};
src[0]=5;src[0][0]=1;src[0][2:1]='b10
-
合并数组
声明合并数组时,合并的位和数组大小作为数据类型的一部分必须在变量名前面指出。数组大小定义的格式必须是[msb:lsb],而不是[size]。bit [3:0][7:0] bytes; //四个字节合并的数组,使用单独的32比特的字来存放。 bytes//32比特 bytes[3]//最高位字节 bytes[3][7]//最高字节的最高位 bit [3:0][7:0] barray[3];//3*32比特 barray[2]//32比特的数据 barray[2][3]//8比特的数据 barray[2][3][7]//单比特的数据
2.3动态数组
我们知道Verilog数组类型中,都是定宽度的数组,其宽度在编译时就确定了。但是如果我们事先并不知道数组的宽度,那么我们又该如何分配数组的宽度呢?下面我们就来介绍一下动态数组。
动态数组在声明时使用空下标[],数组在最开始时是空的,必须使用new[]操作符来分配空间,同时在方括号中传递数组宽度。
下面我们通过一个例子来深入了解一下动态数组。
module test_enum();
int dyn[],d2[]; //声明动态数组
initial
begin
dyn=new[5];//dyn的宽度为5
foreach(dyn[j])
dyn[j]=j;
d2=dyn; //复制动态数组
d2[0]=5;
$display("%d %d",dyn[0],d2[0]); // 0,5
dyn=new[20](dyn); //给dyn分配20个整数值并将前五个值进行复制
$display("%d %d",dyn[3],dyn[19]);//3,0
dyn=new[100];//分配100个整数值给dyn
dyn.delete(); //删除所有元素
end
endmodule
2.4队列
SystemVerilog引进了一种新的数据类型队列。在一个队列中的任何一个地方增加或删除元素,这类操作在性能上的损失要比动态数组小的多,因为动态数组需要分配新的数组并复制所有元素。
队列的声明是使用的带有美元符号的下标[$],队列元素的编号从0到$。
module test_enum();
int j,
q2[$]={3,4},
q[$]={0,2,5};
initial
begin
j=q2[$]; //j=4
j=q2[0]; //j=3
q.insert(1,1); //在第1位插入1{0,1,2,5}
q.insert(2,3);//在第2位插入3{0,1,3,2,5}
q.delete(1);//删除第一位{0,3,2,5}
q.push_front(6); //最前面插入6{6,0,3,2,5}
j=q.pop_back; //j=5 {6,0,3,2}
q.push_back(8);//在最后插入8{6,0,3,2,8}
j=q.pop_front;//j=6{0,3,2,8}
foreach(q[i])
$display("%0d",q[i]);
q.delete();//等价于命令q={};
end
endmodule
注意:把$放在一个范围表达式的左边,那么$将代表最小值[$:2]等价于[0:2],将$放在一个范围表达式的右边,那么$将代表最小值[1:$]等价于[1:2]
2.5关联数组
如果你只是需要对一个有着几个G字节寻址范围的处理器进行建模。在典型的测试中,这个处理器可能只访问了用来存放可执行代码和数据的几百或几千个字节,这种情况下对几个G字节的存储空间进行分配和初始化显然是浪费的。
仿真器一般采用32位地址线或者64位数据作为索引的数据包,显然这是有一定的额外开销的。
关联数组采用在方括号中放置数据类型的形式来进行声明
module test_enum();
bit[63:0] assoc[bit[63:0]], //64个bit[63:0]
idx=1;
initial
begin
repeat(64) //对1,2,4,8,16等等的稀疏元素进行初始化。
begin
assoc[idx]=idx;
idx=idx<<1;
end
foreach(assoc[i]) //foreach遍历数组
$display("assoc[%0d]=%0d",i,assoc[i]);
if(assoc.first(idx)) //遍历数组的另一种形式
begin
do
$display("assoc[%h]=%h",idx,assoc[idx]);
while(assoc.next(idx));
end
assoc.first(idx); //找到并删除第一个元素
assoc.delete(idx);
$display("the array now has %0d elements",assoc.num);
end
endmodule