欢迎大家关注我的微信公众账号,支持程序媛写出更多优秀的文章
任务和函数也属于过程块,多用于仿真文件设计中,使用两者的目的有所区别:
函数(function):对输入的值执行一些处理,返回一个新的值。
因此至少有一个input类型的参数,不能有inout或output类型的参数。
函数在一个仿真时间单位内执行完毕,因此不能包含任务、不能使用非阻塞赋值。
使用上都是把函数作为表达式中的一个操作数。
任务(task):其实作用与module差不多,只是能在过程块中调用,实现的功能比函数更加广泛。
任务可以包含时序控制语句,也可以调用其它任务和函数;
可以使用任意类型的参数,也可以没有参数。
将一个数据按位取反后得到一个新的数据,
将这个功能用任务的形式实现,调用时形式如下:
switch_bytess(old_word, new_word);
将这个功能用函数的形式实现,调用时形式如下:
new_word = switch_bytess(old_word);
接下来分别介绍任务和函数的一些用法,再给出Verilog支持的系统任务和系统函数。
1 函数(function)
函数用作表达式中的一个操作数。一个函数的声明架构如下:
1 function 返回值位宽或类型说明 函数名; 2 端口声明; 3 局部变量定义; 4 //函数主体 5 begin 6 语句1; 7 ..... 8 语句n; 9 end 10 endfunction
下面是一个function的例子:
1 module alu(a,b,product,result); 2 input[1:0] a,b; 3 output[3:0] product,result; 4 wire[1:0] a,b; 5 reg[3:0] product,result; 6 reg [7:0] all_result; 7 8 always@(a,b) 9 begin //注意调用函数的方法与任务不同; 10 all_result = cal(a,b); 11 product = all_result[7:4]; 12 result = all_result[3:0]; 13 end 14 15 //定义函数cal 16 function [7:0] cal; 17 input[1:0] a; 18 input[1:0] b; 19 reg[3:0] temp; 20 21 begin 22 product = a*b; 23 temp = a*a; 24 result = temp-b; 25 cal = {product,result}; 26 end 27 endfunction 28 29 endmodule
函数的默认返回值是一个标量,可以使用“[范围或类型]”将返回值设置为real、integer、time、realtime或矢量。
定义函数时,函数内部会隐式地定义一个和函数名相同的数据。函数内部可以直接使用这个数据,这个数据的值便是函数的返回值。
递归函数
verilog中的函数是不能进行递归调用的;设计模块中若某函数在两个不同的地方被同时并发调用,由于这两个调用同时对同一块地址空问进行操作,那么计算结果将是不确定的。
若在函数声明时使用了关键字automatic,那么该函数将成为自动的或可递归的.
下例说明如何定义自动函数,来完成阶乘运算:
1 //用函数的递归调用定义阶乘计算 2 module top; 3 .................. 4 5 //定义自动(递归)函数 6 function automatic integer factorial; 7 input [31 : 0] oper; 8 begin 9 if (oper >= 2) 10 factorial = factorial( oper - 1) * oper;//递归调用 11 else 12 factorial=1: 13 end 14 endfunction 15 16 integer result; 17 initial 18 begin 19 result = factorial (4); 20 end 21 endmodule
2 任务(task)
任务(task)定义与调用的格式分别如下:
1 //任务定义的格式为: 2 task 任务名 ; 3 端口及数据类型声明语句; 4 其他语句; 5 endtask 6 7 //任务调用的格式为: 8 任务名 (端口1,端口2,……)
比如下面是定义一个任务的例子:
1 //使用任务描述运算单元 2 module alu(a,b,result); 3 input[1:0] a,b; 4 output[3:0] result; 5 wire[1:0] a,b; 6 reg[3:0] result; 7 8 always@(a, b) 9 begin 10 //按照任务中定义的端口顺序调用任务 11 cal(a,b, result); 12 end 13 14 //定义任务cal 15 task cal; 16 //任务端口列表 17 input[1:0] a; 18 input[1:0] b; 19 output[3:0] result; 20 //内部定义局部变量(必须reg型) 21 reg[3:0] temp; 22 begin 23 temp = a*a; 24 result = temp-b; 25 end 26 endtask 27 endmodule
任务的定义和module差不多,只不过端口列表中的参数可以是任意数据类型。
使用“disable+任务名”可以提前终止任务的执行。
自动任务
任务在本质上是静态的,任务中的所有声明项的地址空间是静态分配的。因此,如果这个任务在模块中的两个地方被同时调用,则这两个任务调用将对同一块地址空间进行操作。操作的结果很有可能是错误的。
为了避免这个问题,Verilog通过在task关键字前面添加关键字automatic,使任务成为可重入的,这样声明的任务也称为自动任务,每次调用时,在动态任务中声明的所有模块项的存储空间都是动态分配的,每个调用都对各自独立的地址空间进行操作。这样,每个任务调用只对自己所拥有的独立变量副本进行操作.因此可以得到正确的执行结果。
1 module auto_task; 2 reg [4:0] cd_add, ef_add; 3 reg [4:0] c, d , e, f; 4 reg clk1,clk2; 5 parameter delay=1; 6 7 initial 8 begin 9 clk1=0; clk2=0; 10 c=3;d=5;e=7;f=4; 11 #20 c=2;d=4;e=8;f=10; 12 #20 $stop; 13 end 14 initial forever #4 clk1=~clk1; 15 initial forever #5 clk2=~clk2; 16 17 task automatic adder; // 任务定义 18 output [4: 0] ab_adder; 19 input [4: 0] a, b; 20 begin 21 #delay ab_adder = a + b; 22 end 23 endtask 24 25 always @(posedge clk1) 26 adder(ef_add,e,f); 27 28 always @ (posedge clk2) 29 adder(cd_add, c, d); 30 31 endmodule
3 任务task与function的区别
不同点:
任务 task | 函数 function |
通常用于调试,或对硬件进行行为描述 | 通常用于计算,或描述组合逻辑 |
可以包含时序控制(#延迟,@, wait) | 不能包含任何延迟;函数仿真时间为0 |
可以有 input,output,和inout参数 | 只含有input参数并由函数名返回一个结果 |
可以调用其他任务或函数 | 可以调用其他函数,但不能调用任务 |
共同点:
1)任务和函数必须在module内调用
2)在任务和函数中不能声明wire
3)所有输入输出都是局部寄存器
4)任务函数执行完成后才返回结果
4 小结
(1)任务和函数都用来对设计中多处使用的公共代码进行定义,使用任务和函数可以将模块分割成许多个可独立管理的子单元,增强了模块的可读性和可维护性。
(2)任务可以有任意多个输入、输入/输出(inout)和输出变量。在任务中可以使用延迟、事件和时序控制结构,在任务中可以调用其他的任务和函数;
(3)自动任务使用关键字automatic进行定义,它的每一次调用都对不同的地址空间进行操作。因此,在被多次并发调用时仍然可以获得正确的结果;
(4)函数只能有一个返回值,并且至少要有一个输入变量。在函数中不能使用延迟、事件和时序控制结构。在函数可以调用其它函数,但不能调用任务;
(5)当声明函数时,Verilog仿真器都会隐含地声明一个同名的寄存器变量,函数的返回值通过这个寄存器传递回调用处;
(6)递归函数使用关键字automatic进行定义,递归函数每一次调用都拥有不同的地址空间。因此对这种函数的递归调用和并发调用可以得到正确的结果;
(7)任务和函数都包含在设计层次中,可以通过层次名对它们进行调用。