保姆级超硬核包会,System Verilog SV数组

前言: SV中常用的数组包括定长数组、动态数组、队列、关联数组。

  • 定长数组:大小不可以改变。
  • 动态数组:元素、空间可以重新分配。
  • 队列:可以在任何位置添加或删除数据成员。
  • 关联数组:索引可以为任何类型,存放的数据在软件的空间里可以没有任何关系。

1. 定长数组

1.1 定数组的简单概念

定长数组包括非组合型数组和组合型数组,声明完后数组的长度无法改变
对于Verilog,数组通常被用来做数据存储。如

reg [ 15:0 ]  RAM [ 0 : 125] ;
wire [7:0] table [3:0] ;
  • SV将上述数组声明的方式成为非组合型数组的声明,每个成员存储数据相互独立。
  • 并保留了非组合型数组的声明方式,并且扩展了类型像event、logic、byte、int、longint、shortreak和real都可以使用。
  • 保留了Verilog索引非组合型数组或者数组片段的能力。

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个元素。

1.2 定长数组的声明方式

非组合行数组声明方式
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] ;

1.3 数组的初始化、赋值和拷贝

组合型数组的初始化: 直接采用向量赋值方式。

	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个元素,分别给值。

组合型数组拷贝两个数组维度大小不同也可以赋值,截取或者扩展右侧操作数的方式来赋值。
非组合型数组拷贝两个数组维度和大小必须严格一致。

1.4 foreach循环结构

通过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把数组里的每一个元素都显示出来。

2. 动态数组

  • 动态数组的声明需要使用 [ ] ,这表示不会在编译的时候规定它的尺寸,而是仿真运行时确定。
  • 动态数组起始状态为空(无初始化,无赋值),需要使用new[ ]分配空间(new( )表示调用函数)。
  • 动态数组可以在声明时完成初始化。

通过下面的例子来充分了解动态数组。

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

3. 队列

  • SV中队列结合了数组和链表的功能。
  • 可以在任何位置添加或删除数据成员。
  • 可以通过索引来访问队列的任何一个成员。
  • 通过[$]来声明队列,队列的索引值从0到 $。(最后一个元素为 $ )
  • 通过内建方法push_buck(val)、 push_front(val)、 pop_back()和pop_front()来顺序添加或者移除成员,通过insert(pos,val)在指定位置插入数据成员,通过delete() 来删除所有数据成员。

上述几点在下面的例子中可以做一个简单了解。

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

4. 关联数组

  • 在别的语言里又叫做HASH/dictionary 。
  • 当对一个非常大的地址空间进行寻址时,SV只为实际写入的元素分配空间,这样可以节省空间。
  • 索引可以为任何类型。
  • 存放的数据在软件的空间里可以没有任何关系。
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等类型,但万剑不离其宗。

5. 数组中常见的方法

5.1 缩减方法

基本的数组缩减方法是把数组缩减为一个值,最常见的是sum,对数组中所有元素求和。

byte b[ $]  = {2, 3, 4, 5};
	int w ;
	w = b.sum() ; //相加
	w = b.product() ; //数组相乘
	w = b.and() ; //与操作

5.2 定位方法

对于非合并数组,可以使用数组定位方法,此时返回值是一个队列而不是一个值。

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}== 注意返回值是队列而不是一个值==

6. 选择存储方式

对于数组的概念、简单使用想必大家已经通透了,但在具体的仿真中应该如何选择呢?

  • 如果元素一次性全部加入,选择定长或者动态数组。
  • 只需要对数组进行一次分配。 如果元素一个一个加入,选择队列。
  • 如果数组的值不连续且互异,可以使用关联数组,把元素值本身作为索引。

温故而知新,可以为师矣。 留两道题加深印象。

  • 对于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

你可能感兴趣的:(System,Verilog,学习)