SystemVerilog学习笔记(会持续更新~)
文章目录
SV数据类型
数据类型 | 2值逻辑/4值逻辑 | 默认值 | 是否有符号数 |
---|---|---|---|
logic | 4 | x | 无符号 |
bit | 2 | 0 | 无符号 |
byte | 2 | 0 | 有符号 |
shortint | 2 | 0 | 有符号 |
int | 2 | 0 | 有符号 |
longint | 2 | 0 | 有符号 |
固定数组
固定数组的声明格式
固定数组的格式声明为:Type name [constant] , 其中 type 为存储数据的类型。
例如: int array[16], array为数组的名字,int为数组数据的存储类型为int型数据,16为数组的大小,共16个元素。
一维数组与二维数组
一维数组例如:int array[16];
//array为数组名字,数组元素共16个,索引为0…15,存储的数据类型为int型。这里数组的索引为 0…15,索引只有一个维度。
二维数组例如:int array[8] [4]
//array为数组的名字,int为数组数据的存储类型为int型数据,数组的大小为8行4列,共32个元素。
固定数组的一些操作
为数组初始化,使用赋值符号:'{ } 和 '{n{ }}
例如:
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 {8,8,8,8}
descend = ‘{9, 8, default:-1}; // {9,8, -1, -1, -1}
遍历数组常用方法之一为for循环
例如:
initial begin
bit [31:0] src[5],dst[5];
for (int i=0; i<$size(src); i++)
src[i] = i;
end
注:
- i变量为for循环内部的局部变量;
- 在SV中,$size函数返回数组的宽度;
- 当然可以不使用$size函数,可以直接使用数组的最大维度,上例中为4**(索引为0~4)**;
遍历数组常用方法foreach操作
例如:
initial begin
bit [31:0] src[5],dst[5];
foreach (dst[j])
dst[j] = src[j]* 2; // dst doubles src values
end
多维数组的赋值
例如定义了一个多维数组:logic [3:0] [7:0] array [0:3] [0:3] … [0:3],那么如何引用数组为其赋值呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pY0rlduS-1658909074932)(C:\Users\ad\AppData\Roaming\Typora\typora-user-images\image-20220723144607352.png)]
固定数组的注意事项:
-
数组可以是一维的,也可以是多维的
-
byte/short/int类型的数据,都是占据一个字(32bit)的存储空间
-
longint占据两个字(64bit)的存储空间
-
如果越界读取数组的话,则返回数组元素类型的缺省值:(越界指超出范围读取)
如果数组元素为4值逻辑,如logic,则返回x;
如果数组元素为2值逻辑,如int或者bit,则返回0
本规则适用于固定数组,动态数组,关联数组和队列,以及地址中含有x或z的情况。
-
如果越界写数组的话,则写数据被忽略.
动态数组
与固定数组不同的是,动态数组可以在仿真时分配空间或者调整宽度。
动态数组的声明使用空的[],数组的宽度不是在编译时给出,而是在程序运行时指定。
动态数组在最开始是空的,所以必须调用new[]操作符来分配空间,同时在方括号中传递数组宽度。
动态数组的声明格式
动态数组声明格式为:type name [ ] , 其中 type 为存储数据的类型,声明时[ ]为空。
例如: int array[ ], 其中array为数组的名字,int为数组数据的存储类型为int型数据。在使用数组之前一定要调用new[ ]函数为数组分配空间后才可使用。
例如:
int dyn[], d2[];// Declare dynamic arrays
initial begin
dyn = new[5]; //A: Allocate 5 elements
foreach (dyn[j]) dyn[j]= j; // B: Initialize the elements
d2 = dyn; // C: Copy a dynamicarray
d2[0] = 5; //D: Modify the copy
$display(dyn[0],d2[0]); // E: See both values(0 & 5)
dyn = new[20](dyn); // F: Allocate 20 ints& copy
dyn = new[100]; //G: Allocate 100 new ints
// Old values are lost
dyn.delete(); // H: Delete allelements
end
动态数组的一些操作
delete和size
例如:
int test[];
test=new[4]; //用new来创建数组
num=test.size(); //用size获取数组大小
test.delete(); //用delete删除数组
- 注:$size(test)同样可以获取数组的大小
关联数组
关联数组的声明格式
关联数组声明方式:data_type array_name[index_type],
例如 int array [string],其中int为关联数组存储数据的类型为int型数据,数组的索引为字符串索引。
关联数组的索引方式
-
通配符索引:任意数据类型进行索引:int array_name [*];
-
字符串索引:int array_name[string];
-
类索引:int array_name [some_Class];
-
integer(或int)索引:int array_name[integer];
-
有符号的压缩数组索引:typedef bit signed [4:1] Nibble; int array_name[Nibble];
-
无符号的压缩数组索引:typedef bit [4:1] Nibble; int array_name [Nibble];
-
其它用户定义类型索引:typedef struct { real R; int I[*]; } Unpkt; int array_name [Unpkt];
实际上,关联数组实现了一个所声明类型的元素的查找表。用作索引的数据类型作为查找表的查找键值,并强制了一种顺序。
例如:
integer i_array[*]; // 整数关联数组(未指定索引)
bit [20:0] array_b[string]; // 21位向量的关联数组,使用字符串类型作为索引
event ev_array[myClass]; // 事件类型的关联数组,使用类myClass索引
关联数组的一些操作
num() //返回数组长度
delete() //删除指定元素或者所有元素
exists() //检查是否元素存在,存在返回1,否则返回0
first() //将指定的索引变量赋值为数组第一个索引的值
last() //将指定的索引变量赋值为数组最后一个索引的值
next() //索引变量被赋值为下一个条目的索引
prev() //索引变量被赋值为上一个条目的索引
关联数组的注意事项
简单的for循环不能遍历关联数组,需要使用foreach遍历数组,还可以使用内建的first()和next()函数。
读取不存在的关联数组元素,4值逻辑返回x,2值逻辑返回0。
关联数组的索引可以是多种数据类型:string、int,甚至是class以及用户自定义的数据类型。
例如:
initial begin
bit [63:0] assoc[int],idx =1;
// Initialize widely scatteredvalues
repeat (64) begin
assoc[idx] =idx;
idx = idx << 1;
end
// Step through all index values withforeach
foreach (assoc[i])
$display("assoc[%h]= %h", i, assoc[i]);
// Step through all index values withfunctions
if (assoc.first(idx))
begin // Get first index
do
$display("assoc[%h]=%h",idx, assoc[idx]);
while (assoc.next(idx)); // Get next index
end// Find and delete the first element
assoc.first(idx);
assoc.delete(idx);
$display("The array now has %0delements", assoc.num);
end
结构体
结构体可以把不同类型的变量组合到一起,作为一个整体,可以单独访问结构体中的元素。
结构体的声明格式
结构体的声明方式
struct {
int a;
int b;
bit c;
} struct_name
结构体内引用变量的方式为“.”,即struct_name.a可以引用结构体内部的a变量。
结构体赋值
定义结构体的同时给结构体赋值
typedef struct {
int addr = 1 + constant;
int crc;
byte data [4] = '{4{1}};
} packet1;
为结构体单独赋值
initial begin
typedef struct {int a;
byte b;
shortintc;
int d;} my_struct_s;
my_struct_s st ='{32'haaaa_aaaad,
8'hbb,
16'hcccc,
32'hdddd_dddd};
end
通过“.”引用成员变量给结构体赋值
typedef struct {
int addr = 1 + constant;
int crc;
byte data [4] = '{4{1}};
} packet1;
Packet1 p1;
p1.addr = 32’h0000_0080;
p1.data = 32’h8c;
枚举
SV枚举提供了一种强大的变量类型,可以使得用户自定义特定名称的集合,例如指令中的操作码或者状态机中的编码等,可阅读性比较好。这种数据类型称为枚举。
枚举的声明格式
枚举的声明格式为:enum {变量名,…,变量名} enum_name
enum{INIT, DECODE, IDLE} state;
//INIT:0 DECODE:1 IDLE:2
可以显示的指定,枚举数据的存储类型
例如:
enum bit {FALSE, TRUE} boolstate;
//FALSE bit type : 0
//TRUE bit type : 1
enum logic[1:0] {INIT, WAIT, START} action_e ;
//INIT logic type : 0
//WAIT logic type : 1
//START logic type : 2
enum{INIT, DECODE, IDLE} state;
//INIT:0 DECODE:1 IDLE:2
enum{INIT=3, DECODE=5, IDLE=7} state;
//INIT:3 DECODE:5 IDLE:7
enum{A=2, B=5, C, D=30, E, F} dic;
//A=2 B=5 C=6 D=30 E=31 F=32
enum{A=2, B, C, D=4} dic; //ERROR
C=4 and D=4, this is error
枚举的一些操作
first() //returns the first member of theenumeration.
last() //returns the last member of theenumeration.
next() //returns the next element of theenumeration.
next(N) //returns the Nth next element.
prev() //returns the previous element ofthe enumeration.
prev(N) //returns the Nth previous element.
例如要遍历一个枚举类型
typedef enum{RED, BLUE, GREEN} color_e;
color_e color;
color = color.first;
do
begin
$display("Color = %0d/%s",color, color.name);
color = color.next;
end
while (color != color.first); // Done at wrap-around
队列
SV引入了队列的数据类型,使用方便,性能上比动态数组好很多。
队列可以存储任意的数据类型,包括SV内建的数据类型,也可以是用户自定义的数据类型。队列相当于维护了一个表格,其中表格可以实现任意的增删改查。
队列的顺序是由用户来维护的。
队列的声明格式
队列的声明格式为 data_type queue_name [$]
例如,int data_q [ ] ,其中 i n t 为队列中存储的数据类型为 i n t 型数据,声明队列时使用符号 [ ],其中int为队列中存储的数据类型为int型数据,声明队列时使用符号[ ],其中int为队列中存储的数据类型为int型数据,声明队列时使用符号[]
队列的一些操作
queue_name.size //返回queue的大小
queue_name.insert(index,item) //在某个索引处插入某元素
queue_name.delete(index) //刪掉某元素或整个queue
queue_name.pop_front() //去除第一个元素
queue_name.pop_back() //去除最后一个元素
queue_name.push_front() //插入元素到queue(0)
queue_name.push_back() //插入元素到queue($)
其中,关于队列队首和队尾的方法,可以使用下图形象的了解和记忆
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPJawNLf-1658909074933)(C:\Users\ad\AppData\Roaming\Typora\typora-user-images\image-20220725145527737.png)]
队列的注意事项
- 队列的存储空间无限大,理论上为物理内存的最大空间,从0到$。
- 可以在队列的任意地方实现元素的增删改查。
- 注意不要对队列使用构造函数new[ ]。
队列举例:
int j = 1,q2[$]={3,4}, // Queue literals do not use‘
q[$]={0,2,5}; // {0,2,5}initial begin
q.insert(1, j); // {0,1,2,5} Insert 1before 2
q = {q[0:2],3,4,q[3]}; // {0,1,2,3,4,5}Insert queue in q
q.delete(1); // {0,2,3,4,5} Delete elem.#1
// These operations are fast
q.push_front(6);// {6,0,2,3,4,5} Insert at front
j = q.pop_back;// {6,0,2,3,4} j = 5
q.push_back(8);// {6,0,2,3,4,8} Insert at back
j = q.pop_front;// {0,2,3,4,8} j = 6
foreach (q[i])
$display(q[i]);// Print entire queue
q.delete(); // {} Delete entirequeue
end
int q[$] = {2, 4, 8};
int p[$];
int e, pos;e = q[0]; // 读取第一个(最左边)条目。
e = q[$]; // 读取最后一个(最右边)条目。
q[0] = e; // 写第一个元素
p = q; // 读和写整个队列(拷贝)
q = {q, 6}; //在队列的尾部插入'6'
q = {e, q}; // 在队列的头部插入'e'
q = q[1:$]; // 删除第一个(最左边)元素
q = q[0:$-1]; // 删除最后一个(最右边)元素
q = q[1:$-1]; // 删除第一个和最后一个元素
q = {}; // 清除队列(删除所有的元素)
q = {q[0:pos-1], e, q[pos:$]}; // 在位置'pos'处插入'e'
q = {q[0:pos], e, q[pos+1:$]}; // 在位置'pos'之后插入'e’
SV数组方法
SV提供了很多数组方法,这些方法用于任何一种非合并的数组类型,包括定宽数组,动态数组,队列和关联数组。这些方法有繁有简,种类繁多,包括求和,求积,排序等。
数组缩减方法
sum(求和)、product(求积)、and(与)、or(或)、xor(异或)
bit on[10]; // Array of single bits
int total;
initial begin
foreach (on[i]) on[i] = i;// on[i]gets 0 or 1
// Print the single-bit sum
$display(“on.sum =%0d”, on.sum);// on.sum =1
// Print the sum using 32-bittotal
$display(“on.sum =%0d”, on.sum +32’d0); // on.sum = 5
// Sum the values using 32-bits astotal is 32-bits
total = on.sum;
$display("total = %0d",total); // total = 5
// Compare the sum to a 32-bitvalue
if (on.sum>=32’d5) // True
$display("sum has 5 or more1’s");
// Compute with 32-bit signed arithimetic
$display("int sum=%0d", on.sum with (int’(item)));
end
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}
byte b[] = ‘{ 1, 2, 3, 4};
int y;
y = b.sum ; // y becomes 10 => 1 + 2 +3 + 4
y = b.product ; // y becomes 24 => 1 * 2 *3 * 4
y = b.xor with ( item + 4 ); // y becomes12 => 5 ^ 6 ^ 7 ^ 8
logic [7:0] m [2][2] = '{ '{5, 10}, '{15, 20} };
int y;
y = m.sumwith (item.sumwith (item)); // y becomes 50 => 5+10+15+20
logic bit_arr[1024];
int y;
y = bit_arr.sumwith ( int'(item) ); // forces result to be 32-bit
数组定位方法
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}
int d[] = ‘{9,1,8,3,4,4}, tq[$];
// Find all elements greater than 3
tq = d.find_with (item > 3); // {9,8,4,4}
// Equivalent code
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); // {} -nonefound
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
数组排序方法
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}
SV过程语句
initial语句
initial语句在仿真一开始就立刻开始执行(0时刻),每个initial语句只执行一次。如下例:
注:
- 多个initial块并行执行,每个块的执行是独立的。
- 包含多条语句的initial过程块需要使用begin…end或fork…join块将这些语句封装成语句块块。
- 常用于测试模块,虚拟模块的编写,用于产生仿真测试激励信号,信号初始化等仿真环境。
always语句
SV对Verilog 的always语句做了扩展,除了always外还有:
-
always_comb //组合逻辑建模
-
always_latch //为latch建模
-
always_ff //为时序逻辑建模
always语句示例如下:
always @(posedge clk or negedgerst_n) begin
if(~rst_n)
begin
...
end
else
begin
...
end
end
always_comb a = b & c;
always_ff @(posedge clockor posedgereset)
begin
r1 <= reset ? 0 : r2 + 1;
...
end
always_latch
if(ck) q <= d;
final语句
-
final块类似于intial块,它们都定义了一个过程化的语句块,不同的是,final块仅仅在仿真结束前执行。典型情况下,final块用来显示有关仿真的统计信息。
-
在final块中可以使用的语句同允许在一个函数声明中使用的语句一样。与intial块不同,final块不会作为一个单独的进程执行。
-
当一个显式或隐式的$finish调用引起仿真结束的时候会执行final块。
-
final块只能在一次仿真中触发一次。
finial语句示例如下:
final
begin
$display("Number of cyclesexecuted %d",$time/period);
$display("Final PC = %h",PC);
end
initial begin
…
$finish();
end
SV控制流语句
unique if
-
unique if指示在一系列if…else…if条件中不应该有任何交迭,也就是说,这些条件是互斥的。
-
如果有多于一个条件为“真”,则会报错。
-
如果没有条件为“真”,或者没有条件为“真”,并且最后的if没有对应的else语句,同样会报错。
priority if
-
priority if指示一系列if…else…if条件应该按列出的顺序计算。
-
如果多个条件同时满足,那么选择第一个满足条件的分支,即具有优先级逻辑。
-
如果软件工具发现没有条件为“真”,或者没有条件为“真”,并且最后的if没有对应的else语句,则会报错。
unique case
unique case应该检查case条目的交迭。如果多于一个case条目匹配于case表达式,那么unique case应该发布一条警告信息。
priority case
priority case仅仅作用于第一个匹配。
SV循环语句
for循环
SystemVerilog加入了在for循环中声明for循环控制变量的能力。这种方式会在循环内产生一个本地变量。
例如:下例中for循环内部声明循环变量,同时可以使用自增与自减。
module foo;
initial begin
for (int i = 0; i <= 255; i++)
...
end
initial begin
loop2: for (int i = 15; i >= 0; i--)
...
end
endmodule
do…while循环
do…while的语句格式为:do statement while(condition) ,与C语言相同
例如:
initial
begin:example
integer array[10], sum, j;
// Declare i in for statement
for (int i=0; i<10; i++) // Increment i
array[i] = i;
// Add up values in the array
sum = array[9];
j=8;
do // do...whileloop
sum += array[j]; // Accumulate
while(j--); // Test ifj=0
$display("Sum=%4d", sum); // %4d - specify width
end : example
foreach循环
foreach结构在数组元素上的迭代。它的自变量是一个指明任意类型数组(固定尺寸的、动态的、及联合数组)的标识符,然后紧跟着一个包围在方括号内的循环变量的列表。每一个循环变量对应于数组的某一维。foreach结构类似于一个使用数组范围替代一个表达式来指定重复次数的repeat循环。
例如:
string words[2] ={"hello", "world"};
int prod[1:8][1:3];
foreach (words[j])
$display(j, words[j]); // 打印每一个索引和值
foreach (prod[k,m])
prod[k][m] = k * m; // 初始化
repeat循环
repeat循环常用于固定次数的循环
SV跳转语句
SystemVerilog加入了像C语言中一样的跳转语句:break、continue和return。
break
像C语言一样跳出循环,结束循环体。
continue
像C语言一样跳转到循环的尾部,结束本次循环,继续下一次循环。
return expression
退出一个函数且具有返回值,expression必须具有正确的类型,否则会报错。
return
退出一个任务或void函数
disable语句
disable语句用于终止正在运行的代码块。可以分为disable fork以及disable lable
SystemVerilog可以在任务中使用return,但也支持disable。
如果disable被应用到一个命名的任务,那么这个任务中所有当前正在运行的部分都会被关闭。
例如:
for (int i = 0; i < 5; i++)begin : forloop
if( i == 3 )
disable forloop
$display("i=%0d",i);
end
event语句
-
event是一个静态的句柄,用于同步多个线程,在一个线程中触发事件,在另外一个线程中等待事件。
-
事件的句柄可以是null。
-
声明一个事件如下方式:event done;
-
事件的触发使用->,事件的等待使用@或者.triggered
SV块语句
块语句用来将多个语句组织在一起,使得他们在语法上如同一个语句。块语句分为:
-
顺序块:语句置于关键字begin和end之间,块中的语句以顺序方式执行。
-
并行块:关键字fork和join/join_any/join_none之间的是并行块语句,块中的语句并行执行。
begin…end顺序块
命名块:可以为块语句定义一个标识名称,将块名加在begin或fork后面。
为块语句命名,可以方便我们为块语句进行操作,例如 disable当前块等操作。
例如:
begin: Continue //该begin…end块定义为Continue
statement1;
statement2;
……
end
fork…join/join_any/join_none并行块
Verilog即有fork…join并行块语句,Verilog的fork…join块总是引起执行fork语句的进程阻塞直到所有分支进程中止。
除此之外,SystemVerilog加入了join_any和join_none关键字,提供了三种选择来指定父进程何时恢复执行。
fork…join/join_any/join_none三者描述如下,同时下图形象的绘制了并行线程的执行关系与异同点。
选项 | 描述 |
---|---|
join | 父进程会阻塞直到这个分支产生的所有进程结束。 |
join_any | 父进程会阻塞直到这个分支产生的任意一个进程结束。 |
join_none | 父进程会继续与这个分支产生的所有进程并发执行。在父线程执行一条阻塞语句或者结束之前,产生的进程不会启动执行。 |
在定义一个fork…join块的时候,将整个分支封装在一个begin…end块中会引起整个块作为单个进程执行,其中每条语句顺序地执行。
例如:
fork
begin
statement1; // 一个带有2条语句的进程
statement2;
end
join
在下面的例子中,包含两个进程分支,第一个等待20ns,第二个等待命名事件eventA被触发。因为指定了join关键字,父进程应该阻塞直到这两个进程都结束;也就是说,直到过了20ns并且eventA被触发。
fork
begin
$display("First Block\n");
#20ns;
end
begin
$display("Second Block\n");
@eventA;
end
join
fork…join举例
initial begin
$display("@%0t: start fork...joinexample", $time);
#10 $display("@%0t: sequentialafter #10", $time);
fork
$display("@%0t: parallelstart", $time);
#50 $display("@%0t: parallelafter #50", $time);
#10 $display("@%0t: parallelafter #10", $time);
begin
#30 $display("@%0t: sequentialafter #30", $time);
#10 $display("@%0t: sequentialafter #10", $time);
end
join
$display("@%0t: after join",$time);
#80 $display("@%0t: finish after#80", $time);
end
其执行结果为
@0: start fork...join example
@10: sequential after #10
@10: parallel start
@20: parallel after #10
@40: sequential after #30
@50: sequential after #10
@60: parallel after #50
@60: after join
@140: finish after #80
fork…join_any举例
initial begin
$display("@%0t: start fork...join_anyexample", $time);
#10 $display("@%0t: sequentialafter #10", $time);
fork
$display("@%0t: parallelstart", $time);
#50 $display("@%0t: parallelafter #50", $time);
#10 $display("@%0t: parallelafter #10", $time);
begin
#30 $display("@%0t: sequentialafter #30", $time);
#10 $display("@%0t: sequentialafter #10", $time);
end
join_any
$display("@%0t: after join_any",$time);
#80 $display("@%0t: finish after#80", $time);
end
其执行结果为
@0: start fork...join_any example
@10: sequential after #10
@10: parallel start
@10: after join_any
@20: parallel after #10
@40: sequential after #30
@50: sequential after #10
@60: parallel after #50
@90: finish after #80
fork…join_none举例
initial begin
$display("@%0t: start fork...join_noneexample", $time);
#10 $display("@%0t: sequentialafter #10", $time);
fork
$display("@%0t: parallelstart", $time);
#50 $display("@%0t: parallelafter #50", $time);
#10 $display("@%0t: parallelafter #10", $time);
begin
#30 $display("@%0t: sequentialafter #30", $time);
#10 $display("@%0t: sequentialafter #10", $time);
end
join_none
$display("@%0t: after join_none",$time);
#80 $display("@%0t: finish after#80", $time);
end
其执行结果为
@0: start fork...join_none example
@10: sequential after #10
@10: after join_none
@10: parallel start
@20: parallel after #10
@40: sequential after #30
@50: sequential after #10
@60: parallel after #50
@90: finish after #80
wait fork语句
wait fork语句被用来确保所有子进程的执行都已经结束。
例如:
task do_test;
fork
exec1();
exec2();
join_any
fork
exec3();
exec4();
join_none
wait fork; // 阻塞直到exec1... exec4完成
endtask
disable fork
disable fork语句用来中止调用进程的所有活跃的子进程以及子进程的所有子进程。也就是说,如果任何子进程还具有它们自己的子进程,那么disablefork语句也会将它们中止。
例如:
task get_first(output int adr);
fork
wait_device(1, adr);
wait_device(7, adr);
wait_device(13, adr);
join_any
disable fork;
endtask
wait/disable fork注意事项:
-
wait fork作用于父进程下的子进程,而不包括子进程下的子进程,而disable fork则是作用于父进程下的所有进程,包括子进程的子进程;
-
Verilog支持disable结构,当它应用于由进程正在执行的命名块的时候,它会将这个进程中止。
disable fork语句与disable lable的不同之处在于,disable fork会考虑动态的进程父子关系,因此,disable lable应该中止执行一个特定块的所有进程,无论进程是否由调用线程产生的分支,而disable fork则仅仅中止那些由调用线程产生的进程。
SV的Task
task并不是SV特有的,Verilog中也支持task,但是SV对task进行了增强和扩展,扩展内容如下:
-
SystemVerilog加入了在静态任务和函数中声明自动变量的功能。
-
SystemVerilog加入了在自动任务和函数中声明静态变量的功能。
-
无需一个begin…end块或fork…jion块就可以在一个任务或函数中使用多条语句的能力。
-
在到达任务或函数的结尾之前从任务或函数返回的能力。
Task的声明格式
task mytask1 (output int x, inputlogic y);
...
endtask
task mytask2;
output x;
input y;
int x;
logic y;
...
endtask
Task的参数方向
input // 在开始的时候拷贝值
output // 在结束的时候拷贝值
inout // 在开始的时候拷贝,在结束的时候输出
ref // 传递引用
其中,input为输入参数的传递,为task开始调用时,将实参的数值copy一份传递给task的形参。类似的,output与inout同样是参数的copy传递给形参。ref为实参引用的传递,传递的是实参的地址,值得注意的是,如果在task内部将参数修改,那么实参的数值对应的也会被修改。
在SystemVerilog中,如果没有指定参数的方向,那么它的缺省方向是输入。一旦指定了一个方向,那么它就成为后续参数的缺省方向。在下面的例子中,形式参数a和b缺省为输入,u和v都是输出。
task mytask3(a, b, outputlogic [15:0] u, v);
...
endtask
每一个形式参数都具有一个数据类型,它或者显式声明,或者从一个缺省类型继承。
SystemVerilog中任务参数的缺省类型为logic。
SystemVerilog允许将一个数组声明为task的形式参数。
例如:
task mytask4(input [3:0][7:0] b[3:0], output[3:0][7:0]y[1:0]);
...
endtask
Task的注意事项
-
在SystemVerilog中,多条语句可以在task和endtask之间被写入,可以省略begin…end。如果省略begin…end,这些语句也是顺序地执行,这与它们被包含在begin…end中是一样的。
-
task中没有任何语句也是合法的,即空task。
-
在Verilog中,当task到达endtask的时候任务退出。而对于SystemVerilog,在endtask关键字之前可以使用return语句退出task。
Task举例:
module task_test ();
initial begin
#1 mytask(4,5);
#1 mytask(92,6);
#1 $finish;
end
task mytask(input bit [3:0] count, delay);
automatic reg [7:0] a;
if (count > 20)begin
$display ("@%t Returning from task", $time);
return;
end
#(delay) $display ("@%t Value passed is %d", $time, count);
endtask
endmodule
SV的Function
Function的声明格式
function logic [15:0] myfunc1(int x, int y);
...
endfunction
function logic [15:0] myfunc2;
input int x;
input int y;
...
endfunction
Function参数
类似Task。
同样的,SystemVerilog允许在function和endfunction之间写入多条语句,begin…end可以省略。如果省略begin…end,这些语句仍然为顺序地执行,这与它们被包含在begin…end中是一样的。
Function返回值
在Verilog中,函数必须具有返回值。返回值是通过为函数的名字赋值来完成的。
SystemVerilog允许将函数声明成void类型,它没有返回值。对于非void函数,可以使用return语句实现。
例如:
function int myfunc2 (input int x,y);
return x * y - 1; //使用return语句指定返回值
endfunction
SV 丢弃函数返回值:通过将函数返回值强制转换成void类型,SystemVerilog允许使用void数据类型来忽略一个函数的返回值。调用方法如下:
void'(some_function());
Task与Function参数传递
SV中task与function的参数传递,共有四种方式,分别为:通过值传递,通过引用传递,缺省的参数值以及通过名字传递。
值传递
-
通过值传递是向function传递参数的缺省机制,它也是Verilog-2001提供的唯一的参数传递机制。这种参数传递机制是通过将每一个参数拷贝到function区域的方式实现的。
-
如果function是automatic的,那么function在它的堆栈中保留一个参数的本地拷贝。如果参数在function中被改变,那么这种改变在function外是不可见的。
-
当参数很大的时候,我们可能不希望拷贝这个参数。拷贝会造成性能上的影响。
引用传递
通过引用传递的参数不会拷贝到function区域,相反,一个对原始参数的引用会被传递到function。然后function可以通过引用访问参数数据。为了指示通过引用传递的参数,参数声明需要以ref关键字开始。
例如:
function int crc(ref byte packet[1000:1]);
for(int j=1; j<=1000; j++) begin
crc ^= packet[j];
end
endfunction
byte packet1[1000:1];
int k = crc(packet1); // 无论是通过值传递还是通过引用传递,调用方法是一样的
当通过引用传递参数的时候,在调用者内或function中对参数所作的任何改变对两者都是可见的。在function的外部可以立即(在function返回之前)看到变量的变化。
为了保护通过引用传递的参数不被子例程修改,可以将const限定符与ref一起使用,用来表明尽管这个参数是通过引用传递,但它是一个只读变量。
例如:
task show (constref byte [] data);
for (int j = 0; j < data.size; j++)
$display(data[j]); // 数据可以被读出但不能被修改
endtask
缺省参数值传递
为了处理一些共用的情况或者考虑一些不用的参数。
例如:这个例子声明了一个任务,read(),它具有两个缺省参数,j和data。接着这个任务就可以使用不同的缺省参数调用
task read(int j=0, int k, intdata=1);
...
endtask;
read( , 5); // 等价于read(0, 5, 1);
read(2, 5); // 等价于read(2, 5, 1);
read( , 5, ); // 等价于read(0, 5, 1);
read( , 5, 7); // 等价于read(0, 5, 7);
read(1, 5, 2); // 等价于read(1, 5, 2);
read(); // 错误;k没有缺省值
名字传递
SystemVerilog允许任务和函数的参数通过名字或位置进行传递。这就允许指定非连续的缺省参数,并能够方便地指定调用中传递的参数。
例如:
function int fun(int j=1, strings="no");
...endfunction
fun(.j(2), .s("yes")); //fun(2, "yes");
fun(.s("yes")); // fun(1, "yes");
fun(, "yes"); // fun(1, "yes");
fun(.j(2)); // fun(2, "no");
fun(.s("yes"), .j(2)); //fun(2 , "yes");
fun(.s(), .j()); // fun(1 , "no");
fun(2); // fun(2, "no");
fun(); // fun(1, "no");
SV进程间的通信
SystemVerilog加入了一个内建的semaphore类,这个内建类可以用来同步以及相互排斥地访问共享资源。一个mailbox内建类可以用作是进程间的通信通道。SystemVerilog还增强了Verilog的命名事件数据类型以便满足许多系统级的同步需求。
event
SV中定义一个事件如下所示:
event status; *//定义了一个事件,事件的名称为 status*
触发事件
SV中触发一个事件时,被命名事件可以通过**->** 操作符触发。
触发一个事件可以为当前等待这个事件的所有进程解除阻塞。
等待事件
等待一个事件被触发的基本机制是通过事件控制操作符 @。@操作符阻塞调用进程直到指定的事件被触发。
等待进程在触发进程执行触发操作符->之前必须执行@语句。如果触发器首先执行,那么等待进程会保持在阻塞状态。
semaphore(信号量)
semaphore是SV中一个内建的类,它提供了下列方法:
- 产生一个具有指定数目键值的semaphore:new() 方法。new()函数返回semaphore的句柄,如果没有产生semaphore则返回null。
- 从桶中获取一个或多个键值:get()方法。get()指定了需要从semaphore中获得的键值的数目,它的缺省值为1。如果指定的键值数目有效,那么方法返回并且进程会继续执行;如果指定的键值数目无效,进程会阻塞直到键值变成有效。
- 向桶中返回一个或多个键值:put()方法。当调用semaphore.put()任务的时候,指定数目的键值被返回到semaphore中。
- 尝试无阻塞地获取一个或多个键值:try_get()方法。如果指定的键值数目有效,那么try_get()方法返回1并且进程会继续执行;如果指定的键值数目无效,那么try_get()方法返回0。
mailbox
maibox是一种通信机制,它使得消息能够在进程间通信。一个进程发送到mailbox的数据可以被另外一个进程获得。
mailbox的行为就像一个真实的邮箱一样。SystemVerilog的mailbox以一个可控的方式来传输和接收数据。在产生mailbox的时候,它可以具有固定大小也可以无限大。当一个具有固定大小n的mailbox包含了 n个消息的时候,mailbox会变满。
mailbox同样是SV一个内建的类,它提供了下列方法:
- 产生一个mailbox:new()
- 将一个消息放置到一个mailbox中:put()
- 尝试将一个消息无阻塞地放置到一个mailbox中:try_put()
- 从一个mailbox中重新获得一个消息:get()或者peek()
- 尝试无阻塞地从一个mailbox中重新获得一个消息:try_get()或try_peek()
- 获得mailbox中消息的数目:num()
如果mailbox已满,则put()会阻塞,如果mailbox为空,则get()会阻塞。peek()任务可以获取对mailbox里面数据的拷贝而不会移除原始数据。
new()方法
- mailbox new()方法的原型为 function new(int bound = 0);
- new()函数返回mailbox的句柄,如果不能产生mailbox则返回null。
- 如果bound参数为0,那么mailbox是无边界的(缺省情况),此时一个put()操作应该永远不会阻塞;
- 如果bound为非0,那么它表示mailbox队列的尺寸。
- bound必须是正的。负的边界是非法的并会导致不确定的行为。
put()方法
- put()方法将一个消息放入到一个mailbox中。
- put()方法严格按FIFO的顺序将一个消息存储在mailbox中。如果mailbox使用一个有界的队列产生,那么put()进程应该被挂起直到队列中有足够的空间。
try_put()方法
- try_put()方法尝试将一个消息放置在一个mailbox中。try_put()方法严格按FIFO的顺序将一个消息存储在mailbox中。这个方法仅仅对有界的mailbox才有意义(因为对于无界的mailbox来说,put()永远不会阻塞,即put()永远能够成功)。
- 如果mailbox没有满,那么指定的消息被放置在mailbox当中并且函数返回1。如果mailbox满了,那么函数返回0。
get()方法
- get()方法从一个mailbox中获得一个消息。
- get()方法从mailbox中获得一个消息,mailbox则在队列中删除这个消息。
- 如果mailbox是空的,那么当前的get()进程阻塞直到一个消息被放置到mailbox中。
- 如果在消息变量和mailbox中的消息间存在类型不匹配,那么会产生一个运行时错误。
try_get()方法
- try_get()方法尝试无阻塞地从一个mailbox中获得一个消息。
- 如果mailbox是空的,那么try_get()方法返回0。
- 如果在消息变量和mailbox中的消息间存在类型不匹配,那么try_get()方法返回-1。
- 如果一个消息是有效的,并且消息类型与消息变量的类型匹配,那么消息被重新获得并且try_get()方法返回1。
peek()方法
- peek()方法从一个mailbox中拷贝一个消息但不会将其从队列中删除。
- 如果mailbox是空的,那么当前的进程会阻塞直到一个消息被放置到mailbox中。
- 如果在消息变量和mailbox中的消息间存在类型不匹配,那么会产生一个运行时错误。
try_peek()方法
- try_peek()方法尝试从一个mailbox中拷贝一个消息但不会将其从队列中删除。
- 如果mailbox是空的,那么try_peek()方法返回0。
- 如果在消息变量和mailbox中的消息间存在类型不匹配,那么try_peek()方法返回-1。
- 如果一个消息是有效的,并且消息类型与消息变量的类型匹配,那么消息被拷贝并且try_peek()方法返回1。
num()方法
- 一个mailbox中消息的数目可以通过num()方法获得。
参数化的mailbox
缺省的mailbox是无类型的,一个mailbox可以发送和接收任何类型的数据。这是一个非常强大的机制。但是也会因为一个消息与get()到消息变量间的类型不匹配而导致运行时错误。
一个mailbox经常被用来传输一个特定的消息类型,在这种情况下,如果能够在编译时发现类型不匹配,那会是很有用的。 此时需要一个参数化的mailbox。
参数化的mailbox声明如下:mailbox#(type=dynamic_type),其中dynamic_type代表一个特殊的类型,它能够执行运行时的类型检查。
参数化的mailbox提供了所有与无类型的mailbox相同的标准方法:num()、new()、get()、peek()、put()、try_get()、try_peek()、try_put()。
无类型的mailbox与参数化mailbox之间唯一的不同是:对于一个参数化的mailbox,编译器能够确保put()、try_put()、peek()、try_peek()、get()和try_get()与mailbox消息类型兼容,从而所有的类型不匹配都可以被编译器在编译期间捕获而不是在运行时捕获。
至此本文引用自:https://blog.csdn.net/flhcherish/article/details/115270579,后续还将继续更新学习笔记~
SV的Constraint(约束)
什么是约束呢?
- 约束是对变量或者元素的描述与限制。例如描述两个变量之间的数值关系,限制变量的产生范围,权重分布等等。
- 约束指定了变量或者元素的产生条件,产生范围等限制,同时可以限制非法数据的产生。
- 约束一般与class、随机化同时使用,可以构建强大的sequence。
为什么需要约束?
- 因为待验证的点成千上万,无法人为的考虑所有情况的组合。
- 有了约束,用户可以产生出用户想要的数据,就更容易产生用户想要的激励。
- 加以约束,然后使用随机化,这样可以利用EDA工具产生出任何满足约束的数据组合,工具会计算和解决约束,从而解放工程师。
SV随机变量
不匹配,那么会产生一个运行时错误。
try_peek()方法
- try_peek()方法尝试从一个mailbox中拷贝一个消息但不会将其从队列中删除。
- 如果mailbox是空的,那么try_peek()方法返回0。
- 如果在消息变量和mailbox中的消息间存在类型不匹配,那么try_peek()方法返回-1。
- 如果一个消息是有效的,并且消息类型与消息变量的类型匹配,那么消息被拷贝并且try_peek()方法返回1。
num()方法
- 一个mailbox中消息的数目可以通过num()方法获得。
参数化的mailbox
缺省的mailbox是无类型的,一个mailbox可以发送和接收任何类型的数据。这是一个非常强大的机制。但是也会因为一个消息与get()到消息变量间的类型不匹配而导致运行时错误。
一个mailbox经常被用来传输一个特定的消息类型,在这种情况下,如果能够在编译时发现类型不匹配,那会是很有用的。 此时需要一个参数化的mailbox。
参数化的mailbox声明如下:mailbox#(type=dynamic_type),其中dynamic_type代表一个特殊的类型,它能够执行运行时的类型检查。
参数化的mailbox提供了所有与无类型的mailbox相同的标准方法:num()、new()、get()、peek()、put()、try_get()、try_peek()、try_put()。
无类型的mailbox与参数化mailbox之间唯一的不同是:对于一个参数化的mailbox,编译器能够确保put()、try_put()、peek()、try_peek()、get()和try_get()与mailbox消息类型兼容,从而所有的类型不匹配都可以被编译器在编译期间捕获而不是在运行时捕获。
至此本文引用自:https://blog.csdn.net/flhcherish/article/details/115270579,后续还将继续更新学习笔记~
SV的Constraint(约束)
什么是约束呢?
- 约束是对变量或者元素的描述与限制。例如描述两个变量之间的数值关系,限制变量的产生范围,权重分布等等。
- 约束指定了变量或者元素的产生条件,产生范围等限制,同时可以限制非法数据的产生。
- 约束一般与class、随机化同时使用,可以构建强大的sequence。
为什么需要约束?
- 因为待验证的点成千上万,无法人为的考虑所有情况的组合。
- 有了约束,用户可以产生出用户想要的数据,就更容易产生用户想要的激励。
- 加以约束,然后使用随机化,这样可以利用EDA工具产生出任何满足约束的数据组合,工具会计算和解决约束,从而解放工程师。