前言:如果程序中有一段语句需要执行多次,则重复性的语句非常多,代码会变得冗长且难懂,维护难度也很大。通过将很大的模块分解为许多小的任务和函数,任务和函数具备将重复性语句聚合起来的能力,可以在程序模块中多次调。通常任务和函数来代替重复性语句,也有效简化程序结构,增加代码的可读性。因此,task和function都是可综合的,不过综合出来的都是组合逻辑电路。
task
- 任务就是封装在task-endtask之间的一段语句
- 任务是通过调用来执行的,也只能通过调用来执行,如果定义了任务,但整个过程都没有调用它,它是不会执行的
- 调用任务时可能需要它来处理某些数据并返回操作结果,所以任务应当有接受数据的输入端和返回数据的输出端
- 任务可以彼此调用,任务还可以调用函数,可以启动的任务数是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回
- 任务内部有定时控制,则启动的时间可以与控制返回的时间不同
task定义
定义任务的语法如下:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
…
<语句n>
endtask
在定义任务时,有下列六点需要注意:
1、在第一行task语句中不能列出端口名称。
2、任务的输入、输出和双向端口数量不受限制,甚至可以没有输入、输出和双向端口。
3、在任务定义的描述语句中,可以出现不可综合操作符合语句,但这样会造成任务不可综合。
4、在任务中可以调用其他的任务或函数,也可以调用自身。
5、在任务定义结构中不可出现initial和always语句。
6、在任务定义中可以出现“disable中止语句“,将中断正在执行的任务,但其是不可综合的。当任务被中断后,程序流程将返回调用任务的地方继续执行。
task定义的举例1:
task my_task;
input a, b; //输入
inout c; //双向
output d, e; //输出
…
<语句> //执行任务工作相应的语句
…
c = foo1; //赋初始值
d = foo2; //对任务的输出变量赋值t
e = foo3;
endtask
task定义的举例2:
task light;
input[31:0] tics; //输入
output color; //输出
begin
repeat(tics) @(posedge clock); //等待tics个时钟的上升沿
color=off; //关灯
end
endtask
task调用
<任务名>(端口1,端口2,…,端口n);
在调用task时,必须要注意一下几点:
1、task的调用是过程性语句,因此只能出现在always过程块和initial过程块中,调用task的输出与输入参数必须是reg类型的
2、task调用语句中的列表必须与task定于时的输入,输出,双向端口参数说明的顺序相匹配
3、在调用task时,参数按照值传递,而不能按照地址传递
4、在一个task中,可也直接访问上一级调用模块中的任何寄存器
5、可以使用循环中断控制语句disable来终端任务执行,当task被中断后,程序流程将返回调用任务的地方继续执行。
下面的例子说明定义的任务如何调用:
task 1 的调用:
my_task(v,w,x,y,z);
任务调用变量(v,w,x,y,z)和任务定义的I/O变量(a,b,c,d,e)之间是一一对应的。当任务启动时,由v,w,和x.传入的变量赋给了a,b,和c,而当任务完成后的输出又通过c,d和e赋给了x,y和z。
下面是一个具体的例子用来说明怎样在模块的设计中使用任务,使程序容易读懂,也就是task 2 的调用:
module traffic_lights;
reg clock, red, amber, green;
parameter on=1, off=0, red_tics=350,
amber_tics=30,green_tics=200;
//交通灯初始化
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(color,tics);
output color;
input[31:0] tics;
begin
repeat(tics) @(posedge clock);//等待tics个时钟的上升沿
color=off;//关灯
end
endtask
//产生时钟脉冲的always块
always
begin
#100 clock=0;
#100 clock=1;
end
endmodule
function
- 函数定义是嵌入在关键字function和endfunction之间的,其中关键词function标志着一个函数定义结构的开端,endfunction标志着一个函数定义结构的结束。
- “<函数名>”是给被定义函数取的名称。这个函数名在函数定义结构内部还代表着一个内部变量,函数调用后的返回值是通过这个函数名变量传递给调用语句的。
- 函数的目的是返回一个用于表达式的值。
function定义
function <返回值的类型或范围> (函数名);
<输入端口说明语句>
<变量类型说明语句>
begin
<语句>
…
end
endfunction
在定义function时要注意:
(1) function定义结构不能出现在任何一个always块或者initial过程块中,与task定义相同
(2) 不允许输出output端口声明(包括输出和双向端口) ,但可以有多个input输入端口
(3) [range]参数指定函数返回值的类型或位宽,是一个可选项,若没有指定,默认缺省值为宽度 1 bit的寄存器数据
(4) function_name为所定义函数的名称,对函数的调用也是通过函数名完成的,并在函数结构体内部代表一个内部变量,函数调用的返回值就是通过函数名变量传递给调用语句。函数定义在函数内部会隐式定义一个寄存器变量,该寄存器变量和函数同名并且位宽也一致,函数通过在函数定义中对该寄存器的显式赋值来返回函数计算结果。
(5) input_declaration为各个输入端口的位宽和类型进行说明,在函数定义中至少要有一个输入端口
(6) function定义不能包括有任何时间控制语句,即任何#,@或者wait来标识的语句,和task不同
function定义举例1
其中getbyte所赋予的值就是函数的返回值
function [7:0] getbyte;
input [15:0] address;
begin
<说明语句> //从地址字中提取低字节的程序
getbyte = result_expression; //把结果赋予函数的返回字节
end
endfunction
function定义举例2
下面的例子中定义了一个可进行阶乘运算的名为factorial的函数,该函数返回一个32位的寄存器类型的值,该函数可后向调用自身,并且打印出部分结果值。
function [31:0] factorial;
input [3:0] operand;
reg [3:0] index;
begin
factorial = operand ? 1 : 0; //1的阶乘为1,2的阶乘为2*1,3的阶乘为3*2*1
for(index = 2; index <= operand; index = index + 1)
factorial = index * factorial;
end
endfunction
function定义举例3
例:统计输入数据中“0”的个数
function[3:0] out0;
input[7:0] x;
reg[3:0] count;
integer i;
begin
count=0;
for(i=0;i<=7;i=i+1) begin
if(x[i]==1’b0)
count = count+1;
else
count = count;
end
out0=count;
end
endfunction
function调用
函数的调用是通过将函数作为表达式中的操作数来实现的。
其调用格式如下:
<函数名> (<表达式><,<表达式>>*)
在调用function时,要注意一下几点:
1、 函数的调用可以在过程块中完成,也可以在assign这样的连续赋值语句中出现,这与task有些许不同
2、函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数
3 、函数可以调用函数,但是不能调用任务
4、函数定义中声明的所有局部寄存器都是静态的,即函数中的局部寄存器在函数的多个调用之间保持它们的值。
调用实例1:
下面的例子中通过对两次调用函数getbyte的结果值进行位拼接运算来生成一个字。
assign word = control? {getbyte(msbyte),getbyte(lsbyte)} : 0;
调用实例2:
module tryfact;
//函数定义------------------------------
function [31:0] factorial;
input [3:0] operand;
reg [3:0] index;
begin
factorial = operand ? 1 : 0;
for(index = 2; index <= operand; index = index + 1)
factorial = index * factorial;
end
endfunction
//函数的测试----------------------------------
reg [31:0] result;
reg [3:0] n;
initial begin
result = 1;
// 0的阶乘为1, 1的阶乘也为1
for (n = 2; n < 9; n = n + 1) begin
result = factorial(n);
$display("正整数 n= %d的阶乘为 result= %d", n, result);
end
$display ("Finalresult = %d",result);
end
endmodule//模块结束
仿真结果:
其中阶乘自递归方式实现:
其中可以看到function的定义中添加了automatic的关键字。
在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。
Verilog 用关键字 automatic来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。
module tryfact;
//函数定义------------------------------
function automatic integer factorial;
input [3:0] operand; //4*8 = 32bit
begin
if(operand >= 2)
factorial = operand * factorial(operand - 1);
else
factorial = 1
end
endfunction
//函数的测试----------------------------------
integer result;
reg [3:0] n;
initial begin
result = 1;
// 0的阶乘为1, 1的阶乘也为1
for (n = 2; n < 9; n = n + 1) begin
result = factorial(n);
$display("正整数 n= %d的阶乘为 result= %d", n, result);
end
end
endmodule//模块结束
仿真结果:
task和function的区别
参考
https://zhuanlan.zhihu.com/p/72083112
https://blog.csdn.net/u010668547/article/details/80338477
https://blog.csdn.net/gsjthxy/article/details/103701716
https://blog.csdn.net/weixin_44502896/article/details/104911518
https://www.runoob.com/w3cnote/verilog-function.html