SV——数据类型
1.定宽数组
1.1常量数组
一个单引号加大括号来初始化数组,注意这里的单引号不同于编译器指引或宏定义中的单引号。
例:初始化一个数组
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}
1.2数组的基本操作for和foreach
$size函数会自动返回数组的宽度。foreach循环只需要指定数组名称并在其后面的方括号中给出索引量,SystemVerilog便会自动遍历数组中的元素。
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]。
module test_enum(); initial begin byte twoD[4][6]; foreach(twoD[i,j]) twoD[i][j]=i*10+j; foreach(twoD[i]) //遍历第一个维度 begin $write("%0d:",i); foreach(twoD[,j]) //遍历第二个维度 $write("%3d",twoD[i][j]); //利用位宽来表示空格 $display; //$display会自动换行
end
end
endmodule
1.3基本的数组操作——复制和比较
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
1.4数组的几种表达方式
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.动态数组
对于定宽数组,其宽度在编译时就确定了;在不知道数组的宽度情况下采用动态数组。
动态数组在声明时使用空下标[ ],数组在最开始时是空的,必须使用new[ ]操作符来分配空间,同时在方括号中传递数组宽度。
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
3.队列
SystemVerilog引进了一种新的数据类型队列。在一个队列中的任何一个地方增加或删除元素,这类操作在性能上的损失要比动态数组小的多,因为动态数组需要分配新的数组并复制所有元素。
队列的声明是使用的带有美元符号的下标[ $ ],队列元素的编号从0到$。注意队列的常量(literal)只有大括号而没有数组常量中开头的单引号。
1 module test_enum(); 2 int j=1, 3 q2[$]={3,4}, //队列的常量不需要使用单引号' 4 q[$]={0,2,5}; //{0,2,5} 5 initial 6 begin 7 j=q2[$]; //j=4 8 j=q2[0]; //j=3 9 q.insert(1,1); //在第1位插入1{0,1,2,5} 10 q.insert(2,3); //在第2位插入3{0,1,3,2,5} 11 q.delete(1); //删除第一位{0,3,2,5} 12 //下面的操作执行速度很快 13 q.push_front(6); //最前面插入6{6,0,3,2,5} 14 j=q.pop_back; //j=5 {6,0,3,2} 15 q.push_back(8); //在最后插入8{6,0,3,2,8} 16 j=q.pop_front; //j=6{0,3,2,8} 17 foreach(q[i]) 18 $display("%0d",q[i]); //打印整个队列 19 q.delete(); //等价于命令q={};删除整个队列 20 end 21 endmodule
注意:把 $放在一个范围表达式的左边,那么 $将代表最小值[ $:2]等价于[0:2],将 $放在一个范围表达式的右边,那么 $将代表最小值[1: $]等价于[1:2]。
4.关联数组
如果你只是需要对一个有着几个G字节寻址范围的处理器进行建模。在典型的测试中,这个处理器可能只访问了用来存放可执行代码和数据的几百或几千个字节,这种情况下对几个G字节的存储空间进行分配和初始化显然是浪费的。
仿真器一般采用32位地址线或者64位数据作为索引的数据包,显然这是有一定的额外开销的。
关联数组采用在方括号中放置数据类型的形式来进行声明。
1 module test_enum(); 2 bit[63:0] assoc[bit[63:0]],idx=1; //64个bit[63:0] 关联数组assoc 3 4 repeat(64) begin //对1,2,4,8,16等等的稀疏元素进行初始化。 5 assoc[idx]=idx; 6 idx=idx<<1; 7 end 8 9 foreach(assoc[i]) //foreach遍历数组 10 $display("assoc[%0d]=%0d",i,assoc[i]); 11 if(assoc.first(idx)) //使用函数遍历数组 12 begin //得到第一个索引 13 do 14 $display("assoc[%h]=%h",idx,assoc[idx]); 15 while(assoc.next(idx)); //得到下一个索引 16 end 17 18 assoc.first(idx); //找到并删除第一个元素 19 assoc.delete(idx); 20 $display("the array now has %0d elements",assoc.num); 21 end 22 endmodule
5.链表
SystemVerilog提供了链表数据结构,但是应该避免使用它,因为SystemVerilog提供的队列更加高效易用。
6.数组的方法
6.1数组的缩减方法
基本的数组缩减方法就是把一个数组缩减成一个值。最常用的方法就是求和sum,除此之外还有product(乘)and(与)or(或)xor(异或)等。
在进行数组压缩的时候,应该特别重要的一点需要注意,那就是位宽的问题。
例:数组求和
1 module test_enum(); 2 bit on[10]; //单比特数组 3 int total; 4 initial begin 5 foreach(on[i]) 6 on[i]=i; //on[i]的值为0或1 7 $display("on.sum=%0d",on.sum); //on.sum是单比特无符号的数 打印出单比特和 on.sum=1 8 $display("on.sum=%0d",on.sum+32'd0); //on.sum是32比特数 打印出32比特和 on.sum=5 9 //由于total是32比特变量,所以数组的和也是32比特变量 10 total=on.sum; 11 $display ("total=%0d",total); //total=5 12 //将数组和一个32比特数进行比较 13 if (on.sum>=32'd5) //条件成立 14 $display ("sum has 5 or more 1's''); 15 //使用带32比特有符号运算的with表达式 16 $display("int sum=%0d",on.sum with (int'(item))); //利用with来限定on.sum的数据类型,这是一种比较好用的方式 17 end 18 endmodule
SystemVerilog中,对定宽数组、队列、动态数组和关联数组可以使用 $urandom_range( $size(array)-1)来选取随机一个元素,而对于队列和动态数组还可以使用 $urandom_range(array.size()-1)。
6.2数组定位方法
数组定位方法min、max、unique、find
1 module test_enum(); 2 int f[6]={1,6,2,6,8,6}, 3 d[]='{2,4,6,8,10}, 4 q[$ ]={1,3,5,7}, 5 tq[$]; 6 initial 7 begin 8 tq=q.min(); //求最小值{1} 9 foreach(tq[i]) 10 $display("min:tq[%0d]=%0d",i,tq[i]); 11 tq=q.max(); //求最大值{7} 12 foreach(tq[i]) 13 $display("max:tq[%0d]=%0d",i,tq[i]); 14 15 tq=f.unique(); //求数组中唯一值的队列 16 foreach(tq[i]) 17 $display("unique:tq[%0d]=%0d",i,tq[i]); 18 19 tq=d.find with (item>3); //利用find函数做操作 20 foreach(tq[i]) 21 $display("find:tq[%0d]=%0d",i,tq[i]); 22 tq.delete(); //等价的操作 23 foreach(d[i]) 24 if(d[i]>3) 25 tq.push_back(d[i]); 26 foreach(tq[i]) 27 $display("tq[%0d]=%0d",i,tq[i]); 28 29 tq=d.find_index with (item>3); //输出的是index索引也就是第几位的值 30 foreach(tq[i]) 31 $display("tq[%0d]=%0d",i,tq[i]); 32 end 33 endmodule
注意:item被称为重复参数,它代表了数组中一个单独的元素,item是缺省的名字,你也可以指定别的名字。下面四种情况是等价的。
tq=d.find_first with (item==4);
tq=d.find_first() with (item==4);
tq=d.find_first(item) with (item==4);
tq=d.find_first(x) with (x==4);
当数组的缩减方法和条件语句with结合使用时,sum操作符的结果是条件表达式为真的次数。
1 module test_enum(); 2 int count,total, 3 d[]='{9,1,8,3,4,4}; 4 initial begin 5 count=d.sum with (item>7); //比较表达式返回0或1 6 total=d.sum with ((item>7)*item); 7 $display("count=%0d total=%0d",count,total); //2,17 8 9 count=d.sum with (item<8); 10 total=d.sum with (item<8?item:0); 11 $display("count=%0d total=%0d",count,total);//4,12 12 count=d.sum with (item==4); 13 $display("count=%0d",count); //2 14 end 15 endmodule
6.3数组的排列
SystemVerilog有几个可以改变数组中元素顺序的方法。包括反向、正序、逆序、随机。
int d[]='{9,1,8,3,4,4};
d.reverse(); //反向'{4,4,3,8,1,9}
d.sort(); //正序{1,3,4,4,8,9}
d.rsort(); //逆序'{9,8,4,4,3,1}
d.shuffle(); //随机'{9,4,3,8,1,4}
6.4使用数组定位方法建立记分板
7.选择存储类型
其实数据类型的选择是多方面的,我们要考虑灵活性、存储器用量、速度、排序和数据结构等多种方面,在我们以后的应用中,我们将会深入地理解每种不同的数据类型的利弊。
8.使用typedef创建新的类型
在原有数据类型之上定义新的数据类型。为了不混淆,本书约定所有用户自定义类型都带后缀“_t”。
parameter opsize=8;
typedef reg[opsize-1:0] opreg_t;
opreg_t op_a,op_b;
typedef bit[31:0] uint;
typedef int unsigned uint; //等价的两种方式
对于新的数组定义并不是很明显。需要把数组的下标放在新的数组名称中。
typedef int fixed_array5[5];
fixed_array5 f5;
initial
begin
foreach(f5[i])
f5[i]=i;
end
9.创建用户自定义结构
在SystemVerilog中,引入了数据结构的概念。struct只是把数据组织在一起,只是一个数据的集合。
9.1使用struct创建新类型
struct可以把若干个变量组合到一起。我们统一将struct创建的新类型用“_s”来表示。
1 typedef struct{bit[7:0] r, g,b;} pixel_s; 2 pixel_s my_pixel; 3 4 initial 5 begin 6 typedef struct {int a, 7 byte b, 8 shortint c;} my_struct_s; 9 my_struct_s st='{32'haaaaaaaa, 10 8'hbb, 11 16'hcccc}; 12 $display("st=%x %x %x",st.a,st.b,st.c); 13 end
9.2创建可容纳不同类型的联合
联合体,通常意义上来讲就是同一位置放置不同类型的数据。如果需要以若干不同的格式对同一寄存器进行频繁读写时,联合体相当有用。我们约定以“_u”为后缀。
typedef union { int i; real f;} num_u; num_u un; un.f=0.0; //把数值设为浮点形式
9.3合并结构
通过一个例子来描述一下合并结构(packed)可以节省存储空间。
typedef struct packed {bit [7:0] r,g,b} pixel_p_s; pixel_p_s my_pixel;
10.类型转换
10.1静态转换
静态转换不对转换值进行检查。如果越界的话,我们也不能察觉。
基本转换格式:type’(val)。
例: 在整形和实型之间进行静态转换
int i; real r; i=int'(10.0-0.1); //转换是非强制的 r=real'(42); //转换是非强制的
10.2动态转换
动态转换函数$cast允许对越界的数值进行检查,如果不越界返回1,否则返回0。
10.3流操作符
流操作符>>和<<用于把其后的数据打包成一个比特流。>>是把数据从左到右变成数据流,<<是把数据从右到左变成数据流。
1 initial begin 2 int h; 3 bit [7:0] b, 4 g[4], 5 j[4]='{8'ha,8'hb,8'hc,8'hd}; 6 bit [7:0] q,r,s,t; 7 8 h={>>{j}}; //0a0b0c0d把数组打包成整型 9 h={<<{j}}; //b030d050位倒序 10 h={<<byte{j}}; //0d0c0b0a字节倒序 11 b={<<{8'b0011_0101}}; //10101100位倒序 12 b={<<4 {8'b0011_0101}};//0101_0011半字节倒序 13 {>>{q,r,s,t}}=j; //将j分散到四个字节变量里 14 h={>>{t,s,r,q}}; //将四个字节集中到h里 15 end
11.枚举类型
最简单的枚举类型声明包含了一个常量名称列表以及一个或多个变量。
利用内建函数name()可以得到枚举变量值对应的字符串。我们统一用后缀“_e”来表示枚举的数据类型。
11.1定义枚举值
枚举值缺省为从0开始递增的整数,可以自己定义枚举值。通常在我们把0值给枚举常量,可以避免一些不必要的错误。
11.2枚举类型的子程序
(1)first() 返回第一个枚举变量
(2)last() 返回最后一个枚举变量
(3)next() 返回下一个枚举变量
(4)next(N) 返回以后第N个枚举变量
(5)prev() 返回前一个枚举变量
(6)prev(N) 返回以前第N个枚举变量
遍历所有的枚举成员(注意对枚举类型值的定义),当到达枚举常量列表的头或尾时,函数next和prev会自动以环形方式绕回。
11.3枚举类型的转换
枚举类型的缺省类型为双状态的int。可以通过简单的赋值表达式把枚举变量直接赋值给变量int。
不允许直接把int赋值给枚举变量,这种是出于越界情况的考虑。
1 module test_enum(); 2 typedef enum {RED,BLUE,GREEN} COLOR_E; 3 COLOR_E color,c2; 4 int c; 5 6 initial 7 begin 8 color=BLUE; //赋一个已知的合法值 9 c=color; //将枚举类型转换成整型 10 c++; //整型递增 11 if(!$cast(color,c)) //将整型显示转换回枚举类型 12 $display("cast failed for c=%0d",c); 13 $display("color is %0d/%s",color,color.name); 14 c++; //对于枚举类型已经越界 15 c2=COLOR_E'(c); //不做类型检查 16 $display("c2 is %0d/%s",c2,c2.name); 17 if(!$cast(color,c)) 18 $display("cast failed for c=%0d",c); 19 end 20 endmodule
结果:
#color is 2/GREEN #c2 is 3/ #cast failed for c=3
$cast(color,c)将int型动态转化为枚举类型,如果没有越界返回1,否则返回0;界内(0,1,2),3已经越界了。
12.常量
SystemVerilog中支持const修饰符,允许在变量声明时对其进行初始化,但不能在过程代码中改变其值。
13.字符串
SystemVerilog中的string类型可以用来保存长度可变的字符串。单个字节是byte类型。字符串使用动态的存储方式,所以不用担心存储空间会全部用完。
1 module test_enum(); 2 string s; 3 4 initial begin 5 s="IEEE "; 6 $display(s.getc(0));//显示:73('I') 7 $display(s.tolower()); //显示:ieee 8 9 s.putc(s.len()-1,"-"); //将空格变为'-' 10 s={s,"P1800"}; //“IEEE-P1800” 11 12 $display(s.substr(2,5)); //显示:EE-P 13 //创建临时字符串,注意格式 14 my_log($psprintf("%s %5d",s,42)); 15 end 16 17 task my_log (string message); 18 //把信息打印到日志里 19 $display("@%0t:%s",$time, message); 20 endtask 21 endmodule
getc(N) 返回位置N上的字节
tolower()返回一个小写的字符串
putc(M,C)把字节C写到字符串的M位上,M必须介于0和len所给出的长度之间。
substr(start,end),读取从位置start到end之间的所有字符。
参考资料:SystemVerilog验证:测试平台编写指南