前言: SV中常用的数组包括定长数组、动态数组、队列、关联数组。
定长数组包括非组合型数组和组合型数组,声明完后数组的长度无法改变。
对于Verilog,数组通常被用来做数据存储。如
reg [ 15:0 ] RAM [ 0 : 125] ;
wire [7:0] table [3:0] ;
eg:
int a1 [7:0] [255 : 0] ;//二维数组a1,高维在左
int a2 [1:8] [1 :256] ;//二维数组a2,高维在左
a2 = a1 ;
a2 [3] = a1 [0] ; //将a2 第三位256个元素copy给a1第0位256个元素。
非组合行数组声明方式
eg:
logic [31:0] data [1024] ;//(data)名字在左边
logic [31:0] data [0:1023] ;
组合型数组声明方式
组合型数组于Verilog中向量的声明方式相同,并且允许多维组合型数组的声明。
eg:
wire [3:0] select ;
reg [63:0] data ;
logic [3:0] [7:0] data ;//(data)名字在右边
bit [3:0] [7:0] data2 ;
在这需要注意一点,上述data和data2都是4×8位的。当存放到软件中需要的word是不一样的,原因在于bit是二值逻辑, logic是四值逻辑,存放时所需空间是bit的两倍。
组合型数组可以使用结构体的方式存储
eg:
typedef struct packed{
logic [7:0] crc;
logic [63:0] data;
} data_word; //到这位为止data_word的位数为8+64,可直接与logic [71:0] data;进行赋值
data_word [7:0] darray;现在data_word的位宽为72*8
Tips:在下面这个数组里高低维怎么区分呢?先看右边再看左边,右边是45 左边是23,合起来是 452*3 ,顺序千万不要记错。
int [1:0][2:0] arr[3:0][ 4:0] ;
组合型数组的初始化: 直接采用向量赋值方式。
logic [3:0] [7:0] a = 32'h0; //32位值是0
logic [3:0] [7:0] b = {16'hz,16'h0}; //通过‘{}’将两个向量拼接
logic [3:0] [7:0] c = {16{2'b01}}; //16个重复的01组成
非组合型数组初始化: 必须单引号+花括号。
int d [0:1] [0:3] = '{ '{7,3,0,5} '{2,0,1,6} };
// d [0][0] =7
// d [0][1] =3
...
// d [1][2] =2
// d [1][4] =6
...
组合型数组赋值:
logic [1:0][1:0][7:0] a ; //三维数组,可以直接向量赋值
a[1][1][0] = 1'b0;
a = 32'hF1A3C5E7;
a[1][0][3:0] = 4'hF;
非组合型数组的赋值:
int d [0:7] [0:1023] = { 'default: 8'h55 }; 通过defaul关键词添加默认值
byte a [0:3] [0:3] ;
a [1][0] = 8'h5;
a [3] = ' { ' hF,' hA, 'hC, ' hE}; //高纬度的3里边有4个元素,分别给值。
组合型数组拷贝两个数组维度大小不同也可以赋值,截取或者扩展右侧操作数的方式来赋值。
非组合型数组拷贝两个数组维度和大小必须严格一致。
通过foreach循环来对数组进行循环索引,并且不需要指定数组的维度大小。这短短的一句话还是不足以给初学者讲清楚foreach是干嘛的,可以结合下面的例子做一个简单了解。
/*foreach循环结构中的变量无需声明,但这个变量是只读的,作用域只在这个循环结构中(只能引用不可以修改)。*/
int sum [1:8][1:3] ;
foreach ( sum[i,j] ) //i高维,j低维
sum [i][j] = i + j;s//sum[0][0] = 0,sum[0][1] = 1.....
//在这个例子中不难看出通过foreach可以很方便实现数组sum的初始化,还可以通过foreach把数组里的每一个元素都显示出来。
通过下面的例子来充分了解动态数组。
int dyn[ ] , d2[ ] ; //声明了两个动态数组
initial begin
dyn = new [5]; // 分配了5个元素,此时并没有赋值,此时5个元素值是默认值为0
foreach (dyn[j])
dyn[j] = j; //初始化数组,dyn[0] = 0, dyn[1] = 1 .......
d2 = dyn;//dyn有5个元素,此时d2也有五个元素
d2[0] = 5;//将0赋值给5
$display(dyn[0] , d2[0] ) ;//两个空间5个元素互相独立,只是发生了值的拷贝。dyn[0] = 0,d2[0] = 5
dyn = new [20]; //重新开辟20个空间,之前的值就不存在了。
dyn = new [20] (dyn);//开辟空间的同时,把之前5个元素拷贝给了新开辟的空间,这5个元素在新空间所对应的位置是前5个。
dyn = new[100] ;
dyn.delete(); //清空动态数组,释放空间
end
上述几点在下面的例子中可以做一个简单了解。
int j = 1,
q2 [$] = {3,4},//声明bit型的队列,同时做了赋值,此时赋值没有单引号
q[$] = {0,2,3};//对于q队列,q[0] = 0, q[1] = 2,q[2] = 3.
initial begin
q.insert(1,j);//在q[1]位置上插入j。此时q队列为{0, 1, 2, 3},需要注意的是队列中不可以插入队列即便插入的队列只有一个元素,例如q.insert(1,q2);是不可以的。
q.delete(1);//并不是删除数字1,而是删除队列q在1位置上的数据。==队列可以删除某一个位置,动态数组不可以==
/*push往进写一个值,pop往出拿一个值。front在队列整个队伍前,back在整个队伍后,以q为例,{front, 0, 2, 3, back}*/
q.push_front(6);// {6, 0, 2, 3} 等同于q = {6,q};
j = q.pop_back;//尾部的值被拿走,j = 3,队列变为{6, 0, 2}
q.push_back(8);//{6, 0, 2,8}
j = q.pop_front;//{0, 2,8} , j = 6
foreach(q[i])
$display(q[i]) ;
q.delete(); //等同于q = { };
end
module tb1;
initial begin
bit[31:0] mem [int unsigned] ;//mem是数组名,mem左边是索引值(数据)的类型,右边为索引(地址)的类型。int unsigned,表示int型无符号。
int unsigned data ,addr ; //声明数据data,地址addr
//关联数组的储存
repeat (5) begin //下面的内容重复5次
std::randomize ( addr ,data) with {addr[31:8] == 0 ;addr[1:0] ==0; data inside { [1:10]}; }; //随机生成地址和数据并且添加了约束条件,在这里不必在意随机生成带约束条件数,关注关联数组即可
$display("address : "h%0x , data : 'h%0x" , addr, data);
mem[addr] = data; //addr在此处类似于idx,eg:q[2] = x ,其中2是idx,x是数据data;类似于这种。把此时生成的data存放在此时生成的addr地址处。eg:生成的addr是2 , data是3 。当索引mem[2]时结果为3。重复了5次,生成了5个随机的idx,和5个随机的data。
foreach(mem[idx]) //遍历随机生成的数组并打印
$display("mem address : ' h%0x , data : 'h%0x " , idx, mem[idx] ) ;
end
//关联数据的读取
if(mem.first(addr)) begin//拿到第一个地址
do
$display("mem address : 'h%0x, data : 'h%0x" , addr, mem[addr]);
while(mem.next(addr)) //拿到第一个地址后拿第二个地址,如果拿不到索引值,此时while(0)退出循环。
end
end
endmodule
在上述例子中索引的类型是int,当仿真使用的时候可能是字符串string等类型,但万剑不离其宗。
基本的数组缩减方法是把数组缩减为一个值,最常见的是sum,对数组中所有元素求和。
byte b[ $] = {2, 3, 4, 5};
int w ;
w = b.sum() ; //相加
w = b.product() ; //数组相乘
w = b.and() ; //与操作
对于非合并数组,可以使用数组定位方法,此时返回值是一个队列而不是一个值。
int f[6] = '{1,2,6,5,8,6};//数组
int d[] = '[2,4,5,6,10];//动态数组[]
int q[$] = {1,3,5,6};//队列[ $ ]
tq = q.min() ; //{1}
tq = d.max() ;//{10}
tq = f.unique();//{1,2,6,5,8}== 注意返回值是队列而不是一个值==
对于数组的概念、简单使用想必大家已经通透了,但在具体的仿真中应该如何选择呢?
温故而知新,可以为师矣。 留两道题加深印象。
对于bit word [2:0] [3:0]; 应该怎么赋值呢?
动态数组怎么声明?
队列怎么赋值?
参考原文链接:https://blog.csdn.net/SummerXRT/article/details/117155517
可能对您有帮助的参考:
System Verilog学习笔记—接口(interface )
https://blog.csdn.net/jackack/article/details/127215204?spm=1001.2014.3001.5501