开发工具:ISE Design Suite 14.7
仿真工具:Modelsim SE-64 10.4
在初学FPGA时设计工程较小仿真比较简单,未考虑使用task任务来写TestBench,随着不断的学习和对官方IP的使用逐渐发现仿真程序会越写越多,导致仿真程序结构不好规划,看到大多数官方的仿真都使用了Task使仿真更加清晰,通过自己的理解对task做了简单总结。
一、task说明
task说明语句用来定义任务。利用任务可以把一个很大的程序模块分解成许多较小的任务便于理解和调试。输入、输出和总线信号的值可以传入、传出任务。任务往往还是大的程序模块中在不同地点多次用到的相同的程序段。使用task语句可以简化程序的结构,使程序明白易懂。
在任务的使用过程中容易和函数产生混淆,在Verilog中函数的说明语句是function。
1.1、task与function区别
task和function的区别如下:
-
函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
-
函数不能启动任务,而任务能启动其它任务和函数。
-
函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。
-
函数返回一个值,而任务则不返回值。
函数的目的是通过返回一个值来响应输入信号的值。任务却能支持多种目的,能计算多个结果值,这些结果值只能通过被调用的任务的输出或总线端口送出。Verilog HDL模块使用函数时是把它当作表达式中的操作符,这个操作的结果值就是这个函数的返回值。
1.2、Task语法
如果传给任务的变量值和任务完成后接收结果的变量已定义,就可以用一条语句启动任务。任务完成 以后控制就传回启动过程。如任务内部有定时控制,则启动的时间可以与控制返回的时间不同。任务可以启动其它的任务,其它任务又可以启动别的任务,可以启动的任务数是没有限制的。不管有多少任务启动,只有当所有的启动任务完成以后,控制才能返回。
1.2.1 任务的定义
定义任务的语法如下:
task <任务名>;
<端口及数据类型声明语句>
<语句1>
<语句2>
.....
<语句n>
endtask
这些声明语句的语法与模块定义中的对应声明语句的语法是一致的。 下面通过一个任意(整数)分频器对task进行举例。
二、task使用
下面是任意分频器工程模块
bash
`timescale 1 ns / 1 ps
module clk_arb_odd#(
parameter U_DLY = 1
)
(
output wire uart_baud_en , // flag for uart data baud
output wire clk_c ,
input wire[11:0] uart_baud_param , // uart baud parameter
input wire rst_n , // reset, active low
input wire clk
);
//----------------------------------------------------------------
// register define
//----------------------------------------------------------------
reg [11:0] clk_cnt ; //clk cycle count//
reg [11:0] clk_cnt_h ; //Hign level count//
reg [11:0] clk_cnt_l ; //Low level count//
reg clk_cnt_odd ; //If input div_para odd, for adjustment//用于奇数分频
reg clk_div_a ;
reg clk_div_b ;
reg clk_div_o ;
reg clk_div_o_dly ;
reg uart_baud_flag ;
//----------------------------------------------------------------
// assignment
//----------------------------------------------------------------
assign uart_baud_en = (uart_baud_param == 12'h001) ? clk : uart_baud_flag;
assign clk_c = (clk_div_a || clk_div_b) ? 1'b1 : 1'b0;
//----------------------------------------------------------------
//----------------------------------------------------------------
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 1'b0)
begin
clk_cnt_odd <= #U_DLY 1'b0;
clk_cnt_h <= #U_DLY 12'h0;
clk_cnt_l <= #U_DLY 12'h0;
end
else
begin
clk_cnt_odd <= #U_DLY uart_baud_param[0]; //if odd Num., low cnt_l + 1//
clk_cnt_h <= #U_DLY (uart_baud_param >>1);
clk_cnt_l <= #U_DLY clk_cnt_h + clk_cnt_odd;
end
end
//----------------------------------------------------------------
//----------------------------------------------------------------
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 1'b0)
begin
clk_cnt <= #U_DLY 12'h0;
clk_div_a <= #U_DLY 1'b0;
end
else if((clk_div_a == 1'b1) && (clk_cnt >= clk_cnt_h))
begin
clk_cnt <= #U_DLY 12'h1;
clk_div_a <= #U_DLY 1'b0;
end
else if(clk_cnt >= clk_cnt_l)
begin
clk_cnt <= #U_DLY 12'h1;
clk_div_a <= #U_DLY 1'b1;
end
else
begin
clk_cnt <= #U_DLY clk_cnt + 12'h1;
clk_div_a <= #U_DLY clk_div_a;
end
end
//----------------------------------------------------------------
//----------------------------------------------------------------
always @(negedge clk,negedge rst_n)
begin
if (rst_n == 1'b0)
clk_div_b <= #U_DLY 1'b0;
else if ((clk_div_a == 1'b1) && (clk_cnt_odd == 1'b1))
clk_div_b <= #U_DLY 1'b1;
else
clk_div_b <= #U_DLY 1'b0;
end
//----------------------------------------------------------------
//----------------------------------------------------------------
always @(posedge clk,negedge rst_n)
begin
if(rst_n == 1'b0)
begin
clk_div_o <= #U_DLY 1'b0;
clk_div_o_dly <= #U_DLY 1'b0;
uart_baud_flag <= #U_DLY 1'b0;
end
else
begin
clk_div_o_dly <= #U_DLY clk_div_o;
uart_baud_flag <= #U_DLY (clk_div_o_dly == 1'b0 && clk_div_o == 1'b1);
if (uart_baud_param == 12'h000)
clk_div_o <= #U_DLY 1'b0;
else if (uart_baud_param == 12'h001)
clk_div_o <= #U_DLY clk;
else
clk_div_o <= #U_DLY clk_div_a || clk_div_b; //cnt_L > cnt_H, use"|", else "&"//
end
end
//----------------------------------------------------------------
//----------------------------------------------------------------
endmodule
下面是仿真模块,该工程比较小,可以不用task任务进行调试更方便,但是这儿为了对task进行巩固还是选择用task。下面仿真分别在任务中设置了3、10和100分频。
bash
module vtf_clk_arb_odd;
// Inputs
reg [11:0] uart_baud_param;
reg rst_n;
reg clk;
// Outputs
wire uart_baud_en;
wire clk_c;
// Instantiate the Unit Under Test (UUT)
clk_arb_odd uut (
.uart_baud_en(uart_baud_en),
.uart_baud_param(uart_baud_param),
.clk_c(clk_c),
.rst_n(rst_n),
.clk(clk)
);
//-----------------------------------
//分频参数设置
//-----------------------------------
parameter clk_div3 = 32'd3;
parameter clk_div10 = 32'd10;
parameter clk_div100 = 32'd100;
//-----------------------------------
//复位初始化
//-----------------------------------
initial
begin
rst_n = 0;
clk = 0;
#100
$display("reset start...");
rst_n = 1;
$display("reset end...");
end
//-----------------------------------
//分频任务设置
//-----------------------------------
task freq_param;
input [31:0] freq_in;
output reg [31:0] freq_out;
begin
repeat(200)
@(posedge clk)
freq_out = freq_in;
end
endtask
//-----------------------------------
//分频任务使用
//-----------------------------------
reg [31:0] clk_param;
initial
begin
uart_baud_param = 0;
clk_param = 0;
wait(rst_n == 1);
$display("frequency start...");
$display("3 frequency");
freq_param(clk_div3,clk_param);//使用任务设置3分频
uart_baud_param = clk_param;
$finish(1);
$display("10 frequency");
freq_param(clk_div10,clk_param);//使用任务设置10分频参数
uart_baud_param = clk_param;
$finish(2);
$display("100 frequency");
freq_param(clk_div100,clk_param);//使用任务设置100分频
uart_baud_param = clk_param;
$finish(3);
// Add stimulus here
end
//-----------------------------------
//主时钟
//-----------------------------------
always #5 clk = ~clk;
endmodule
仿真工程写完后使用modelsim进行仿真查看;下图还是仿真波形,分别对应了3分频、10分频和100分频;

下面是3分频和10分频放大图;

关于task的简单理解也就差不多了,欢迎大家交流讨论。
最后可以看到在Transcript中打印了一系列的信息,这就和设置的display和finish有关系了,在Verilog中display就相当于C语言中printf作用,finish就相当于打断点的作用。
