参考书籍:《Verilog HDL 数字设计与综合》第二版,本文档为第8章的学习笔记。
在行为级建模时很多不同的地方会实现相同的功能,有必要将相同部分取出来,将其组成子程序,然后其他地方调用。Verilog也提供了任务和函数可以将较大的行为级设计划分为较小的代码段,允许多个地方使用。
任务和函数也可以通过层次名进行引用。
任务和函数必须在模块内部进行定义,其作用范围仅局限于定义他们的模块。
任务用于代替普通Verilog代码,可包括延迟、时序、事件等语法结构,且可有多个输出变量;
函数用于表示纯组合逻辑Verilog代码,在仿真时刻0开始执行,只能有一个输出,因此一般完成各类转换和常用的计算。
任务和函数中声明局部变量(寄存器、时间、整数、实数和事件),但不能声明线网型变量。只能使用行为级语句,但是不能包含always和initial块。但是在always和initial块以及其他任务和函数中调用任务和函数。
任务关键字:task和endtask进行声明。如果子程序满足下面任意一个条件,则必须使用任务而不能使用函数:
可以使用input、output、inout来规定的任务的输入输出变量。
任务可在模块、其他任务或函数中调用。
//在模块中调用任务实例
module op();
...
...
parameter delay = 10;
reg [15:0] A, B;
reg [15:0] AB_AND, AB_OR, AB_XOR;
always @(A or B)
begin
oper(AB_AND, AB_OR, AB_XOR, A, B);
end
...
...
//定义任务
task oper;
output [15:0] ab_and, ab_or, ab_xor;
input [15:0] a, b;
begin
#delay ab_and = a & b;
ab_or = a | b;
ab_xor = a ^ b;
end
endtask
...
endmoudle
//ANSI C风格编程
task oper(output [15:0] ab_and, ab_or, ab_xor,
input [15:0] a, b
);
begin
#delay ab_and = a & b;
ab_or = a | b;
ab_xor = a ^ b;
end
endtask
module sequence
...
reg clock;
...
initial
init_sequence; //启动init_sequence任务
...
always
begin
asy_sequence;
end
...
...
task init_sequence;
begin
clock = 1'b0;
end
endtask
//时钟操作
task asy_sequence;
begin
#20 clock = 1'b1;
#20 clock = 1'b0;
end
endtask
...
endmodule
任务本身上是静态的,任务中所有声明项的地址空间是静态分配的,同时并发执行的多个任务共享这些存储区。因此,如果任务在同一模块中两个地方被同时调用,则这两个任务调用将对同一地址空间操作,因此有可能出现错误。
为避免上述问题,在task关键字前面添加automatic关键字,使任务成为可重入的,这样声明的任务称为自动任务。这样在每次调用任务时,存储空间是动态分配的,每个调用都对各自独立的地址空间进行操作。
//在模块中调用任务实例
module op();
...
...
parameter delay = 10;
reg [15:0] A, B;
reg [15:0] AB_AND, AB_OR, AB_XOR;
always @(A)
begin
oper(AB_AND, AB_OR, AB_XOR, A, B);
end
always @(B)
begin
oper(AB_AND, AB_OR, AB_XOR, A, B);
end
...
...
//定义任务
task automatic oper;
output [15:0] ab_and, ab_or, ab_xor;
input [15:0] a, b;
begin
#delay ab_and = a & b;
ab_or = a | b;
ab_xor = a ^ b;
end
endtask
...
endmoudle
函数关键字:fuction和endfuction进行声明。如果子程序满足下面条件全部成立,则可以使用函数来完成:
函数中多为组合逻辑表达
函数具有的特点:
当函数声明时,在Verilog的内部隐含地声明了一个名为function_identifier(函数标识符)的寄存器类型变量,函数的输出结果通过这个寄存器类型变量被传回。可选项rang_or_type(类型或范围)说明了内部寄存器的位宽。如果没有指定返回值的类型或位宽,则默认位宽为1。
由于函数自带function_identifier,包含了函数返回值,因此函数时没有输出变量的。另外函数中不能调用任务,只能调用其他函数。
//定义一个包含奇偶校验函数的模块
module par;
...
reg [31:0] addr;
reg parity;
always @(addr)
begin
parity = calc_parity(add);
$display("calc_parity = %b", calc_parity(add));
end
...
function calc_parity(input [31:0] address);
begin
calc_parity = ^address; //返回所有地址位的异或值
end
endfunction
...
endmodule
//定义一个含有移位函数的模块
module shifter;
...
`define LEFT 1'b0
`define RIGHT 1'b1
reg [31:0] addr, left_addr, right_addr;
reg control;
always @(addr)
begin
end
...
function [31:0] shift(
input [31:0] address,
input control
);
begin
shift = (control == `LEFT) ? (address<<1) : (address>>1);
end
endfunction
...
endmoudle
函数是不可以递归调用的,在一个模块中不能两次调用,调用时使用同一地址,导致结果不确定。
因此使用关键字automatic,可动态分配地址。但是自动函数中的局部变量不可以通过层次名进行访问,但是函数名可以。
function automatic integer factorial; //与任务类似这里不再进行代码举例
实际上是一个带有某些限制的常规函数。能用来引用复杂的值,因此可以用来代替常量。
//定义一个RAM类型
module ram();
parameter RAM_DEPTH 256;
input [clogb2(RAM_DEPTH) - 1 : 0] addr_bus;
//通过函数得到clogb2(RAM_DEPTH)=8
//常量函数
fucntion integer clogb2(input integer depth); //integer:声明整数寄存器类型
begin
for(clogb2=0; depth>0; clogb2=clogb2+1)
depth = depth >> 1;
end
endfuntion
...
endmodule
module top;
function signed [63:0] compute_signed(input [63:0] vector);
...
endfunction
if(compute_signed(vector) < -3)
...
endmodule