一、函数和任务简介
在Verilog代码中,通过把代码分成小的模块或者使用任务(task)和函数(function),可把一项任务分成许多较小的、易于管理的部分,从而提高代码的可读性、可维护性和可重用性。函数和任务往往还是大的程序模块在不同地点多次用到的相同的程序段。
- 函数(function):一般用于计算,或者用来代替组合逻辑。不能包含任何延迟;函数在零时间执行。函数只有input变量,虽然没有output变量,但可以通过函数名返回一个值。可以调用其他的函数,但不可以调用任务。函数不可以包含时间控制,可综合,多用来模块化组合逻辑,方便复用、调用。
- 任务(task):一般用于编写测试模块,或者行为描述的模块。其中可以包含时间控制(如:
#
,@
,wait
),也可以包含input
,output
、inout
端口定义和参数。可以调用其他的任务或函数。任务可以包含时间控制,但加入时间控制后则该部分无法综合,所以任务常用于testbench测试模块。
二、Verilog 函数function
函数的目的是通过返回一个用于某表达式的值,来响应输入信号。适于对不同变量采取同一运算的操作。函数在模块内部定义,通常在本模块中调用,也能根据按模块层次分级命名的函数名从其他模块调用。函数的目的是返回一个用于表达式的值。
定义函数的语法
function < 返回值的类型或范围 > 函数名; // 返回值类型integer、范围 [15:0]等等 < 端口说明语句> // input xxx < 变量类型说明语句 > // reg yyy begin <语句> end end function // 返回值的类型或范围可以不定义,默认为一位寄存器类型数据
- 函数返回值:函数的定义蕴含声明了一个与函数同名的、函数内部的寄存器,其位数与定义的相同。
- 函数的调用:函数的调用是通过将函数作为表达式中的操作数来实现的
函数的使用规则
- 函数的定义不能包含任何的时间控制语句(
#
,@
,wait
来标识的语句 ),函数的实现只能是组合逻辑。 - 函数不能启动任务,定义函数至少输入一个输入参量
- 函数的定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的值,该内部变量具有和函数名一样的名字。函数的输出结果将通过这个寄存器类型变量被传递回来。
- 函数的输入变量不能像模块的端口那样列在函数名后面的括号内,需要在声明输入时把这些输入端口列出。
- 函数不能被禁用
- 函数定义只能在模块中完成,不能出现在过程块中,函数定义结构中不能出现过程块语句(
always
)
函数的使用注意点
- 函数的定义不能包含有任何的时间控制语句,也不允许使用
disable
终止语句。 - 函数在一个仿真时间单位内执行完毕,因此不能包含任务、不能使用非阻塞赋值
- 函数至少必须有一个输入端口。
- 函数不能有任何类型的输出端口(
output
端口)和双向端口(inout
端口)。 - 在一个函数内可以对其它函数进行调用,但是函数不能调用其它任务。
例子
function [8:0] sum; input [7:0] a; input [7:0] b; begin sum = a + b; end endfunction
function 本身表述的是组合电路,但是可以赋值给某个触发器。
调用形式
functionName( param1, param2,...);
参数列表的顺序必须与函数定义声明时输入的顺序相同。
函数调用的注意点
- 函数调用可以在过程块中出现,也可以在
assign
赋值语句中出现。 - 函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数。
Verilog中的函数可以被综合,但是需要注意一些限制和规则,函数必须满足以下的条件才可以被综合 - 函数必须是纯函数,即对于相同的输入始终产生相同的输出。
- 函数不能使用Verilog中的时序语句,如
always
或者initial
等 - 函数不能改变模块中任何变量的状态,不能使用reg声明变量 。( 允许使用
reg
声明变量,但是声明后就不可综合了) - 函数中使用的数据类型也必须是可以综合的,例如不能使用Verilog中的
real
或者time
等数据类型。
函数支持多文件调用
// modulea.v module modulea(); function [8:0] sum2num; input [7:0] a; input [7:0] b; begin sum2num = a + b; end endfunction endmodule
// moduleb.v module moduleb(); modulea modulea_inst(); // modulea_inst.sum2num(a,b); endmodule
三、Verilog 任务task
任务类似于一段程序,可以使设计者从设计描述的不同位置执行共同的代码段。用任务定义可以将这些共同的代码段编写成任务,从而能够在设计描述的不同位置通过任务名调用该任务。
任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。 任务可以启动其它任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的,不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
任务的定义
task taskName( input xxx , input yyy , output zzz ); begin : label reg aaaa ; wire cccc ; // statement end endtask
任务的使用注意点
- 任务中可以有时间控制语句(此时无法被综合)
- 任务可以没有或可以有一个或多个输入、输出和双向端口。
- 传递给任务的变量与任务I/O端口变量的声明次序需要相同。
- 任务可以没有返回值,也可以通过输出端口或双向端口返回一个或多个返回值。
- 任务可以调用其它的任务或函数,也可以调用该任务本身。
- 任务定义结构内不允许出现过程块(initial或always过程块)。
- 关键字disable可以用来禁止任务的执行。
- 除了在任务中定义变量,任务能够引用任务定义所在模块中声明的任何变量。
- 任务调用语句是一个过程性语句,可以出现在always或者initial语句中。
任务可以带有时序控制或者等待某些特定的事件发生,直到任务退出时,赋给输出变量的值才传递给调用的参变量。
在任务或函数中,引用父模块中声明的变量时要特别注意(即注意变量的层次命名规则)。若想在其它模块中调用任务或函数,该任务和函数中所使用的变量必须全都包含在输入/输出口列表中。
四、函数 vs 任务
4.1 automatic修饰
不要在程序的不同部分同时调用同一个任务,这是因为任务只有一组本地变量,同一时刻调用两次相同的任务将会导致错误,这种情况常发生在使用定时控制的任务中。Verilog任务中所有声明的变量地址空间都是静态分配的,因此如果在一个模块中多次调用任务时,可能会造成地址空间的冲突。
任务可以被声明为automatic类型,其内部声明的所有局部变量在每次任务调用时都进行动态分配,即在任务调用中的局部变量不会对两个单独或者并发的任务调用产生影响。而在静态(非automatic)任务中,在每次任务调用中的局部变量都使用同一个存储空间。借助关键字automatic就可以把任务指定为automatic类型。
跟任务调用一样,在模块中如果调用多次函数,也会碰到地址冲突的问题,因此也引入automatic关键字来对函数可重用性声明。没有进行可重用性声明的函数不可以多次或者递归调用,进行了可重用性声明的函数可以递归调用。
function automatic [8:0] sum2num; input [7:0] a; input [7:0] b; begin sum2num = a + b; end endfunction task automatic taskName( input xxx , input yyy , output zzz ); begin : label // 命名块结构 reg aaaa ; wire cccc ; // statement end endtask
使用关键词automatic修饰后,每一次调用都对不同的地址进行操作,因此可以多次并发调用时,也可得到正确的结果。
4.2 函数vs任务
4.2.1 共同点
- 任务和函数必须在模块中定义,其作用范围仅适用于该模块,可以在模块内多次使用。(
automatic修饰
) - 任务和函数中可以声明局部变量,如寄存器、时间、整数、实数和事件,但是不能声明线网类型的变量。
- 任务和函数中只能使用行为级语句,不能包含
always
块和initial
块。
4.2.2 不同点
- 函数能调用另一个函数,但是不能调用任务,任务可以调用另一个任务,也可以调用函数。
- 函数总是在仿真时刻0开始执行,任务可以在非零时刻开始执行
- 函数一定不能包含任何延迟,事件或者时序控制声明语句,任务可以包含延迟,事件或者时序控制声明语句
- 函数至少要有一个输入变量,也可以有多个输入变量,任务可以没有或者有多个输入,输出,输入输出变量
- 函数只能返回一个值,函数不能有输出或者双向变量,任务可以不返回任何值,或者返回多个输出或双向变量值
- 函数用于替换纯组合逻辑的Verilog代码,而任务可以代替Verilog的任何代码。
- 函数只能与主模块共同用一个仿真时间单位,任务可以定义自己的仿真时间单位
- 函数通常用于计算,或描述组合逻辑,任务通常用于调试,或对硬件进行行为描述