目录
1、概述
2、任务 task
2.1、任务的定义
2.2、一个task例子
3、函数 function
3.1、函数的定义
3.2、一个function例子
4、任务与函数的异同
5、总结与参考
与C语言中的函数类似,在Verilog代码中,通过把代码分成小的模块或者使用任务(task)和函数(function),可把一项任务分成许多较小的、易于管理的部分,从而提高代码的可读性、可维护性和可重用性。
任务(task):一般用于编写测试模块,或者行为描述的模块。其中可以包含时间控制(如:# delays, @, wait);也可以包含input, output 、inout 端口定义和参数;也可以调用其他的任务或函数。任务可以包含时间控制,但加入时间控制后则该部分无法综合,所以任务常用于testbench测试模块。
函数(function):一般用于计算,或者用来代替组合逻辑。不能包含任何延迟;函数在零时间执行。函数只有input变量,虽然没有output变量,但可以通过函数名返回一个值。可以调用其他的函数,但不可以调用任务。函数不可以包含时间控制,所以可综合,多用来模块化组合逻辑,方便复用、调用。
Tips:
如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。
任务可以启动其它任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的,不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
任务的语法为:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
...
<语句n>
endtask
调用任务并传递输入/输出变量的声明语句的语法如下:
<任务名> (端口1,端口2,...,端口n);
任务定义如下所示:
task my_task;
input a,b;
input c;
output d,e;
...
<语句> //执行任务工作相应的语句
...
c = foo1; //赋初始值
d = foo2; //对任务的输出变量赋值
e = foo3; //
endtask
任务调用为:
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。
任务的使用有以下需要注意:
下面的模块描述了交通灯的运作,其中定义和调用了任务。
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
always begin //产生时钟脉冲的always块
#100 clock = 0;
#100 clock = 1;
end
//定义交通灯开启时间的任务
task light(color,tics);
output color;
input [31:0] tics;
begin
repeat(tics) @(posedge clock); //等待tics个时钟的上升沿
color = off;
end
endtask
endmodule
任务light的作用就是根据输入tics,来决定在多少个tics的时钟上升沿后将输入color赋值为0,即模仿红绿黄灯的不同持续时间。
在这里只是一个行为模块,由于其使用了repeat这个时间控制语句,所以该模块是无法综合成实际电路的。当然了,这里的用途本来也就是作为测试手段。
函数的目的是通过返回一个用于某表达式的值,来响应输入信号。适于对不同变量采取同一运算的操作。函数在模块内部定义,通常在本模块中调用,也能根据按模块层次分级命名的函数名从其他模块调用。
函数的语法为:
function <返回值的类型或范围> (函数名)
<端口说明语句>
<变量类型说明语句> begin
<语句>
...
end
endfunction
在这里,<返回值的类型或范围>这一项是可选项,如缺省则返回值为一位寄存器类型数据。例如:
function [7:0] getbyte;
input [15:0] address;
begin
<说明语句> //从地址字节中提取低字节的程序
getbyte = result_expression; //把结果赋予函数的返回字节
end
endfunction
函数的定义蕴含声明了与函数同名的、函数内部的寄存器。如在函数的声明语句中<返回值的类型或范围>为缺省,则这个寄存器是一位;否则是与函数定义中<返回值的类型或范围>一致的寄存器。
函数的定义把函数返回值赋值寄存器的名称初始化为与函数同名的内部变量。上面的例子说明这个概念:getbyte被赋予的值就是函数的返回值。
函数的调用是通过将函数作为表达式中的操作数来实现的,其调用格式为:
<函数名> (<表达式><,<表达式>>*)
其中函数名作为确认符。例如通过对两次调用函数getbyte的结果进行位拼接运算来生成一个字。
word = control?{getbyte(msbyte),getbyte(lsbyte)}:0;
函数的使用有以下需要注意:
在Xilinx的许多源码都出现了这个简单的Function,其功能时:以2为底取对数。
// function 实现
function integer clogb2 (input integer bit_depth);
begin
for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
bit_depth = bit_depth >> 1;
end
endfunction
// 使用案例
localparam integer C_TRANSACTIONS_NUM = clogb2(C_M_AXI_BURST_LEN-1);
reg [C_TRANSACTIONS_NUM : 0] write_index;
reg [C_TRANSACTIONS_NUM : 0] read_index;
上面的代码就是定义了一个求位宽的function,用其求得某类寄存器的位宽,然后再对寄存器赋值时就直接使用求得的位宽来赋值,这样复用起来就比较方便了。
不同点:
共同点:
参考资料1:IEEE Standard for Verilog® Hardware Description Language(IEEE Std 1364™-2005)
参考资料2:Verilog数字系统设计教程(夏宇闻、韩彬)