任务与函数如果程序中如果有一段语句需要执行多次,则重复性的语句非常多,代码会变的冗长且难懂,维护难度也很大。
任务和函数具备将重复性语句聚合起来的能力,类似于C 语言的子程序。
通过任务和函数来替代重复性语句,也有效简化程序结构,增加代码的可读性。
此外,verilog 的task 和function 是可以综合的,不过综合出来的都是组合电路。
任务(TASK)语句任务就是一段封装在“task-endtask”之间的程序。
任务是通过调用来执行的,而且只有在调用时才执行,如果定义了任务,但是在整个过程中都没有调用它,那么这个任务是不会执行的。
调用某个任务时可能需要它处理某些数据并返回操作结果,所以任务应当有接收数据的输入端和返回数据的输出端。
另外,任务可以彼此调用,而且任务内还可以调用函数。
1.任务定义任务定义的形式如下:task task_id;[declaration]procedural_statementendtask其中,关键词task 和endtask 将它们之间的内容标志成一个任务定义,task 标志着一个任务定义结构的开始;task_id 是任务名;可选项declaration 是端口声明语句和变量声明语句,任务接收输入值和返回输出值就是通过此处声明的端口进行的;procedural_statement是一段用来完成这个任务操作的过程语句,如果过程语句多于一条,应将其放在语句块内;endtask 为任务定义结构体结束标志。
下面给出一个任务定义的实例。
定义一个任务。
task task_demo; //任务定义结构开头,命名为task_demoinput [7:0] x,y; //输入端口说明output [7:0] tmp; //输出端口说明if(x>y) //给出任务定义的描述语句tmp = x;elsetmp = y;endtask上述代码定义了一个名为“task_demo”的任务,求取两个数的最大值。
在定义任务时,有下列六点需要注意:(1)在第一行“task”语句中不能列出端口名称;(2)任务的输入、输出端口和双向端口数量不受限制,甚至可以没有输入、输出以及双向端口。
(3)在任务定义的描述语句中,可以使用出现不可综合操作符合语句(使用最为频繁的就是延迟控制语句),但这样会造成该任务不可综合。
(4)在任务中可以调用其他的任务或函数,也可以调用自身。
(5)在任务定义结构内不能出现initial 和always 过程块。
(6)在任务定义中可以出现“disable 中止语句”,将中断正在执行的任务,但其是不可综合的。
当任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
2.任务调用虽然任务中不能出现initial 语句和always 语句语句,但任务调用语句可以在initial 语句和always 语句中使用,其语法形式如下:task_id[(端口1, 端口2, ........, 端口N)];其中task_id 是要调用的任务名,端口1、端口2,…是参数列表。
参数列表给出传入任务的数据(进入任务的输入端)和接收返回结果的变量(从任务的输出端接收返回结果)。
任务调用语句中,参数列表的顺序必须与任务定义中的端口声明顺序相同。
任务调用语句是过程性语句,所以任务调用中接收返回数据的变量必须是寄存器类型。
下面给出一个任务调用实例。
例子:通过Verilog HDL 的任务调用实现一个4 比特全加器。
module EXAMPLE (A, B, CIN, S, COUT);input [3:0] A, B;input CIN;output [3:0] S;output COUT;reg [3:0] S;reg COUT;reg [1:0] S0, S1, S2, S3;task ADD;input A, B, CIN;output [1:0] C;reg [1:0] C;reg S, COUT;beginS = A ^ B ^ CIN;COUT = (A&B) | (A&CIN) | (B&CIN);C = {COUT, S};endendtaskalways @(A or B or CIN) beginADD (A[0], B[0], CIN, S0);ADD (A[1], B[1], S0[1], S1);ADD (A[2], B[2], S1[1], S2);ADD (A[3], B[3], S2[1], S3);S = {S3[0], S2[0], S1[0], S0[0]};COUT = S3[1];endendmodule上述代码正确实现了加法器的功能,达到了设计目的。
在调用任务时,需要注意以下几点:(1)任务调用语句只能出现在过程块内;(2)任务调用语句和一条普通的行为描述语句的处理方法一致;(3)当被调用输入、输出或双向端口时,任务调用语句必须包含端口名列表,且信号端口顺序和类型必须和任务定义结构中的顺序和类型一致。
需要说明的是,任务的输出端口必须和寄存器类型的数据变量对应。
(4)可综合任务只能实现组合逻辑,也就是说调用可综合任务的时间为“0”。
而在面向仿真的任务中可以带有时序控制,如时延,因此面向仿真的任务的调用时间不为“0”。
函数(FUNCTION)语句函数的功能和任务的功能类似,但二者还存在很大的不同。
在Verilog HDL 语法中也存在函数的定义和调用。
1.函数的定义函数通过关键词function 和endfunction 定义,不允许输出端口声明(包括输出和双向端口),但可以有多个输入端口。
函数定义的语法如下:function [range] function_id;input_declarationother_declarationsprocedural_statementendfunction其中,function 语句标志着函数定义结构的开始;[range]参数指定函数返回值的类型或位宽,是一个可选项,若没有指定,默认缺省值为1 比特的寄存器数据;function_id 为所定义函数的名称,对函数的调用也是通过函数名完成的,并在函数结构体内部代表一个内部变量,函数调用的返回值就是通过函数名变量传递给调用语句;input_declaration 用于对函数各个输入端口的位宽和类型进行说明,在函数定义中至少要有一个输入端口;endfunction为函数结构体结束标志。
下面给出一个函数定义实例。
例:定义函数实例。
function AND;//定义输入变量input A, B;//定义函数体beginAND = A && B;endendfunction函数定义在函数内部会隐式定义一个寄存器变量,该寄存器变量和函数同名并且位宽也一致。
函数通过在函数定义中对该寄存器的显式赋值来返回函数计算结果。
此外,还有下列几点需要注意:(1)函数定义只能在模块中完成,不能出现在过程块中;(2)函数至少要有一个输入端口;不能包含输出端口和双向端口;(3)在函数结构中,不能使用任何形式的时间控制语句(#、wait 等),也不能使用disable 中止语句;(4)函数定义结构体中不能出现过程块语句(always 语句);(5)函数内部可以调用函数,但不能调用任务。
2.函数调用和任务一样,函数也是在被调用时才被执行的,调用函数的语句形式如下:func_id(expr1, expr2, ........., exprN)其中,func_id 是要调用的函数名,expr1, expr2, ......exprN 是传递给函数的输入参数列表,该输入参数列表的顺序必须与函数定义时声明其输入的顺序相同。
下面给出一个函数调用实例。
例:函数调用实例。
module comb15 (A, B, CIN, S, COUT);input [3:0] A, B;input CIN;output [3:0] S;output COUT;wire [1:0] S0, S1, S2, S3;function signed [1:0] ADD;input A, B, CIN;reg S, COUT;beginS = A ^ B ^ CIN;COUT = (A&B) | (A&CIN) | (B&CIN);ADD = {COUT, S};endendfunctionassign S0 = ADD (A[0], B[0], CIN),S1 = ADD (A[1], B[1], S0[1]),S2 = ADD (A[2], B[2], S1[1]),S3 = ADD (A[3], B[3], S2[1]),S = {S3[0], S2[0], S1[0], S0[0]},COUT = S3[1];endmodule上述程序正确实现了加法器的功能,达到了设计目的。
在函数调用中,有下列几点需要注意:(1)函数调用可以在过程块中完成,也可以在assign 这样的连续赋值语句中出现。
(2)函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数。
任务和函数的深入理解通过任务和函数可以将较大的行为级设计划分为较小的代码段,允许Verilog HDL 程序开发人员将在多个地方使用的相同代码提取出来,简化程序结构,提高代码可读性。
一般的综合器都是支持了task 和function 语句的。
1.关于task 语句的深入说明根据Verilog HDL 语言标准上看来,task 比always 低1 个等级,即task 必须在always里面调用,task 本身可以调用task,但不能调用Verilog HDL 模块(module)。
module 的调用是与always、assign 语句并列的,所以在这些语句中均不能直接调用module,只能采用和module 端口交互数据的方法达到调用的功能。
task 语句是可综合的,但其中不能包含always 语句,因此也只能实现组合逻辑。
顺序调用task 对于电路设计来说,就是复制电路功能单元。
多次调用task 语句就是多次复制电路,因此资源会成倍增加,不能达到电路复用的目的;同时用task 封装的纯逻辑代码会使得电路的处理时间变长,最高频率降低,不能应用于高速场合。
综上所述,可以看出task 语句的功能就是将代码中重复的组合逻辑封装起来简化程序结构,具备组合逻辑设计的所有优点和缺点;而对于时序设计,task 语句则无法处理,只能通过Verilog HDL 语言中的层次化设计方法,将其封装成module,通过端口交换数据达到化简程序结构的目的。
2.关于function 语句的深入说明在面向综合的设计中,function 语句是可综合的,但由于function 语句中不支持使用always 语句,因此无法捕获信号跳变沿,所以不可能实现时序逻辑。