数组
非组合型数组
-
对于verilog,数组经常会被用来做数据存储,例如:
reg [15:0] RAM [0:4095];
-
SV将Verilog这种声明数组的方式称之为非组合型声明,即数组中的成员之间存储数据都是相互独立的
-
例如:
wire [7:0] table [3:0];
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ZTYurDG-1692258845709)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230612145855217.png)]
-
-
SV保留了非组合型数组声明方式,并且拓展了允许的类型,包括
event
,logic
,bit
,byte
,int
,longint
,shortreal
,real
-
SV页保留了Verilog索引非组合型数组或者数组片段的能力,这种方式为数组及数组片段的拷贝带来了方便
-
声明,以下两种皆可
logic [31:0] data [1024]; logic [31:0] data [0:1023];
组合型数组
-
SV将Verilog的向量作为组合型数组声明方式
wire [3:0] select; reg [63:0] data;
-
SV也进一步允许多维组合型数组的声明
logic [3:0][7:0] data;
-
组合型数组会进一步规范数据的存储方式,而不需要关心编译或者操作系统的区别,数据是连续存放的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvctUmXu-1692258845710)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20230613142544788.png)]
-
组合型除了可以运用数组声明,也可以用来定义结构体的存储方式
typedef struct packed { logic [7:0] crc; logic [63:0] data; } data_word; // 结构体类型:data_word data_word[7:0] darray; // 数组中每个元素都是一个结构体
-
组合型数组和其它数组片段也可以灵活选择,用来拷贝和赋值
混合型数组
- 维度怎么看
- 例如:
int[1:0][2:0] arr [3:0][4:0]
- 先看
arr
的右边[3:0][4:0]
,维度从左至右,所以是4*5 - 再看
arr
的左边[1:0][2:0]
,维度从左至右,所以是2*3 - 所以总的维度:
4*5*2*3
- 先看
- 例如:
数组初始化
-
组合型(packed)数组初始化时,同向量初始化一致
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}};
-
非组合型(unpacked)数组初始化时,需要通过
'{}
来对数组的每个维度进行赋值int d [0:1][0:3] = '{'{7, 3, 0, 5}, '{2, 0, 1, 6}}; // 最终赋值结果: d[0][0] == 7 d[0][1] == 3 d[0][2] == 0 d[0][3] == 5 d[1][0] == 2 d[1][1] == 0 d[1][2] == 1 d[1][3] == 6
-
非组合型数组在初始化时,也可以类似结构体初始化,通过
'{}
和default
关键词完成int a1 [0:7][1:1023] = '{default:8'h55}; // 每个元素的值默认为16进制的55
-
非组合型数组的数据成员或数组本身均可以为其赋值
byte a [0:3][0:3]; a[1][0] = 8'h5; // 单个维度赋值 a[3] = '{'hF, 'hA, 'hC, 'hE}; // 低维集体赋值
数组的拷贝
- 对于组合型数组,由于数组会被视为向量,因此当赋值左右的大小和唯独不同时,也可以做赋值
- 当尺寸不相同时,会通过截取或者拓展右侧操作数的方式(高位被拓展为0或者高位被截取)来对左侧操作数赋值
- 对于非组合型数组,在发生数组间拷贝时,要求左右两侧的维度和大小必须严格一致
- 非组合型数组也无法直接赋值给组合型数组,组合型数组也无法直接赋值给非组合型数组
数组的循环
foreach循环
-
SV添加foreach循环来对一维或者多维数组进行循环索引,而不需要指定该数组的维度大小
int a[4:0][4:0]; foreach (a[i, j]) begin; a[i][j] = i+j; $display("i is %0d, j is %0d, a is %0d", i,j,a[i][j]); end
-
foreach循环结构中的变量无需声明
-
foreach循环结构中的变量是只读的,其作用域在此循环结构中有效
系统函数
-
$dimensions(array_name)
用来返回数组的维度 -
$left(arry_name,dimension)
返回指定维度的最左索引值(msb)logic [1:2][7:0] word [0:3][4:1]; $left(word,1) // 返回:0 $left(word,2) // 返回:4 $left(word,3) // 返回:1 $left(word,4) // 返回:7
-
于
$left()
类似的还有$right(array_name, dimension)
、$low(array_name, dimension)
、$high(array_name, dimension)
-
$size(array_name, dimension)
可以返回指定维度的尺寸大小 -
$increment(array_name, dimension)
,如果指定维度的最左索引大于或等于最右索引,返回1,否则返回-1 -
$bits(expression)
可以返回数组存储的比特数wire [3:0][7:0] a [0:15]; $bits(a); // 返回512(4*8*16) struct packed { byte tag; logic [31:0] addr; } b; $bits(b); // 返回40,tag占8个bit,addr占32个bit
动态数组
-
与之前定长数组相比,SV还提供了可以重新确定大小的动态数组
-
动态数组在声明时需要使用[],这表示不会再编译时为其指定尺寸,而是在仿真运行时来确定
-
动态数组一开始为空,需要使用new[]来为其分配空间
int dyn[],d2[]; // 声明动态数组的时候为空 initial begin dyn = new[5]; // 使用new分配空间,5个元素值默认为0 foreach (dyn[j]) // 使用循环,将索引值赋值给数组内的元素 dyn[j] = j; d2 = dyn; // 将一个数组拷贝给另一个数组,d2也是五个元素了 d2[0] = 5; // d2[0]原来是0,重新赋值为5,syn[0]还是为0 $dispaly(dyn[0], d2[0]); // 打印结果:0,5 dyn = new[20](dyn); // 重新给动态数组分配20个元素的空间,每个元素都为0,再将原来dyn数组的元素值赋值给dyn的前5个元素 dyn = new[100]; // 重新给动态数组分配100个元素的空间,每个元素都为0 dyn.delete(); // 清空一个动态数组 end
-
内建方法size()可以返回动态数组的大小(元素个数)
-
内建方法delete()可以清空动态数组,使其尺寸变为0
-
动态数组在声明时也可以完成其初始化
bit [7:0] mask[] = '{ 8'b0000_0000, 8'b0000_0001, 8'b0000_0011, 8'b0000_0111, 8'b0000_1111, 8'b0001_1111, 8'b0011_1111, 8'b0111_1111, 8'b1111_1111 };
队列
- SV引入了队列类型,结合了数组和链表
- 可以在队列的任何位置添加或删除数据成员
- 可以通过索引来访问队列的任何成员
- 通过
[$]
来声明队列,队列的索引值从0到$ - 可以通过内建方法push_back(val)、push_front(val)、pop_back()和pop_front()来顺序添加或者移除并且获得数据成员
- 可以通过insert(pos,val)来指定位置插入数据成员
- 可以通过delete()来删除所有数据成员
int j = 1;
int q2[$] = {3,4}; // 声明一个队列q2,队列中有两个元素,元素类型为int
int q[$] = {0,2,3}; // 声明一个队列q,队列中有三个元素,元素类型为int
initial begin
q.insert(1, j); // 在队列q索引为1的位置插入元素i,队列变为{0,1,2,3}
q.delete(1); // 删除队列中索引为1的数据,队列变为{0,2,3}
q.push_front(6);// 将6插入到队列的索引为0的位置,队列变为{6,0,2,3}
j = q.pop_back(); // 从队列q尾部,索引为$的位置取出数据并赋值给j,队列变为{6,0,2}
q.push_back(8); // 将8插入到队列最后索引为$的位置,队列变为{6,0,2,8}
j = q.pop_front(); // 从队列q前面,索引为0的位置取出数据并赋值给j,队列变为{0,2,8}
foreach (q[i]) // 使用foreach循环获取队列元素
$dispaly(q[i]);
q.delete(); // 清空队列
q = q2; // 将q2拷贝给q
end
int j = 1,
q2[$] = {3,4},
q[$] = {0,2,5};
initial begin
q = {q[0],j,q[1:$]}; // 将q索引为0的元素,j,q索引1到最后一个元素拼接在一起,组成一个新的队列赋值给q,q={0,1,2,5}
q = {q[0:2],q2,q[3:4]}; // q={0,1,2,3,4,5}
q = {q[0],q[2:$]}; // q={0,2,3,4,5}
q = {6,q}; // q={6,0,2,3,4,5}
j = q[$];
q = q[0:$-1]; // 这两句合在一起等价于j=q.pop_back()
q = {}; // 等价于q.delete()
end
关联数组
其它语言中,也有叫HASH/Dict,哈希/字典
-
如果想要在仿真时创建一个大数组,也许动态数组是一个选择,不过有时候我们并不需要这么大的数组
-
由于处理器在访问存储时的访问时随机散乱的,这意味在整个测试中,处理器也许只会访问几百个存储地址,而剩下大多数地址都将被初始化为0,浪费了仿真时的存储空间
-
SV引入了关联数组,可以用来存放散列的数据成员,这一点于其它脚本语言Perl或python类似,散列的索引类型除了整形以外,还可以为字符串或者其他类型,而散列存储的数据成员也可以为任意类型
bit [128:0] assoc[longint]; // 声明一个关联数组,数组名:assoc,索引类型:longint,值存储类型:bit[128:0]
longint idx = 1; // 初始idx=1
repeat (60) begin // 循环60次,受数据类型长度限制,64次循环,索引会出现内存溢出
assoc[idx] = idx; // assoc赋值
idx = idx << 1; // idx位运算,等价于idx*2
end
foreach (assoc[i]) // foreach循环,i依次为每个索引
$display("assoc[%0d] = %0d",i,assoc[i]);
if (assoc.first(idx)) // 取出第一个索引,assoc.first(idx)返回值:1,idx值也是1,不过一个是int,一个是longint
do
$display("assoc[%0d] = %0d", idx, assoc[idx]);
while (assoc.next(idx));
数组方法
缩减方法
-
基本的数组缩减方法是把一个数组缩减成一个值
-
最常用的缩减方法是sum,它对数组中的所有元素求和
byte b[$] = {2,3,4,5}; int w; w = b.sum(); // w = 2+3+4+5 w = b.product(); // w = 2*3*4*5 w = b.and(); // w = 2&3&4&5=0000_0010& // 0000_0011& // 0000_0100& // 0000_0101 = 0000_0000
-
其它数组缩减方法还有product(积)、and(与)、or(或)、xor(异或)
定位方法
-
对于非合并数组,可以使用数组定位方法,其返回值将是一个队列而非一个数据成员
int f[6] = '{1,6,2,6,8,6}; int d[] = '{2,4,6,8,10}; int q[$] = {1,3,5,7}; int tq[$]; tq = q.main(); // tq = {1} q的最小值 tq = d.max(); // tq = {10} d的最大值 tq = f.unique();// tq = {1,6,2,8} f去重
-
foreach可以实现数组的搜索,不过find…with查找满足条件的数据成员更方便
int d[] = '{9,1,8,3,4,4}; int tq[$]; tq = d.find with (item > 3); // 将大于3的数据组成一个队列赋值给tq,tq={9,8,44} // 等价于下面循环 foreach (d[i]) if (d[i] > 3) tq.push_back(d[i]); tq = d.find_index with (item > 3); // 将大于3的数据的索引组成队列赋值给tq,tq={0,2,4,5} tq = d.find_first with (item > 7); // 将大于7的第一个数据组成队列赋值给tq,tq={9} tq = d.find_first_index with (item==8); // 将第一个等于4的数据的索引组成队列赋值给tq,tq={4} tq = d.find_last with (item==4); // tq={4} tq = d.find_last_index with (item==4); // tq={5}
排序方法
-
可以通过排序方法改变数组中元素的顺序,可以对她们进行逆向或者乱序的排列
int d[] = '{9,1,8,3,4,4}; d.reverse(); // d = '{4,4,3,8,1,9} 反转 d.sort(); // d = '{1,3,4,4,8,9} 顺序排列 d.rsort(); // d = '{9,8,4,4,3,1} 倒序排列 d.shuffle(); // d = '{9,4,3,8,1,4} 打乱顺序