2.1 内建数据类型
通常,在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。
例2.2 带符号的数据类型
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 定宽数组
2.2.1 定宽数组的声明和初始化
所有的数组都是以0作为索引的起始点,所以SystemVerilog允许只给出数组宽度的便捷声明方式。
例2.4 定宽数组的声明
int lo_hi [15:0]; //16个整数[0]...[15]
int c_style [16]; //16个整数[0]...[15]
通过在变量名后指定维度的方式来创建多维定宽数组。
例2.5 声明并使用8行4列的多维数组
int array2 [0:7] [0:3]; //完整的声明
int array3 [8][4]; //紧凑的声明
int array2 [7][3]=1; //设置最后一个元素为1
在SystemVerilog中,仿真器在存放数据元素时使用32比特的字边界,所以byte、shortint和int都放在一个字中,而longint则存放到两个字中。
2.2.2 常量数组
一个单引号加大括号来初始化数组,注意这里的单引号不同于编译器指引或宏定义中的单引号。
例2.7 初始化一个数组
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.3 数组的基本操作for和foreach
对数组进行操作的最常见的方式就是使用for或foreach循环。$size函数会自动返回数组的宽度。foreach循环只需要指定数组名称并在其后面的方括号中给出索引量,SystemVerilog便会自动遍历数组中的元素。
例2.8 在数组操作中使用for和foreach循环
module test_enum();
initial begin
bit[31:0] src[5],dst[5];
int i,j; //无需对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; //dst的值是src的两倍
$display("dst[%0d]=%0d",j,dst[j]);
end
endmodule
对多维数组foreach循环方括号的下标并不是我们想象的[i][j],而是[i,j]。
例2.9 初始化并遍历多维数组
module test_enum();
int md[2][3]='{'{0,1,2},'{3,4,5}}; //对多维数组的赋初始值是在initial之外
initial begin
$display("Initial value:");
// int i,j; //并不需要对i,j进行类型定义
foreach(md[i,j]) //这是正确的语法格式
$display("md[%0d][%0d]=%0d",i,j,md[i][j]);
$display("new value:");
//对最后三个元素重复赋值5
md='{'{9,8,7},'{3{32'd5}}};
foreach(md[i,j]) //这是正确的语法格式
$display("md[%0d][%0d]=%0d",i,j,md[i][j]);
end
endmodule
例2.11 打印一个多维数组
module test_enum();
initial begin
byte twoD[4][6];
foreach(two[i,j])
twoD[i][j]=i*10+j;
foreach(twoD[i]) //遍历第一个维度
begin
$write("%0d:",i);
foreach(two[,j]) //遍历第二个维度
$write("%3d",two[i][j]); //利用位宽来表示空格
$display;
end
end
endmodule
2.2.4 基本的数组操作——复制和比较
例2.13 数组的复制和比较操作
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]", //使用数组片段对第1-4个元素进行比较
(src[1:4] = = dst[1:4])? "==":"!=");
end
endmodule
2.2.5 数组的几种表达方式
1)同时使用数组下标和位下标
例2.14 打印出数组的第一个元素(二进制101)、它的最低位(1)以及紧接的高两位(二进制10)。
initial begin
bit [31:0] src[5]='{5{5}};
$displayb (src[0],, //'b101或'd5
src[0][0],, //'b1
src[0][2:1]); //'b10
end
2)合并数组
声明合并数组时,合并的位和数组大小作为数据类型的一部分必须在变量名前面指出。数组大小定义的格式必须是[msb:lsb],而不是[size]。
例
bit [3:0] [7:0] bytes; //四个字节合并的数组,使用单独的32比特的字来存放。
bytes=32'hCafe_Data;
$display (bytes,, //显示所有的32比特
bytes[3],, //最高位字节“CA”
bytes[3][7]); //最高字节的最高比特位“1”
bit [3:0][7:0] barray[3]; //合并3*32比特
barray[2]; //32比特的数据
barray[2][3]; //8比特的数据
barray[2][3][7]; //单比特的数据
2.3 动态数组
我们知道Verilog数组类型中,都是定宽度的数组,其宽度在编译时就确定了。但是如果我们事先并不知道数组的宽度,那么我们又该如何分配数组的宽度呢?下面我们就来介绍一下动态数组。
动态数组在声明时使用空下标[],数组在最开始时是空的,必须使用new[]操作符来分配空间,同时在方括号中传递数组宽度。
例2.17 使用动态数组
module test_enum();
int dyn[],d2[]; //声明动态数组
initial
begin
dyn=new[5]; //dyn的宽度为5,分配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到$。注意队列的常量(literal)只有大括号而没有数组常量中开头的单引号。
module test_enum();
int j=1,
q2[$]={3,4}, //队列的常量不需要使用单引号'
q[$]={0,2,5}; //{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]],idx=1; //64个bit[63:0] 关联数组assoc
repeat(64) begin //对1,2,4,8,16等等的稀疏元素进行初始化。
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