本文是参考绿皮书进行总结而写。
和verilog相比system verilog引进了一些新的数据类型,他们具有如下优点。
(1)双状态数据类型:更好的性能,更低的内存消耗
(2)队列,动态和关联数组:减少内存消耗,自带搜索和分类功能。
(3)类和结构:支持抽象数据结构。
(4)联合和合并结构:允许对同一数据有多种视图
(5)字符串:支持内建的字符序列。
(6)枚举类型:方便代码编写,增加可读性
任何使用线网(reg和wire)的地方均可以使用logic,但要求logic不能有多个结构性的驱动,例如在对双向总线建模的时候。此时,需要使用线网类型,例如wire。
使用$isunknown
操作符,可以在表达式的任意位出现X或Z时返回1。
verilog要求在声明中必须给出数组的上下界,systemverilog允许只给出数组宽度的便捷声明方式,跟C语言类似,如下所示:
int lo_hi[0:15]; //16个整数
int sv_style[16]; //16个整数
多维定宽数组的声明和使用
int array2 [0:7] [0:3]; //完整的声明
int array3 [8][4]; //紧凑的声明
array2[7][3] = 1; //设置最后一个元素
如果你的代码试图从一个越界的地址中读取数据,那么system verilog将返回数组元素类型的缺省值。也就是说,对于一个元素为四状态类型的数组,例如logic,返回的是X,而对于双状态类型例如int或bit,则返回0。这适用于所有数组类型。
很多system verilog仿真器在存放数组元素时使用32bit的字边界,所以byte,shortint和int都是存放在一个字中,而longint则存放在两个字中。
初始化一个数组,即一个单引号加大括号来初始化数组,可以一次性的为数组的部分或所有元素赋值。在大括号前标上重复次数可以对多个元素重复赋值,还可以为那些没有显式赋值的元素指定一个缺省值。如下所示:
int ascend [4] = '{0,1,2,3}; //对四个元素进行初始化
int descend [5];
descend = '{4,3,2,1,0}; //为五个元素赋值
descend [0:2] = '{5,6,7}; //为前三个元素赋值
ascend = '{4{8}}; //四个值全部为8
descend = '{9,8,default:1}; //{9,8,1,1,1}
在foreach循环中,只需要指定数组名并在其后面的方括号中给出索引变量,system verilog便会自动遍历数组中的元素。如下例所示:
initial begin
bit [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
对多维数组使用foreach的语法可能会与你设想的有所不同。使用时是用逗号隔离开放在同一个方括号里,像[i,j]。代码如下所示:
int md[2][3] = '{'{0,1,2},'{3,4,5}};
initial begin
$display("Initial value:");
foreach (md[i,j]) //正确语法格式
$display ("md[%0d][%0d]=%0d",i,j,md[i][j]);
使用格式符%0t和参数$time可以打印出当前的仿真时间。
$display("@%0t",$time);
$write
与$display
几乎相同,差异仅在于后者缺省第带换行符,而前者不带。所以当你需要将多次调用的语句打印的内容显示在同一行,那就是应该用$write的地方。
你可以在不使用循环的情况下对数组进行聚合比较和复制(聚合操作适用于整个数组而不是单个元素),其中比较只限于比较等于或不等于。操作符?:类似与if语句,代码如下所示:
initial begin
bit[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所有元素值复制给dst
dst = src;
//所有元素的值是否相等(否!)
$display("src[1:4] %s dst[1:4]",(src[1:4] == dst[1:4])? "==" : "!=");
end
initial begin
bit[31:0] src [5] = '{5{5}};
$display (src[0],, //'b101或'd5
src[0][0],, //'b1
src[0][2:1] //'b10
end
verilog也支持此种用法。
对某些数据类型,你可能希望既可以把他作为一个整体来访问,也可以把它分解成更小的单元。system verilog的合并数组就可以实现这个功能,它既可以用作数组,也可以当成单独的数据。与非合并数组不同的是,它的存放方式是连续的bit集合,中间没有任何闲置的空间。
声明合并数组是,合并的位和数组大小作为数据类型的一部分必须在变量名前面指定。数组大小定义的格式必须是[msb:lsb],而不是[size]。代码如下所示:
bit [3:0][7:0] bytes; //四个字节组装成32bit
bytes = 32’hCafe_Data;
$display (bytes,, //显示所有的32bit
bytes[3],, //最高字节“Ca”
bytes[3][7]); //最高bit“1”
bit [3:0] [7:0] barray [3]; //合并:3x32bit
bit[31:0] lw = 32'h0123_4567; //字
bit[7:0][3:0] nibbles; //合并数组
barray[0] = lw;
barray[0][3] = 8'h01;
barrary[0][1][6] = 1'b1;
nibbles = barray[0]; //复制合并数组的元素值
system verilog提供了动态数组类型,可以在仿真时分配空间或调整宽度,这样在仿真中就可以使用最小的存储量。
动态数组在声明时使用空的下标[]。数组在最开始时是空的,所以必须调用new[]操作符来分配空间,同时在方括号中传递数组宽度。可以把数组名传给new[]构造符,并把已有数组的值复制到新数组里。
只要基本数据类型相同,定宽数组和动态数组之间就可以相互赋值。在元素数目相同的情况下,可以把动态数组的值复制到定宽数组。
当你把一个定宽数组复制给一个动态数组时,system verilog会调用构造函数new[]来分配并复制数值。
system verilog引进了一种新的数据类型——队列,他结合了链表和数组的优点。可以在一个队列中的任何地方增加或删除元素,这类操作在性能上的损失比动态数组小得多。
队列的声明是使用带有美元符号的下标:[$]。队列元素的编号从0到$。代码如下所示:
int j = 1,
q2[$] = {3,4},
q[$] = {0,2,5};
initial begin
q.insert(1,j); //{0,1,2,5} 在2之前插入1
q.insert(3,q2); //{0,1,2,3,4,5} 在q中插入一个队列
q.delete(1); //{0,2,3,4,5} 删除第1个元素
//下面的操作执行速度很快
q.push_front(6); //{6,0,2,3,4,5} 在队列前面插入
j = q.pop_back; //{6,0,2,3,4} j = 5
q.push_back(8); //(6,0,2,3,4,8} 在队列末尾插入
j = q.pop_front; //{0,2,3,4,8} j = 6
foreach(q[i])
$display(q[i]); //打印整个队列
q.delete(); //{}删除整个队列
end
如果把$放在一个范围表达式的左边,那么$将代表最小值,反之,则代表最大值。队列操作如下:
int j =1,
q2[$] = {3,4},
q[$] = {0,2,5};
initial begin
q = {q[0],j,q[1:$]}; //{0,1,2,5}
//下面的操作执行速度很快
q = {6,q}; //{6,0,2,3,4,5}
j = q[$]; //j = 5
q = q[0:$-1]; //{6,0,2,3,4}
q = {q,8}; //{6,0,2,3,4,8}
j = q[0]; //j = 6
q = q[1:$]; //{0,2,3,4,8}
q = {}; //{}
end
队列中的元素是连续存放的,所以在队列的前面或后面存取数据非常方便。无论队列有多大,这种操作所耗费的时间都是一样的。在队列中间增加或删除元素需要对已经存在的数据进行搬移以便腾出空间。相应操作所耗费的时间会随着队列的大小线性增加。
system verilog提供了关联数组类型,用来保存稀疏矩阵(矩阵中非零元素的个数远远小于矩阵总数)的元素。这意味着当你对一个非常大的地址空间进行寻址时,system verilog只为实际写入的元素分配空间。如下图所示,关联数组只保留0,3,42,1000,4521和200000等位置上的值。
关联数组采用在方括号中放置数据类型的形式来进行声明,例如[int]或[packet]。关联数组的声明,初始化和使用如下代码所示:
initial begin
bit [63:0] assoc[bit[63:0]],idx = 1;
//对稀疏分布的元素进行初始化
repeat(64) begin
assoc[idx] = idx;
idx = idx << 1;
end
//使用foreach遍历数组
foreach (assoc[i])
$display("assoc[%h] = %h",i,assoc[i]);
//使用函数遍历数组
if(assoc.first(idx)) begin //得到第一个索引
do
$display("assoc[%h] = %h",idx,assoc[idx]);
while(assoc.next(idx)); //得到下一个索引
end
//找到并删除第一个元素
assoc.first(idx);
assoc.delete(idx);
$display("The array now has %0d elements",assoc.num);
end
使用带字符串索引的关联数组
/*输入文件的内容如下:
42 min_address
1492 max_address
*/
int switch[string],min_address,max_address;
initial begin
int i,r,file;
string s;
file = $fopen("switch.txt","r");
while (!$feof(file))begin
r = $fscanf(file,"%d %s",i,s);
switch[s] = i;
end
$fclose(file);
//获取最小地址值,缺省为0
min_address = switch["min_address"];
//获取最大地址值,缺省值为1000
if(switch.exists("max_address"));
max_address = switch["max_address"];
else
max_address = 1000;
//打印数组的所有元素
foreach (switch[s])
$display("switch['%s']=%0d",s,switch[s]);
end
补充:verilog 文件操作方法请参考下方链接。
链接: verilog文件操作方法
数组的缩减方法有sum(和),product(积),and(与),or(或)和xor(异或)。
bit on[10]; //单bit数组
int total;
initial begin
foreach (on[i])
on[i] = i;
//打印出单bit和
$display("on.sum = %0d",on.sum); //on.sum = 1
//打印出32bit和
$display("on.sum = %0d",on.sum + 32'd0); //on.sum = 5
//由于total是32bit变量,所以数组和也是32bit
total = on.sum;
$display("total = %0d",total); //total = 5
//使用带32bit有符号运算的with表达式
$display("int sum = %0d",on.sum with (int ' (item)));
end
从一个关联数组中随机选取一个元素
int aa [int],rand_idx,element,count;
element = $urandom_range(aa.size()-1);
foreach(aa[i])
if(count++ == element) begin
rand_idx = i; //保存关联数组的索引
break; //并退出
end
数组定位方法:min,max,unique
int f[6] = '{1,6,2,6,8,6};
int d[] = '{2,4,6,8,10};
int q[$] = {1,3,5,7},tq[$];
tq = q.min(); //{1}
tq = d.max(); //{10}
tq = f.unique(); //{1,6,2,8}
数组定位方法:find
int d[] = '{9,1,8,3,4,4},tq[$];
//找出所有大于3的元素
tq = d.find with (item > 3); //{9,8,4,4}
//等效代码
tq.delete();
foreach (d[i])
if(d[i]>3)
tq.push_back(d[i]);
tq = d.find_index with (item > 3); //{0,2,4,5}
tq = d.find_first with (item > 99); //{}没有找到
tq = d.find_first_index with (item == 8); //{2} d[2] = 8
tq = d.find_last with (item == 4); //{4}
tq = d.find_last_index with (item == 4); //{5} d[5] = 4
在条件语句with中,item被称为重复参数,它代表了数组中一个单独的元素。item是缺省的名字,你也可以指定别的名字。如下:
tq = d.find_first(x) with (x == 4);
当把数组缩减方法与条件语句with结合使用时,要注意操作符的结果是条件表达式为真的次数,代码如下所示:
int count,total,d[] = '{9,1,8,3,4,4};
count = d.sum with (item > 7); //2:{9,8}
total = d.sum with ((item > 7) * item); //17 = 9 + 8
count = d.sum with (item < 8); //4:{1,3,4,4}
total = d.sum with (item < 8 ? item : 0); //12 = 1+3+4+4
count = d.sum with (item == 4); //2:{4,4}
对数组排序
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}乱序
reverse和shuffle方法不能带with条件语句,所以他们的作用范围是整个数组。
对结构数组进行排序
struct packed {byte red,green,blue;} c[];
initial begin
c = new[100]; //分配100个像素
foreach(c[i])
c[i] = $urandom; //填上随机数
c.sort with (item.red); //只对红色(red)像素进行排序
//先对绿色像素后对蓝色像素进行排序
c.sort(x) with ({x.green,x.blue});
end
带数组方法的记分板
typedef struct packed
{bit [7:0] addr;
bit [7:0] pr;
bit [15:0] data;} Packet;
Packet scb[$];
function void check_addr(bit[7:0] addr);
int intq[$];
intq = scb.find_index() with (item.addr == addr);
case (intq.size())
0:$display("Addr %h not found in scoreboard",addr);
1:scb.delete(intq[0]);
default:
$display("ERROR:Multiple hits for addr %h",addr);
endcase
endfunction : check_addr
typedef语句可以用来创建新的类型。
system verilog中用户自定义类型
parameter OPSIZE = 8;
typedef reg [OPSIZE-1:0] opreg_t;
opreg_t op_a,op_b;
一般来说,即使数据位宽不匹配,例如值被扩展或截断,system verilog都允许这些基本类型之间进行复制而不会给出警告。
uint 的定义
typedef bit [31:0] uint; //32bit双状态无符号数
用户自定义数组类型
typedef int fixed_array5[5];
fixed_array5 f5;
initial begin
foreach (f5[i])
f5[i] = i;
end
创建一个pixel类型
struct {bit[7:0] r,g,b;} pixel;
上述代码只是声明创建了一个pixel变量,要想在端口和程序中共享它,则必须创建以恶搞新的类型,如下代码所示:
typedef struct {bit[7:0] r,g,b;} pixel_s; pixel_s my_pixel;
使用typedef创建联合
typedef union {int i;real f;}num_u;
num_u un;
un.f = 0.0; //把数值设为浮点形式
枚举创建了一种强大的变量类型,它仅限于一些特定名称的集合,例如指令中的操作码或者状态机中的状态名。
//创建代表0,1,2的数据类型
typedef enum{INIT,DECODE,IDLE} fsmstate_e;
fsmstate_e pstate,nstate; //声明自定义类型变量
initial begin
case(pstate)
IDLE:nstate = INIT; //数据赋值
INIT:nstate = DECODE;
default:nstate = IDLE;
endcase
$display("Next state is %s",nstate.name()); //显示状态的符号名
end
枚举值缺省为从0开始递增的整数。
system verilog提供了一些可以遍历枚举类型的函数:
(1)first()返回第一个枚举常量。
(2)last()返回最后一个枚举常量。
(3)next()返回下一个枚举常量。
(4)next(N)返回以后第N个枚举常量。
(5)prev()返回前一个枚举变量。
(6)prev(N)返回以前第N个枚举变量。
当到达枚举常量列表的头或尾时,函数next和prev会自动以环形方式绕回。
枚举类型的缺省类型为双状态int。可以使用简单的赋值表达式把枚举变量的值直接赋值给非枚举变量如int。
c a s t ( ) 函数可以把其右边的值赋给左边的量。如果赋值成功, cast()函数可以把其右边的值赋给左边的量。如果赋值成功, cast()函数可以把其右边的值赋给左边的量。如果赋值成功,cast()返回1。如果因为数值越界而导致赋值失败,则不进行任何赋值,函数返回0 。