task和function说明语句分别用来定义任务和函数。
利用任务和函数可以把一个很大的程序模块分解成许多较小的任务和函数便于理解和调试。
输入、输出和总线信号的值可以传入、传出任务和函数。任务和函数往往还是大的程序模块中在不同地点多次用到的相同的程序段。
学会使用task和function语句可以简化程序的结构,使程序明白易懂,是编写较大型模块的基本功。
任务和函数有些不同,主要的不同有以下四点:
函数的目的是通过返回一个值来响应输入信号的值。任务却能支持多种目的,能计算多个结果值,这些结果值只能通过被调用的任务的输出或总线端口送出。Verilog HDL模块使用函数时是把它当作表达式中的操作符,这个操作的结果值就是这个函数的返回值。下面让我们用例子来说明:
例如,定义一任务或函数对一个16位的字进行操作让高字节与低字节互换,把它变为另一个字(假定这个任务或函数名为: switch_bytes)。
任务返回的新字是通过输出端口的变量,因此16位字字节互换任务的调用源码是这样的:
switch_bytes(old_word,new_word);
任务switch_bytes把输入old_word的字的高、低字节互换放入new_word端口输出。
而函数返回的新字是通过函数本身的返回值,因此16位字字节互换函数的调用源码是这样的:
new_word = switch_bytes(old_word);
如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。任务完成以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。任务可以启动其它的任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
任务的定义
定义任务的语法如下:
任务:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
.....
<语句n>
endtask
这些声明语句的语法与模块定义中的对应声明语句的语法是一致的。
任务的调用及变量的传递
启动任务并传递输入输出变量的声明语句的语法如下:
任务的调用:
<任务名>(端口1,端口2,...,端口n);
下面的例子说明怎样定义任务和调用任务:
任务定义:
task my_task;
input a, b;
inout c;
output d, e;
…
<语句> //执行任务工作相应的语句
…
c = foo1; //赋初始值
d = foo2; //对任务的输出变量赋值t
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。
任务定义时需注意以下事项:
(1)在第一行“task”语句中不能列出端口名列表。
(2)任务中可以有延时语句、敏感事件控制语句等事件控制语句。
(3)任务可以没有或可以有一个或多个输入、输出和双向端口。
(4)任务可以没有返回值,也可以通过输出端口或双向端口返回一个或多个返回值。
(5)任务可以调用其它的任务或函数,也可以调用该任务本身。
(6)任务定义结构内不允许出现过程块(initial或always过程块)。
(7)任务定义结构内可以出现disable终止语句,这条语句的执行将中断正在执行的任务。在任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
函数定义是嵌入在关键字function和endfunction之间的,其中关键词function标志着一个函数定义结构的开端,endfunction标志着一个函数定义结构的结束。
“<函数名>”是给被定义函数取的名称。这个函数名在函数定义结构内部还代表着一个内部变量,函数调用后的返回值是通过这个函数名变量传递给调用语句的。
函数的目的是返回一个用于表达式的值。
定义函数的语法:
function <返回值的类型或范围> (函数名);
<端口说明语句>
<变量类型说明语句>
begin
<语句>
........
end
endfunction
从函数返回的值
函数的定义蕴含声明了与函数同名的、函数内部的寄存器。如在函数的声明语句中<返回值的类型或范围>为缺省,则这个寄存器是一位的,否则是与函数定义中<返回值的类型或范围>一致的寄存器。
函数的定义把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。
函数的调用
函数的调用是通过将函数作为表达式中的操作数来实现的。
其调用格式如下:
<函数名> (<表达式><,<表达式>>*)
其中函数名作为确认符。
函数的使用规则
与任务相比较函数的使用有较多的约束,下面给出的是函数的使用规则:
A.在进行函数定义时需要注意以下几点:
(1)与任务一样,函数定义结构只能出现在模块中,而不能出现在过程块内。
(2)函数至少必须有一个输入端口。
(3)函数不能有任何类型的输出端口(output端口)和双向端口(inout端口)。
(4)在函数定义结构中的行为语句部分内不能出现任何类型的时间控制描述,也不允许使用disable终止语句。
(5)与任务定义一样,函数定义结构内部不能出现过程块。
(6)在一个函数内可以对其它函数进行调用,但是函数不能调用其它任务。
(7)在第一行“function”语句中不能出现端口名列表。
(8)在函数声明的时候,在Verilog HDL的内部隐含地声明了一个名为function_identifier(函数标识符)的寄存器类型变量,函数的输出结果将通过这个寄存器类型变量被传递回来。
B.函数调用时要注意以下几点:
(1)函数的调用不能单独作为一条语句出现,它只能作为一个操作数出现在调用语句内。
(2)函数的调用既能出现在过程块中,也能出现在assign连续赋值语句中。
(3)函数定义中声明的所有局部寄存器都是静态的,即函数中的局部寄存器在函数的多个调用之间保持它们的值。
最后总结一下任务与函数的区别:
=========================================================================
示例:模块(内部定义了函数)
`timescale 1ns / 1ps
module reg_test(
input clk,
input [3:0] address,
input [7:0] data_in,
output [7:0] data_out,
input write_en,
input read_en,
input resetb
);
reg [7:0] reg0,reg1,reg2,reg3;
reg [7:0] read_data;
//定义函数
function [7:0] dev_reg_nxt;
input [3:0] address;
input [3:0] reg_offset;
input write_en;
input [7:0] data_in;
input [7:0] dev_reg;
begin
dev_reg_nxt=(address==reg_offset && write_en)? data_in:dev_reg;
end
endfunction
assign data_out=read_data;
always@(posedge clk or negedge resetb)begin
if(!resetb)
begin
reg0<=8'b0000_0000;
reg1<=8'b0000_0000;
reg2<=8'b0000_0000;
reg3<=8'b0000_0000;
read_data<=8'b0000_0000;
end
else
begin
reg0<=dev_reg_nxt(address,4'b0000,write_en,data_in,reg0);//调用函数
reg1<=dev_reg_nxt(address,4'b0001,write_en,data_in,reg1);
reg2<=dev_reg_nxt(address,4'b0010,write_en,data_in,reg2);
reg3<=dev_reg_nxt(address,4'b0011,write_en,data_in,reg3);
end
end
always@(*)begin
if(read_en)begin
case(address)
4'b0000:read_data=reg0;
4'b0001:read_data=reg1;
4'b0010:read_data=reg2;
4'b0011:read_data=reg3;
endcase
end
end
endmodule
上面模块的testbench(内部定义了任务)
`timescale 1ns / 1ps
module tstbench();
reg clk,write_en,read_en,resetb;
reg [3:0] address;
reg [7:0] data_in;
wire [7:0] data_out;
reg_test test_function(
.clk(clk),
.address(address),
.data_in(data_in),
.data_out(data_out),
.write_en(write_en),
.read_en(read_en),
.resetb(resetb)
);
initial begin
clk=1'b0;
forever begin
#5 clk=~clk;
end
end
initial begin
resetb=1'b1;
#10 resetb=1'b0;
#10 resetb=1'b1;
end
initial begin
address=4'b0000;
data_in=8'b0000_0000;
write_en=1'b0;
read_en=1'b0;
end
//定义任务,此任务执行完成占用三个时钟周期
task reg_write;
input [3:0] address_task;
input [7:0] data_in_task;
begin
@(posedge clk);
#1 address=address_task;
@(posedge clk);
#1 write_en=1'b1;
data_in=data_in_task;
@(posedge clk);
#1;
write_en=1'b0;
address=4'hF;
data_in=4'h0;
end
endtask
//定义任务,此个任务执行完成占用四个时钟周期
task reg_read;
input [3:0] address_task;
input [7:0] expected_data;
begin
@(posedge clk);
#1 address=address_task;
@(posedge clk);
#1 read_en=1'b1;
@(posedge clk);
#1 read_en=1'b0;
address=4'hF;
@(posedge clk);
if(expected_data===data_out)
$display("data match:expected_data=%h ,actual data=%h ",expected_data,data_out);
else
$display("ERROR !!! data nomatch:expected_data=%h ,actual data=%h ",expected_data,data_out);
end
endtask
initial begin
#100;
reg_write(4'b0000,8'hA5);//执行写任务
reg_read(4'b0000,8'hA5);//执行读任务并与预期数据做比对
reg_write(4'b0001,8'hA2);
reg_read(4'b0001,8'hA2);
reg_write(4'b0010,8'hA3);
reg_read(4'b0010,8'hA3);
reg_write(4'b0011,8'hA1);
reg_read(4'b0011,8'hA1);
// $finish;
end
endmodule
以上的仿真波形: