task和function说明语句分别用来定义任务和函数,利用任务和函数可以把一个很大的程序模块分解成许多较小的任务和函数便于理解和调试。输入、输出和总线信号的值可以传入、传出任务和函数。任务和函数往往在大的程序模块中和在不同位置多次用到的相同的程序段。
1. task说明语句
任务在模块中任意位置定义,并在模块内任意位置引用,作用范围也局限于此模块。
任务特点:
1)任务可以包含时序控制,即延时;
2)任务可以定义自己的仿真时间;
3)任务可以调用其他任务和函数;
4)任务可以没有参变量或者有一个或多个参变量;
5) 任务调用语句是一个过程性语句,可以出现在always或initial语句中;
6)对 output 信号赋值时不需要用关键字 assign。为避免时序错乱,建议 output 信号采用阻塞赋值。
7)进行任务的逻辑设计时,可以把 input 声明的端口变量看做 wire 型,把 output 声明的端口变量看做 reg 型。但是不需要用 reg 对 output 端口再次说明。
8)任务调用时,端口必须按顺序对应。输入端连接的模块内信号可以是 wire 型,也可以是 reg 型。输出端连接的模块内信号要求一定是 reg 型。
9)任务中所有声明的变量地址空间都是静态分配的,因此如果在一个模块中多次调用任务时,可能会造成地址空间的冲突,这也意味着可以通过再次调用的方式重置任务,可重入任务使用关键字automatic进行定义,每一次调用都对不同的地址空间进行操作。因此在被多次并发调用时,可以获得正确结果。
- 任务语法:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
.
.
.
<语句n>
endtask
- 任务调用:
<任务名>(端口1,端口2,···,端口n);
- 使用举例
//示例:红绿交通灯,行为模块,不能综合成电路网表
module traffic_lights;
reg clock,red,amber,green;
parameter on = 1'b1,off = 1'b0,red_tics = 'd350,amber_tics = 'd30,green_tics = 'd200;
// 初始化交通灯
initial alock = 0;
initial red = off;
initial amber = off;
initial green = off;
//交通灯时序控制
always
begin
red = on;
light(red,red_tics);
green = on;
light(green,green_tics);
amber = on;
light(amber,amber_tics);
end
//定义交通灯开启时间的任务
task light;
output color;
input [31:0]tics;
begin
repeat(tics)
@(posedge clock);
color = off;
end
endtask
//产生时钟脉冲的always块
always #100 clock = ~clock;
endmodule
2. function说明语句
函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。
函数主要有以下几个特点:
1)不含有任何延迟、时序或时序控制逻辑,即不含有非阻塞赋值语句,以及任何用#、@或wait来标识的语句;
2)函数只能与主模块共用同一个仿真时间单位;
3)至少有一个输入变量;
4)只有一个返回值,且没有输出;
5)函数可以调用其他函数,但是不能调用任务;
6)函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个寄存器变量进行传递回调用处。当该寄存器变量没有指定位宽时,默认位宽为 1。
- 几种特殊函数说明
automatic函数
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。
常数函数
指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。这种函数能够用来引用复杂的值,因此可用来代替常量。
符号函数
带符号函数的返回值可以作为带符号数进行运算,用关键字signed对函数进行说明。
- 函数语法
function <返回值的类型或宽度>(函数名);
<端口及数据类型声明语句>
begin
<语句1>
<语句2>
.
.
.
<语句n>
end
endfunction
- 函数调用
<函数名> (<表达式>,···<表达式>)
- 使用举例
//自动递归函数
//用函数的递归调用定义阶乘计算
module top;
//定义自动递归函数
function automatic integer factorial;
input [31:0] oper;
integer i;
begin
if(operand >= 2)
factorial = factorial(oper -1) * oper; //递归调用
else
factorial = 1;
end
endfunction
//调用函数
integer result;
initial
begin
result = factorial (4);
$display("Factorial of 4 is %0d ",result);
end
endmodule
//常量函数
module ram( ... );
parameter RAM_DEPTH = 256;
input [clogb2(RAM_DEPTH)-1:0] addr_bus;
...
function integer clogb2(input integer depth);
begin
for(clogb2 = 0;depth >0;clogb2 = clogb2 +1)
depth = depth >> 1;
end
endfunction
...
endmodule
3. 任务和函数的比较
任务和函数都包含在设计层次之中,可以通过层次名进行调用,但函数一般用于组合逻辑的各种转换和计算,而任务更像一个过程,不仅能完成函数的功能,还可以包含时序控制逻辑。下面对任务与函数的区别进行概括:
比较点 | 函数 | 任务 |
---|---|---|
输入 | 函数至少有一个输入,端口声明不能包含 inout 型 | 任务可以没有或者有多个输入,且端口声明可以为 inout 型 |
输出 | 函数没有输出 | 任务可以没有或者有多个输出 |
返回值 | 函数至少有一个返回值 | 任务没有返回值 |
仿真时刻 | 函数总在零时刻就开始执行 | 任务可以在非零时刻执行 |
时序逻辑 | 函数不能包含任何时序控制逻辑 | 任务不能出现 always 语句,但可以包含其他时序控制,如延时语句 |
调用 | 函数只能调用函数,不能调用任务 | 任务可以调用函数和任务 |
书写规范 | 函数不能单独作为一条语句出现,只能放在赋值语言的右端 | 任务可以作为一条单独的语句出现语句块中 |
模块内子程序出现下面任意一个条件时,则必须使用任务而不能使用函数。
1)子程序中包含时序控制逻辑,例如延迟,事件控制等;
2)没有输入变量;
3)没有输出或输出端的数量等于1;