在 Verilog HDL 语言中,任务(task)和函数(function)可以把一个大的程序模块分解成许多小的任务和函数,以方便调试,使写出的程序结构更清晰。
任务(task)定义格式如下:
task <任务名>; //注意无端口列表
端口及数据类型声明;
其他语句;
endtask
函数(function)定义格式如下:
function <返回值位宽或类型说明> 函数名;
端口及数据类型声明;
其他语句;
endfunction
<返回值位宽或类型说明> 是一个可选项,如果缺省,则返回一个一位寄存器类型的数据。
任务与函数的区别
比较项目 | 任务(task) | 函数(function) |
输入与输出 | 可有任意个各种类型的参数 | 至少有一个输入,不能将inout类型作为输出 |
调用 | 任务只可在过程语句中调用,而不能在连续赋值语句中调用assign | 函数可作为表达式中的一个操作数来调用,在过程赋值和连续赋值语句中均可调用 |
定时和事件控制(#,@和wait) | 任务可以包含定时和事件控制语句 | 函数不能包含这些语句 |
调用其他任务和函数 | 任务可以调用其他任务和函数 | 函数可调用其他函数,但不可以调用其他任务 |
返回值 | 任务不向表达式返回值 | 函数向调用它的表达式返回一个值 |
下面通过一个例子来说明任务和函数如何使用。
module task_function(
in1,
in2,
in3,
in4,
out1,
out2
);
input [7:0] in1,in2,in3,in4;
output reg [8:0] out1,out2;
//任务
task add1; //注意无端口列表
input [7:0] a,b; //输入端口和数据类型声明
output reg [8:0] out1; //输出端口和数据类型声明
out1 = a + b; //a,b,out1名称的作用范围仅为task任务内部
endtask
//调用任务
always@(in1 or in2) begin
add1(in1,in2,out1); //调用任务,注意端口列表的顺序要和任务中定义的一致
end
//函数
function [8:0] add2; //函数定义 函数会自动生成一个和函数名同名的内部变量,作为函数返回值所用的寄存器。
input [7:0] c,d; //输入端口声明 函数只有输入端口,输出端口为函数名本身。
add2 = c + d; //返回输出值 输出寄存器为和函数同名的内部寄存器
endfunction
//调用函数
always @(in3 or in4) begin
out2 = add2(in3,in4); //调用函数,注意端口列表的顺序要和函数中定义的一致
end
endmodule
分别在任务和函数中实现两个数相加的操作,在调用任务时,需要向任务中传递输入输出端口。在调用函数的时候,只需要传递输入信号,函数的返回值作为输出信号。
下面编写测试代码
`timescale 1ns/1ns
module task_function_tb;
reg [7:0] in1,in2,in3,in4;
wire [8:0] out1,out2;
task_function task_function(
.in1 (in1),
.in2 (in2),
.in3 (in3),
.in4 (in4),
.out1 (out1),
.out2 (out2)
);
integer i;
initial begin
for(i = 0;i < 100;i = i + 1) begin
in1 = i;
in2 = i;
#5;
end
end
integer j;
initial begin
for(j = 100;j < 200;j = j + 1) begin
in3 = j;
in4 = j;
#5;
end
#100;
$stop;
end
initial $monitor($time,,,"in1=%d in2=%d ou1=%d in3=%d in4=%d out2=%d",in1,in2,out1,in3,in4,out2);
endmodule
in1和in2的值从0增加到100,in3和in4的值从100增加到200。并将输入值和输出值打印出来。
打印结果如下
输出波形如下:
通过上面例子可以看出,任务和函数在使用的时候方法基本相同。唯一差别就是输出端口输出方式不一样,可以根据情况选择适合的方式。