转自: http://www.cnblogs.com/hechengfei/p/4104253.html
(原创)task和function语法的使用讨论(Verilog,CPLD/FPGA)
1. Abstract
function和task语句的功能有很多的相似之处,在需要有多个相同的电路生成时,可以考虑使用它们来实现。因为个人使用它们比较少,所以对它们没有进行更深的了解,现在时间比较充裕,我想通过写几个简单的电路将它们二者的功能进行验证一下,看看究竟是怎么生成电路的。
2. Contents
主要为测试function和task各自生成的电路,所以电路设计功能比较简单——4位BCD码转换成4位余3码。
文件开头的注释说明。
/* --------------------------------------- Module Name: temp Module Function: 将4位BCD码转换成为余3码,无效状态为4'b0000 Module Input: 4-bit data_in Module Output: 4-bit data_out Module Reference: None Note: 码表如下 ######################################## data_in data_out 0 4'b0000 4'b0011 1 4'b0001 4'b0100 2 4'b0010 4'b0101 3 4'b0011 4'b0110 4 4'b0100 4'b0111 5 4'b0101 4'b1000 6 4'b0110 4'b1001 7 4'b0111 4'b1010 8 4'b1000 4'b1011 9 4'b1001 4'b1100 10 4'b1010 4'b0000 无效 11 4'b1011 4'b0000 无效 12 4'b1100 4'b0000 无效 13 4'b1101 4'b0000 无效 14 4'b1110 4'b0000 无效 15 4'b1111 4'b0000 无效 ---------------------------------------*/
2.1 直接使用组合逻辑电路生成BCD码转余3码。
module temp(data_in,data_out); output reg [3:0] data_out; input [3:0] data_in; always @(data_in) begin if(data_in >= 4'd10) data_out = 4'b0000; else data_out = data_in + 4'd3; end endmodule
用RTL视图查看一下生成出来的网表
生成出来的电路由加法器、比较器和选择器构成,输入数据data_in + 3作为选择器的选择控制,下面做一下验证测试。
2.2 用task语句编写组合电路
module temp(data_in,data_out); output reg [3:0] data_out; input [3:0] data_in; always @(data_in) begin if(data_in >= 4'd10) data_out = 4'b0000; else BCD2Access3(data_out,data_in); end task BCD2Access3; output [3:0] data_out; input [3:0] data_in; data_out = data_in + 4'd3; endtask endmodule
用RTL视图查看一下最后生成的网表。
仔细对比,FIG2.3和FIG2.1是一模一样的,也就是说采用task语句可以生成预期的逻辑图,非常成功!既然生成出的逻辑图都是一样的,测试部分在此就省略了吧,和FIG2.2应该是一样的。
2.3 用function语句编写组合逻辑
module temp(data_in,data_out); output reg [3:0] data_out; input [3:0] data_in; always @(data_in) begin if(data_in >= 4'd10) data_out = 4'b0000; else data_out = BCD2Access3(data_in); end function [3:0] BCD2Access3; input [3:0] data_in; BCD2Access3 = data_in + 4'd3; endfunction endmodule
用RTL视图查看一下最后生成出来的网表。
相信已经很熟悉这个逻辑图了,与前面的FIG2.1一模一样,也就是说使用function语句也是可以生成预期的电路的,而且也是非常成功的!既然逻辑图都是与前面一致的,故测试部分……也省去了吧。
小结一下,用function和task语句都可以生成预期的逻辑电路,不过,查查语法书,可以知道task语句的适用性更广泛一点,更符合逻辑思维的习惯;function最大的好处就是可以有一个返回值,运算以后结果可以直接返回供调用的块使用。
在组合逻辑设计的过程中,写成可综合的电路可以达到预期的生成电路逻辑,那么在时序逻辑设计中,function和task语句又会生成出怎样的电路呢?还是以这一个电路功能为模板,值得注意的是,得额外加一个时钟信号。
2.4 直接使用时序逻辑编写的BCD码转余3码。
module temp(data_in,data_out,clk); output reg [3:0] data_out; input [3:0] data_in; input clk; always @(posedge clk) begin if(data_in >= 4'd10) data_out = 4'b0000; else data_out <= data_in + 3'd3; end endmodule
用RTL视图来看下生成出来的逻辑网表。
和上面生成的逻辑网表相比,多了一个锁存器,这也是典型的时序逻辑的特征,数据的变化只在clk的上升沿才有效。下面来做一下电路逻辑的验证。
图确实有点小,可能看起来有点不方便,但逻辑的功能是正确的,每到clk的上升沿,数据更新一次,译码是正确的,为了方便看,再截取一部分出来吧,作为一个局部放大图。
直接用时序逻辑设计成功了,再尝试着用task语句和function语句实现,看看最后的效果会怎么样。
2.5 时序逻辑下使用task语句实现电路
module temp(data_in,data_out,clk); output reg [3:0] data_out; input [3:0] data_in; input clk; always @(posedge clk) begin if(data_in >= 4'd10) data_out = 4'b0000; else BCD2Access3(data_out,data_in); end task BCD2Access3; output [3:0] data_out; input [3:0] data_in; data_out = data_in + 4'd3; endtask endmodule
用RTL视图来看一下生成的逻辑网表。
同样,仔细对照一下,发现FIG2.8和FIG2.6是一样的,也就是说在时序逻辑中使用task语句也是可以实现预期的电路的,非常的成功!和上述讨论的一样,既然逻辑图相同的话,验证的部分就略去了吧。
2.6 时序逻辑下用function语句实现电路
module temp(data_in,data_out,clk); output reg [3:0] data_out; input [3:0] data_in; input clk; always @(posedge clk) begin if(data_in >= 4'd10) data_out = 4'b0000; else data_out <= BCD2Access3(data_in); end function [3:0] BCD2Access3; input [3:0] data_in; BCD2Access3 = data_in + 4'd3; endfunction endmodule
跟上面一样,用RTL视图来看看最后生成出来的电路。
FIG2.9 时序逻辑下使用function语句生成的逻辑网表
仔细对比,会发现跟如上的逻辑网表一样,也就是说在时序逻辑下使用function语句也是可以实现预期的电路的,非常的成功!逻辑网表一致的话,在此逻辑的验证容我略去吧。
小结一下,在时序逻辑的电路设计中,也可以使用task语句和function语句来实现电路。
在学习过程中,也不断的在看语法书,毕竟这一块是我不太熟悉的地方,现在将它们使用的一些要点整理出来,对如何使用和最后生成怎样的很有参考帮助。
task语句要点
(1)若用于任务中的命名变量或参数没有在任务块中声明,则指的是在模块中声明的命名变量或参数。
(2)任务中的input,output和inout的个数不受限制(也可以为0个)。(任务可以没有参数)
(3)任务中的变量(包括输入(input)和双向端口(inout))可以声明为寄存器型。如果没有明确地声明,则默认为寄存器类型,且其位宽与相应的变量匹配。
(4)当启动任务时,相应于任务的输入和双向端口(inout)的变量表达式的值被存入相应的变量寄存器中。当任务结束时,输入和双向端口(inout)的变量寄存器中的值又被代入启动任务的语句中相应的表达式。
电路综合参考:包含时序控制语句的任务是不可综合的。启动的任务往往被综合成组合逻辑。
function语句要点
(1)函数必须至少有一个输入变量,不能有任何输出或输入/输出双向变量(output 和inout类型)。
(2)函数不能包含时间控制语句(如延迟#、事件控制@或者等待wait)。
(3)函数是通过对函数名赋值的途径返回其值的,就好比是一个寄存器。
(4)函数不能启动任务。
(5)函数不能被禁用。(特指应用于编写测试文件)
电路综合参考:函数的每一次调用都被综合为一个独立的组合逻辑电路块。
3.Conclusion
总体来说,task的功能更加符合硬件逻辑设计的习惯,而且语法中对它的限制比较少。而且它们最终生成的电路都是组合逻辑,所以在需要产生多个相同的模块时,可以采用由它们构成。
4.Platform
Quartus II 9.1 Build 222 Full Version
5.Reference
[1] Verilog 数字系统设计教程(第2版) 夏宇闻
[2] Advanced Digital Design with the Verilog HDL (Second Edition) Michael D.Ciletti

版权声明:本文为博主原创文章,未经博主允许不得转载。
函数的功能和任务的功能类似,但二者还存在很大的不同。在 Verilog HDL 语法中也存
在函数的定义和调用。
1.函数的定义
函数通过关键词 function 和 endfunction 定义,不允许输出端口声明(包括输出和双向
端口) ,但可以有多个输入端口。函数定义的语法如下:
function [range] function_id;
input_declaration
other_declarations
procedural_statement
endfunction
其中,function 语句标志着函数定义结构的开始;[range]参数指定函数返回值的类型或
位宽,是一个可选项,若没有指定,默认缺省值为 1 比特的寄存器数据;function_id 为所定
义函数的名称,对函数的调用也是通过函数名完成的,并在函数结构体内部代表一个内部变
量,函数调用的返回值就是通过函数名变量传递给调用语句;input_declaration 用于对寒暑
各个输入端口的位宽和类型进行说明,在函数定义中至少要有一个输入端口;endfunction
为函数结构体结束标志。下面给出一个函数定义实例。
定义函数实例。
function AND;
//定义输入变量
input A, B;
//定义函数体
begin
AND = A && B;
end
endfunction
函数定义在函数内部会隐式定义一个寄存器变量, 该寄存器变量和函数同名并且位宽也
一致。函数通过在函数定义中对该寄存器的显式赋值来返回函数计算结果。此外,还有下列
几点需要注意:
(1)函数定义只能在模块中完成,不能出现在过程块中;
(2)函数至少要有一个输入端口;不能包含输出端口和双向端口;
(3) 在函数结构中, 不能使用任何形式的时间控制语句 (#、 wait 等) , 也不能使用 disable
中止语句;
(4)函数定义结构体中不能出现过程块语句(always 语句) ;
(5)函数内部可以调用函数,但不能调用任务。
2.函数调用
和任务一样,函数也是在被调用时才被执行的,调用函数的语句形式如下:
func_id(expr1, expr2, ........., exprN)
其中,func_id 是要调用的函数名,expr1, expr2, ......exprN是传递给函数的输入参数列
表,该输入参数列表的顺序必须与函数定义时声明其输入的顺序相同。下面给出一个函数调
用实例。
函数调用实例。
module comb15 (A, B, CIN, S, COUT);
input [3:0] A, B;
input CIN;
output [3:0] S;
output COUT;
wire [1:0] S0, S1, S2, S3;
function signed [1:0] ADD;
input A, B, CIN;
reg S, COUT;
begin
S = A ^ B ^ CIN;
COUT = (A&B) | (A&CIN) | (B&CIN);
ADD = {COUT, S};
end
endfunction
assign S0 = ADD (A[0], B[0], CIN),
S1 = ADD (A[1], B[1], S0[1]),
S2 = ADD (A[2], B[2], S1[1]),
S3 = ADD (A[3], B[3], S2[1]),
S = {S3[0], S2[0], S1[0], S0[0]},
COUT = S3[1];
endmodule
在函数调用中,有下列几点需要注意:
(1)函数调用可以在过程块中完成,也可以在 assign 这样的连续赋值语句中出现。
(2)函数调用语句不能单独作为一条语句出现,只能作为赋值语句的右端操作数。