子程序
systemveilog中函数和任务的使用方法如下:
-
一般情况下,不带参数的子程序在定义或调用时不需要带空括号();
-
begin…end为可选的,因为task…endtask和function…endfunction关键词足以定义这些程序的边界,
但是在verilog-1995中,除了单行以外的子程序是必须的; -
可以采用简明的c语言风格,缺省的类型和方向是logic
input;但是在verilog中,对参数要进行两次声明,一次是方向声明,一次是类型声明; -
task 和 function 的声明在module内部,且不需要先于调用之前。
//systemverilog风格
task test(a, b, output bit [15:0] c, d);
//其中a,b为input logic a,b; c,d为output bit [15:0];
//verilog风格
task test2;
output [31:0] x;
reg [31:0] x;
input y;
endtask
- 可以在参数列表中指定输入参数(input),输出参数(output)、输入输出参数(inout)或引用参数(ref);只有数组变量可以在形式参数列表中声明为ref类型,而线网类型不能声明为ref类型;在使用ref时,有时候为了保护数据对象不被写入,可以同通过const方式来限定ref声明的参数
但是在verilog中,对参数的处理方式很简单,在子程序的开头把input或inout的值复制给本地变量,在子程序退出时,则复制output或inout的值。除了标量以外,没有任何把存储器传递给verilog子程序的方法; - 数组可以作为形式参数传递;
- 可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值。
function void print_checksum(ref bit [31:0] a[], input bit [31:0] low, 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
print_checksum(a); //a[0:size()-1]中所有元素的校验和---缺省情况
print_checksum(a, 2, 4); //a[2:4]中所有元素的校验和
print_checksum(a, 1); //a[1;$]中所有元素的校验和
print_checksum(a,,2) //a[0:2]中所有元素的校验和
- 采用名字进行参数传递;
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); //a b c d的值分别为 6 7 8 9
many(); //a b c d全部使用默认值
many(.c(8)); //c的值为8,其他全部用默认值
many(,6, , d(8)); // 1 6 3 8混合指定方式
end
- 需要注意的其他问题:
task sticky(ref int array[10], int a, b);
上述a和b参数类型会采用与前一个参数一致的ref类型,对简单的int变量使用ref并无必要,但编译器不会对此做出任何反应,可能连警告都没有,这是使用了一个错误的方向类型。
如果你在子程序中使用了非缺省输入类型的参数,应该明确指明所有参数的方向,如下所示:
task sticky(ref int array[10], input int a, b);
function
function的独特使用规则如下:
1、可以返回数值或不返回数值,如果返回需要使用关键词return,如果不返回,则应该声明函数为void function;
2、在systemveilog中,允许函数调用任务,但是只能在由fork…join_none语句生成的线程中调用。
void函数如下
function void print_state();
$display("@%0t: time = %s", $time);
endfunction
4、从函数返回一个数组有两种方法:
第一中方法是定义一个数组类型,然后在函数的声明中使用该类型。
typedef int fixed_array5[5];
fixed_array5 f5;
function fixed_array5 init(int start);
foreach(init[i])
init[i] = i + start;
endfunction
initial begin
f5 = init(5);
foreach(f5[i[)
$display("fd[%0d] = %0d", i, f5[i]);
end
上述代码存在的问题是,函数init创建了一个数组,给数组的值被复制到数组f5中。如果数组很大,那么可能会引起一个性能上的问题。
第二种方法是通过引用来进行数组参数的传递。
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[%0d] = %0d", i, fa[i]);
end
从函数中返回数组的最后一种方式是将数组包装到一个类中,然后返回对象的句柄。
task
task的独特使用规则如下:
1、task无法通过return返回结果,因此只能通过output, inout或者ref参数来返回;
2、任务中可以使用return,提前返回;
关于task和function使用的例题:
typedef struct {
bit [1:0] cmd;
bit [7:0] addr;
bit [31:0] data;
} trans;
function automatic void cp_copy(trans s, trans t);
t = s;
endfunction
initial beign
trans s, t;
s.cmd = 'h1;
s.addr = 'h10;
s.data = 'h100;
op_copy(t, s);
t.cmd = 'h2;
end
//result
t.cmd = 'h2;
t.addr = 'h0;
t.data = 'h0;
task的调用方法:
logic red;
parameter red_tics = 100;
always begin //控制灯光
red = on; //打开红灯
light(red,red_tics) ; //等待end
//等待‘tics’时钟上升沿的任务
//在关掉‘color’灯之前
task light (output color,input [31:0] tics) ;
repeat (tics)@ (posedge clock);
color = off;//关灯
endtask: light
区别
task:只能在procedural 内部调用,task的调用方式是statement本身,而不是表达式。
function:可以在module的任何地方调用,是作为一种operands来使用。
task
(1)可以有0个或者多个任何类型的输入输出端口。
(2)任务中可以有延时语句、敏感事件控制语句等事件控制语句。(repeat(5) @posedge(clk) 等待5个上升沿时钟)
(3)任务可以没有返回值,也可以通过输出端口或者双端输出端口有1个或多个返回值。
(4)任务可以调用任务和函数也能调用任务本身。
(5)任务中不能出现always和initial块。
(6)任务中允许出现disable中断任务,程序会回到引用任务的地方继续下去。
function有比较多的约束条件:
(1)函数能调用函数但是不能调用任务。
(2)函数只能返回一个值,不能有任意类型的output和inout类型端口。
(3)不能使用任何延时,即任何用#、@、或wait来标识的语句。
(4)至少有一个输入变量,可以有多个输出变量。
(5)函数一定不能包含任何延迟、事件或者时序控制声明语句。
function声明返回值时,如果不定义返回值类型或范围则默认是1位寄存器。
function和task内部的局部变量是放在寄存器中是静态变量,即多次调用不改变的
至于task和funciton能不能综合的问题
函数 | 任务 |
---|---|
函数能调用另一个函数,但不能调用另一个任务 | 任务能调用另一个任务,也能调用另一个函数 |
函数总是在仿真时刻0就开始执行 | 任务可以在非0仿真时刻执行 |
函数一定不能包含任何延迟、时间或者时序控制声明语句 | 任务可以包含延时、时间或者时序控制声明语句 |
函数至少有一个输出变量,可以有多个输入变量 | 任务可以没有或者有多个输入(input)、输出(output)和双向(inout)变量 |
函数只能返回一个值,函数不能有输出或者双向变量 | 任务不返回值,任务可以通过输出或者双向变量传递多个值 |
函数不能单独作为一条语句出现,它只能以语句的一部分的形式出现 | 任务调用只能出现在过程块中 |
函数调用可以出现在过程块或连续赋值语句中 | 任务调用只能出现在过程块中 |
函数的执行不允许由disable语句进行中断 | 任务的执行可以由disable语句进行中断 |