第三章. 过程语句和子程序
3.1 循环语句
continue
:在循环中跳出本轮循环剩下的语句直接进入下一轮循环。
break
:用于终止并跳出循环
bit[127:0] cmd;
int file, c;
file = $fopen("commands.txt", "r");
while(!feof(file))
begin
c = $fscanf(file,"%s", cmd);
case(cmd)
"" : continue;
"done" : break;
...
endcase
$fclose(file);
end
3.2 任务和函数
V中,task和function之间有明显的区别,其中最重要的一点是任务可以消耗时间而函数不能。SV中函数允许调用任务,但是只能在由fork…join_none语句生成的线程中调用。
SV中不消耗时间的任务,应该定义成void函数(无返回值)。这样它就可以被任何任务或者函数所调用。从最大灵活性的角度考虑,所有用于调试的子程序都应该定义成void函数而非任务,以便于被任何其他任务或函数所调用。SV中,如果想调用函数并且忽略他的返回值,可以使用void
进行结果转换。(常用)
void'($scanf(file, "%d", i));
SV中,对任务和函数做了一些小的改进,使他看起来更像C和C++。不带参数的子程序在定义或调用是并不需要带()
。SV中begin...end
块为可选。
3.3 子程序参数
- C语言峰峰的子程序参数
V中要求对参数进行两次声明,一次时方向声明,另一次是类型声明。
task mytask2;
output [31:0] x;
reg [31:0] x;
endtask
//C语言风格的子程序参数
task mytask1(output logic [31:0] x,
input logic y);
endtask
- 参数的方向
SV中在声明子程序参数时,默认的类型和方向是“logic输入”。虽然SV提供了默认类型和方向,但是建议对所有子程序参数的声明都带上类型和方向,这样可以避免难以发现的漏洞。
- 高级的参数类型
V中对参数的处理方式:在子程序开头把input和input的值复制给函数的local变量,在子程序退出时则复制output和inout的值。SV中参数的传递方式可以指定为引用而不是复制,这种ref参数类型(C中国的指针)比input,output和inout更好用。SV允许不带ref进行数组参数的传递,这时数组会被复制到堆栈区,这种操作的代价很高,除非是对特别小的数组。LRM中规定了ref参数只能被用于带自动存储的子程序中。向子程序传递数组时应尽量使用ref以获得最佳的性能。如果不希望子程序改变数组的值,可以使用const ref类型,此情况下,编译器会进行检查以确保数组不被子程序修改。ref参数的第二个好处就是任务里可以修改变量而且修改结果对调用他的函数随时可见。
- 参数的缺省值
SV中,可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值。
function void print_checksum(ref bit [31:0] a[ ],
input bit [31:0] low = 0,
input int high = -1);
pass;
endfunction
- 利用名字进行参数传递
如果有一个带着许多参数的任务或函数,其中一些参数有缺省值,而你又只想对他们中的部分参数进行设置,那么可以通过采用类似port的语法指定子程序参数名字的方式指定一个子集。
task many( input int a = 1, b = 2, c = 3, d = 4);
$display(“%0d %0d %0d %0d” ,a, b, c, d);
endtask
initial begin
many(6,7,8,9); //6,7,8,9
many(); //1,2,3,4缺省值
many(.c(5)); //1,2,5,4只指定c
many(,6,.d(8)); //1,6,3,8混合方式
end
3.4 子程序的返回
V中子程序的结束方式比较简单,当你执行完子程序的最后一条语句,程序就会返回到调用子程序的代码上。此外,函数还会返回一个值,该值被赋给与函数同名的变量。
- 返回语句
SV中增加了return语句,使子程序中的流程控制变得更加方便。
- 从函数中返回数组
V的子程序只能返回简单值,例如比特,整数或是向量。SV中可用函数返回数组。
一种是在子程序内部定义数组并初始化,然后在函数声明中使用该类型。
typedef int array5[5];
array5 a5;
//init函数创建了一个数组
function array5 init(start);
foreach(init[i]) init[i] = i + start;
endfunction
a5 = init[5];
第二种是通过ref来进行数组参数的传递。
function void init(ref int f[5], input int start);
foreach(f[i]) f[i] = i + start;
endfunction
int fa[5];
init(fa, 5);
最后一种是将数组包装到一个类中,然后返回对象的句柄。
3.5 局部数据存储
**V的目的是用来描述硬件,语言中的所有对象都是静态分配的。子程序参数和局部变量是被存放在固定位置的,**而不像其他编程语言可以放在堆栈区里。
- 自动存储
由于V中任务是静态存储方式,因此在多个地方调用同一个任务,由于任务的局部变量会使用共享的静态存储区,所以不同线程之间会串用这些局部变量。可以指定任务,函数和模块使用自动存储,从而迫使仿真器使用堆栈区存储局部变量。SV中,module和program块中的子程序缺省情况下仍然使用静态存储。如果要使用自动存储,则必须在程序语句中加入automatic关键字。
- 变量的初始化**(不太理解)**
当试图在声明中初始化局部变量时,因局部变量实际上在仿真开始前被赋了初值,因此会出现值覆盖的情况。
3.6 时间值
- 时间单位和精度
timeunit
和timeprecision
声明语句可以明确地为每个模块指明时间值,从而避免含糊不清。如果使用这些语句替代timescale
,则必须把他们放到每个带有时延的模块里。
- 时间参数
SV中允许使用数值和单位来明确指定一个时间值。可以使用timeunit
和timeprecision
或者timescale
即可。还可以使用经典的V时间函数,$timeformat
,$time
和$realtime
来使代码在试件标度上更清楚。$timeformat
的四个参数分别是:时间标度(-9代表纳秒,-12代表皮秒),小数点后的数据精度,时间值之后的后缀字符串,以及显示数值的最小宽度。
- 时间和变量
可以把时间存放到变量中,并在计算和延时中使用。根据当前的时间量程和精度,时间值会被缩放或舍入。time类型的变量不能保存小数时延,因为他是64bit的整数,所以时延的小数部分会被舍入。如果不希望被舍入,可以使用real变量。
- $time和$realtime的对比
$time:返回一个根据所在模块的时间精度要求进行舍入的整数,不带小数部分。
$realtime:返回一个带小数部分的完整实数。
参考文献:
SystemVerilog验证 测试平台编写指南(原书第二版)张春 麦宋平 赵益新 译