2.2定宽数组
相比于 Verilog1995中的一维定宽数组, System verilog提供了更加多样的数组类型,功能上也大大增强。
2.2.1定宽数组的声明和初始化
Verilog要求在声明中必须给出数组的上下界。因为几乎所有数组都使用0作为索引下界,所以 System verilog允许只给出数组宽度的便捷声明方式,跟C语言类似。
例2.4定宽数组的声明
int lo_hi[0:15];//16个整数[0] ... [15]
int c_style[16];//16个整数[0] ...[15]
可以通过在变量名后面指定维度的方式来创建多维定宽数组。例2.5创建了几个ニ维的整数数组,大小都是8行4列,最后一个元素的值被设置为1。多维数组在 Verilog2001中已经引人,但这种紧凑型声明方式却是新的。
例2.5声明并使用多维数组
int array2[0:7][0:3] ;//完整的声明
int array3[8][4]; //紧凑的声明
array2[7][3]=1; //设置最后一个元素
如果你的代码试图从一个越界的地址中读取数据,那么 System Verilog将返回数组元素类型的缺省值。也就是说,对于一个元素为四状态类型的数组,例如logic,返回的是X,而对于双状态类型例如int或bit,则返回0。这适用于所有数组类型,包括定宽数组、动态数组、关联数组和队列,也同时适用于地址中含有X或Z的情况。线网在没有驱动的时候输出是Z。
很多 System Verilog仿真器在存放数组元素时使用32比特的字边界,所以byte,shorting和int都是存放在一个字中,而longint则存放到两个字中。如例2.6所示,在非合并数组中,字的低位用来存放数据,高位则不使用。图2.1所示的字节数组 b_unpack被存放到三个字的空间里。
例2.6非合并数组的声明
bit [7:0] b_unpack[3];//非合并数组
非合并数组会在2.2.6节中介绍。仿真器通常使用两个或两个以上连续的字来存放logic和 integer等四状态类型,这会比存放双状态变量多占用一倍的空间。
2.2.2常量数组
例2.7示范了如何使用常量数组,即一个单引号加大括号来初始化数组(注意这里的单引号并不同于编译器指引或宏定义中的单引号)。你可以一次性地为数组的部分或所有元素赋值。在大括号前标上重复次数可以对多个元素重复赋值,还可以为那些没有显式赋值的元素指定一个缺省值。
例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};//为前3个元素赋值
ascend='{4{8}}; //4个值全部为8
descend='{9,8,default:1};//{9,8,1,1,1}
2.2.3基本的数组操作——fox和foreach
操作数组的最常见的方式是使用for或 foreach循环。在例2.8中,i被声明为for循环内的局部变量。 System Verilog的 ssize函数返回数组的宽度。在 foreach循环中,只需要指定数组名并在其后面的方括号中给出索引变量, System verilog便会自动遍历数组中的元素。索引变量将自动声明,并只在循环内有效。
例2.8在数组操作中使用for和 foreach循环
initial
beginbit [31:0] src[5],dst[5];for (int i=0;i<$size(src);i++)src[i]=i;foreach (dst[j])dst[j]=src[j]*2;//dst的值是src的两倍end
注意在例29中,对多维数组使用 foreach的语法可能会与你设想的有所不同。使用时并不是像[i][j]这样把每个下标分别列在不同的方括号里,而是用逗号隔开后放在同一个方括号里,像[i,j]。
例2.9初始化并遍历多维数组
int md[2][3]='{'{0,1,2},'{3,4,5}};
initial begin$display("initial value:");foreach(md[i,j]);//这是正确的语法格式$display("md[%0d][%0d]=%d",i,j,md[i][j]);$display("new value ");//对最后三个元素重复赋值5md='{'{9,8,7},'{3{32'd5}}};foreach (md[i,j])//这是正确的语法格式$display("md[%0d][%0d]=%0d",i,j,md[i][j]);end
例2.9的输出结果如例2.10所示。
例2.10多维数组元素值的打印输出结果
initial value:
md[0][0]=0
md[0][1]=1
md[0][2]=2
md[1][0]=3
md[1][2]=4
md[1][3]=5new value :
md[0][0]=9
md[0][1]=8
md[0][2]=7
md[1][0]=5
md[1][1]=5
md[1][2]=5
如果你不需要遍历数组中的所有维度,可以在 foreach循环里忽略掉它们。例2.11把一个二维数组打印成一个方形的阵列。它在外层循环中遍历第一个维度,然后在内层循环中遍历第二个维度。例2.11打印一个多维数组。
initial beginbyte twoD[4][6];foreach(twoD[i,j])twoD[i][j]=i*10 +j;foreach (twoD[i]) //遍历第一个维度begin$write("%2d:",i);foreach(twoD[,j])//遍历第二个维度$write("%3d",twoD[i][j]);$display;end
end
例2.11的输出结果如例2.12所示例2.12打印多维数组的输出结果
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
最后要补充的是, foreach循环会遍历原始声明中的数组范围。数组f[5]等同于f[0:4],而 foreach(f[i])等同于for(inti=0;i<=4;i++)。对于数组rev[6:2]来说,foreach(rev[i])语句等同于for(inti=6;i>=2;i--)。
2.2.4基本的数组操作ー一复制和比较
你可以在不使用循环的情况下对数组进行聚合比较和复制(聚合操作适用于整个数组而不是单个元素),其中比较只限于等于比较或不等于比较。例2.13列出了几个比较的例子。操作符?:是一个袖珍型的if语句,在例2.13中用来对两个字符串进行选择。例子最后的比较语句使用了数组的一部分,src[1:4],它实际上产生了一个有四个元素的临时数组。
例2.13数组的复制和比较操作
initial beginbit [31:0] src[5]='{0,1,2,3,4},dst[5]='{5,4,3,2,1};//两个数组的聚合比较if(src==dst)$display("src==dst");else$display("src!=dst");//把src所有元素赋值给dstdst=src;//只改变一个元素的值src[0]=5;//所有元素的值是否相等(否!)$display("src %s dst",(src==dst));//使用数组片段对第1——4个元素进行比较$display("src[1:4] $s dst[1:4]",(src[1:4]==dst[1:4]?"==":"!="));end
对数组的算术运算不能使用聚合操作,而应该使用循环,例如加法等运算。对于逻辑运算,例如异或等运算,只能使用循环或2.2.6节中描述的合并数组。
2.2.5同时使用位下标和数组下标
在Verilog1995中一个很不方便的地方就是数组下标和位下标不能同时使用。 Verilog2001对定宽数组取消了这个限制。例2.14打印出数组的第一个元素(二进制101)、它的最低位(1)以及紧接的高两位(二进制10)。
例2.14同时使用数组下标和位下标
initial begin
bit [31:0] src[5]='{5{5}};
$display(src[0], , //'b101或'd5src[0][0], //'b1, src[0][2:1]); //'b10end
虽然这个变化并不是 Systemverilog新增加的,但可能有很多使用者并不知道Verilog-2001中的这个有用的改进。
2.2.6合并数组
对某些数据类型,你可能希望既可以把它作为一个整体来访问,也可以把它分解成更小的单元。例如,有一个32比特的寄存器,有时候希望把它看成四个8比特的数据,有时候则希望把它看成单个的无符号数据。 System Verilog的合并数组就可以实现这个功能,它既可以用作数组,也可以当成单独的数据。与非合并数组不同的是,它的存放方式是连续的比特集合,中间没有任何闲置的空间。
2.2.7合并数组的例子
声明合并数组时,合并的位和数组大小作为数据类型的一部分必须在变量名前面指定。数组大小定义的格式必须是[msb:lsb],而不是[size]。例2.15中的变量 bytes是一个有4个字节的合并数组,使用单独的32比特的字来存放,如图2.2所示。
例2.15合并数组的声明和用法
bit [3:0] [7:0] bytes;//4个字节组成32比特
bytes=32'hCafe_Dada;$display(bytes , , //显示所有的32比特bytes[3], , //最高的字节"CA"bytes[3][7]);//最高比特位"1"
合并和非合并数组可以混合使用。你可能会使用数组来表示存储单元,这些单元可以按比特、字节或长字的方式进行存取。在例2.16中, barry是一个具有3个合并元素的非合并数组。
例2.16合并/非合并混合数组的声明
bit [3:0][7:0] barray[3];//合并:3*32比特
bit [31:0] lw=32'0123_4567;//字
bit [7:0] [3:0] nibbles;//合并数组
barray[0]=lw;
barray[0][3]=8'h01;
barry[0][1][6]=1'b1;
nibbles=barray[0];//赋值合并数组的元素值
例2.15中的变量 bytes是一个具有4个字节的合并数组,以单字形式存放。 barray则是一个具有3个类似 bytes元素的数组,其存放形式如图2.3所示。
使用一个下标,可以得到一个字的数据 barray[2]。使用两个下标,可以得到一个字节的数据 barray[3]。使用三个下标,可以访问到单个比特位 barray[6]。注意数组声明中在变量名后面指定了数组的大小, barray[3],这个维度是非合并的,所以在使用该数组时至少要有一个下标。
例2.16中的最后一行在两个合并数组间实现复制。由于操作是以比特为单位进行的,所以即使数组维度不同也可以进行复制。
2.2.8合并数组和非合并数组的选择
究竟应该选择合并数组还是非合并数组呢?当你需要和标量进行相互转换时,使用合并数组会非常方便。例如,你可能需要以字节或字为单位对存储单元进行操作。图2.3中所示的 barry可以满足这一要求。任何数组类型都可以合并,包括动态数组、队列和关联数组,2.3~2.5节中会有进一步的介绍。
如果你需要等待数组中的变化,则必须使用合并数组。当测试平台需要通过存储器数据的变化来唤醒时,你会想到使用操作符。但这个操作符只能用于标量或者合并数组。在例2.16中,你可以把lw和 barray[0]用作敏感信号,但却不能用整个的 barra数组除非把它扩展成:@( barray[0] or barray[1] or barray[2])。