过程语句
SystemVerilog从C和C++中引入了很多操作符和语句。如for循环中定义循环变量,它的作用范围仅限于循环内部,从而有助于避免一些代码漏洞;自动递增符“i++”和自动递减符“i–”既可以作为前缀,也可以作为后缀。如果在begin或fork语句中使用标识符,那么在相对应的end或join语句中可以放置相同的标号,这使得程序块的首尾匹配更加容易。你也可以把标识符放在SystemVerilog的其他结束语句里,例如endmodule、endtask、endfunction。
initial
begin:example
integer array[10],sum,j;
//在for语句中声明i
for(int i=0;i<10;i++) //i递增
array[i]=i;
//把数组里的元素相加
sum=array[9];
j=8;
do
sum+=array[j]; //累加
while(j--); //判断j=0是否成立
end:example
同时System Verilog为循环功能增加了两个新语句。第一个是continue,用于在循环中跳过本轮循环剩下的语句而直接进入下一轮循环。第二个是break,用于终止并跳出循环。
任务、函数及void函数
任务(task)和函数(function)的区别:
- 任务可以消耗时间而函数不能(函数里面不能带有注入#100的时延语句或诸如@(posedge clock)、wait(ready)的阻塞语句);
- 函数也不能调用任务,单任务可以调用函数和其他任务;
- 函数必须有返回值,并且返回值必须被使用,例如用到赋值语句中;任务则不必(System Verilog对这条限制稍有放宽,允许函数调用任务,但只能在由fork….join_none语句生成的线程中调用)。
(注:如果有一个不消耗时间的svstemverilog任务,应该把它定义成void函数,这种函数没有返回值。这样它就能被任有任务或函数所调用了。从最大灵活性的角度考虑,所有用于调试的子程序都应该定义成void函数而非任务,以便于被任何其他任务或函数所调用。)
在SystemVerilog中,如果你想调用函数并且忽略它的返回值,可以使用void进行结果转换,如下所示。
void'($fscanf(file,"8d",i));
任务和函数概述
一般情况下,不带参数的子程序在定义或调用时并不需要带空括号()。
在System Verilog中,begin…end块变成了可选,而在Verilog-1995中则对单行以外的子程序都是必须的。task/endtask和function/endfunction的关键词已经足以定义这些子程序的边界了。
task multiple lines;
$display("First line");
$display("Second line");
endtask:multiple lines
高级参数的类型
Verilog对参数的处理方式很简单:在子程序的开头把input和inout的值复制给本地变量,在子程序退出时则复制 output和inout的值。除了标量以外,没有任何把存储器传递给Verilog子程序的办法。
在System Verilog中,参数的传递方式可以指定为引用而不是复制,这种ref参数类型比input、output或inout更好用。
//使用ref和const传递数组
function void print_checksum(const ref bit[31:0]a[]);
bit[31:0]checksum=0;
for(inti=0;i<a.size();i++)
checksum^=a[i];
$display("The array checksum is $0d",checksum);
endfunction
(注:1、System Verilog 允许不带ref进行数组参数的传递,这时数组会被复制到堆栈区里。这种操作的代价很高,除非是对特别小的数组;2、System Verilog的语言参考手册(LRM)规定了ref参数只能被用于带自动存储的子程序中。如果你对程序或模块指明了automatic属性,则整个子程序内部都是自动存储的。3、上例用到了const 修饰符。虽然数组变量a指向了调用程序中的数组,但子程序不能修改数组的值,否则编译器将报错。)
参数的缺省值
当测试程序越来越复杂时,你可能希望在不破坏已有代码的情况下增加额外的控制。在下例的函数中,可能希望把数组中间部分元素的校验和打印出来,但是又不希望改写代码,在System Verilog中,可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值
function void print checksum(ref bit [31:0] a[],
input bit[31:0]low=0,
input int high=-1);
bit [31:0] checksum=0;
if(high==-1|high>=a. size())
high=a. size()-1;
for(int i=low;i<=high;i++)
checksum+=a[i];
$display("The array checksum is $0d", checksum);
endfunction
一个常见的错误
在编写子程序代码时往往会忘记,在缺省的情况下参数的类型是与其前一个参数相同的,而第一个参数的缺省类型是单比特输入。
task sticky(ref int array[50],
inta,b);
a和b的参数类实际采用的是与上一个参数一致的reg类型,对简单的int变量使用ref通常并无必要,但编译器不会对此做出任何反应,所以不会意识到正在使用一个错误的方向类型。如果在子程序中使用了非缺省输入类型的参数,应该明确指的所着参数的方向,如下例:
task sticky(ref int array[50],
input int a,b); //明确指定方向
子程序的返回
Verilog中子程序的结束方式比较简单;当你执行完子程序的最后一条语句,程序就会返回到调用子程序的代码上。此外,函数还会返回一个值,该值被赋给与函数同名的变量。
返回语句
System Verilog 增加了return语句,使子程序中的流程控制变得更方便。
从函数返回一个数组
Verilog的子程序只能返回一个简单值,例如比特、整数或是向量。在System Verilog中,函数可以采用多种方式返回一个数组
- 第一种方式是定义一个数组类型,然后在函数的声明中使用该类型。
typedef int fixed_array5[5];
fixed_array5 f5;
function fixed arravs init(int start);
foreach(init[i])
init[i]=i+start;
endfunction
initial begin
f5=init(5);
foreach(f5[i])
$display("f5[%0d]=%0d",i,f5[i]);
end
(注:使用上述代码的一个问题是,函数init创建了一个数组,该数组的值被拷贝到数组f5中。如果数组很大,那么可能会引起一个性能上的问题)
另一种方式是通过引用来进行数组参数的传递。以ref参数的形式将数组传递到函数里,如下例所示。
function void init(ref int f[5],input int start);
foreach(f[i])
f[i]=i+start;
endfunction
int fa[5];
initial begin
init(fa,5);
foreach(fa[i])
$display("fa[sod]=80d",i,fa[i]);
end
从函数中返回数组的最后一种方式是将数组包装到一个类中,然后返回对象的句柄;
局部数据的存储
Verilog最初的目的是用来描述硬件。因此语言中的所有对象都是静态分配的。特别是,子程序参数和局部变量是被存放在固定位置的,而不像其他编程语言那样存放在堆栈区里。
//静态初始化的漏润
program initialization;//有漏洞的版本
task check_bus;
repeat(5)@(posedge clock);
if(bus cmd=='READ)begin
logic[7:0]local addr=addr<<2;//有漏洞,何时对localaddr赋初值?
$display("Local Addr=8h",local_addr);
end
endtask
endprogram
一般为了防止初始化的漏洞,将声明和初始化分开即可;或者将程序块声明为automatic,这样就避免了一开始就有初值;如下所示:
logic[7:0]local addr;
local_addr=addr<2;
这篇笔记参考《system verilog验证》绿皮书整理而成,仅作学习心得交流,如果涉及侵权烦请请告知,我将第一时间处理。