Task&Function

task和function的区别

  1. function只能与主模块共用一个仿真时间单位,而task可以定义自己的仿真时间单位;
  2. function不能启动任务,而task能启动其他task和function;
  3. function至少要有一个输入变量,而task可以没有或有多个任何类型的变量;
  4. function返回一个值,而task则不返回值;

task和function的输出

function的目的是通过返回一个值来响应输入信号的值,

task的结果只能通过被调用的任务的输出或总线端口送出,能支持多种目的,能计算多个结果值。

task

特点:

  task允许嵌套,task可以启动其他的task,这些其他的task有可以启动别的task,且启动的task数量没有限制;

  控制信号在所有task启动完成后才能返回,无论有待启动的任务的数量。

定义task的语法:

//声明语句的语法与模块定义中的对应声明语句语法相同
task
<任务名>; <端口及数据类型声明语句> <语句1> <语句2> ... <语句n> endtask

 

task的调用及变量传递:

<任务名>(端口1,端口2,..., 端口n);

调用task使用的端口名和定义task不用保持相同,但是需要一一对应。

 

范例:

 

module traffic_lights;
    reg            clock, red, amber, green;
    parameter    on            =    1,
                off            =    0,
                red_tics    =    350,
                amber_tics    =    20,
                green_tics    =    200;
    
    // 信号灯初始化
    initial        red            =    off;
    initial        amber        =    off;
    initial        green        =    off;
    
    // 信号灯控制时序
    always
        begin
            red            =    on;
            light(red, red_tics);
            green        =    on;
            light(green, green_tics);
            amber        =    on;
            light(amber, amber_tics);
        end
        
    // 定义信号灯开启时间的任务
    task    light;
        output            color;
        input    [31:0]    tics;
        
        begin
            repeat(tics) @(posedge clock)
            color    =    off;
        end
    endtask
    
    // 产生时钟脉冲的always快
    always
        begin
            #100    clock = 0;
            #100    clock = 1;
        end
        
endmodule

 

 

function

作用:返回一个用于表达式的值。

 

定义function的语法:

function <返回值的类型或范围(可选,默认则返回值为一位寄存器类型数据)> (函数名(返回值));
    <端口说明语句>
    <变量类型说明语句>
        begin
           <语句>
            ...
        end
endfunction

 

从function返回的值:

在函数的定义蕴含声明了与函数同名的、函数内部的寄存器。函数的蒂尼把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。

 

function的调用:

function的调用是通过将函数作为表达时钟的操作数来实现,调用格式为:

<函数名>(<表达式>, ..., <表达式>)

 

function的使用规则:

  1. function的定义不能包含有任何的时间控制语句,即任何使用#、@、wait来表示的语句;
  2. function不能启动task;
  3. 定义function时至少需要有一个输入参量;
  4. 在function的定义中必须有一套赋值语句给funciton中的一个内部变量赋以function的结果值,该内部变量具有和函数名相同的名字;

范例:

// factoral为进行阶乘的函数,返回一个32位reg型的值,还可向后调用自身,并打印部分结果值
module
tryfact(); function [31:0] factorial; input [3:0] operand; reg [3:0] index; begin factorial = 1; for(index=2; index <=operand; index = index+1) factorial = index * factorial; end endfunction // 函数的测试 reg [31:0] result; reg [3:0] n; initial begin result = 1; for(n=2; n<9; n=n+1) begin $display("Partial result n=%d result = %d", n, result); result = n* factorial(n)/((n*2)+1); end $display("Finalresult = %d", result); end endmodule

 

function写法

常规写法

function    calc_parity;
    input    [31:0]    address;
    begin
        calc_parity = ^address;
    end
endfunction

C语言形式写法

function calc_parity(input [31:0] address);
    begin
        calc_parity = ^address;
    end

endfunction

 

自动(递归)function

verilog中的function时不能进行递归调用:这是因为若某一个函数在两个不同的地方被同时调用时,由于这两个调用同时对一块地址空间进行操作,最终会导致计算结果不确定。

 

解决方法:如果在function声明中使用关键字automatic,那么仿真器将为每一次函数调用动态的分配新的地址空间,function将成为自动的或可递归的。

 

特点:自动函数中声明的局部变量不能通过层次名进行访问,而自动函数本身可以通过层次名进行调用。

 

// 定义(自动)递归函数
function
automatic integer factorial; begin ... end endfunction

 

 

常量function

常量函数实际上是一个带有某些限制的常规verilog函数,这种函数能用来引用复杂的值,因此看用来代替常量。

// 定义及调用常量函数
integer result; function integer clogb2(input integer depth); begin for(clogb2=0; depth>0; clogb2=clogb2+1) depth = depth >> 1; end endfunction

result = clogb2(4);

 

带符号function

带符号函数的返回值可以作为带符号数进行运算,即函数返回的值也带符号。

//定义及调用带符号函数
function    signed [63:0]    compute_signed(input    [63:0]  vector);
.
.
.
endfunction

if(compute_signed(vector)<-3)
  begin
    ...
  end

 

关于使用task和function的小结

(1)task和function都是用来对设计中多次使用的公共代码进行定义;使用task和function可以将模块分割成许多个可独立管理的子单元,增强了模块的可读性和可维护性;

(2)task可以具有任意多个输入、输入/输出和输出变量;在task中可以使用延迟,时间和时序控制结构,可以在task中调用其他task和function;

(3)可重复task使用关键字automatic进行定义,它的每一次调用都对不同的地址空间进行操作,因此在被多次并发调用时仍可以获得正确的结果;

(4)function只能返回一个与函数名相同变量,并且至少要有一个输入变量;在function不能使用延迟、事件和时序控制结构;可以调用其他function,但是不能调用task;

(5)当声明function时,verilog仿真器都会隐含的声明一个同名的寄存器变量,function的返回值通过这个寄存器传递回调用处;

(6)递归function使用关键词automa进行定义,每一次调用递归function都拥有不同的地址空间,因此对这种function的递归调用和并发调用可以得到正确结果;

(7)task和function都包含在设计层次之中,可以通过层次名进行调用。

 

常用系统任务

主要包含verilog中一些常用的系统任务及其各自适用的场景。以及用于文件输出、显示层次、选通显示(strobing)、存储器初始化和值变转储的系统任务。

$display 和 $write任务

格式($与字符要相连):

  $display (p1, p2, ..., pn);

  $write (p1, p2, ..., pn);

p1称为“格式控制”,p2到pn称为“输出列表”。

作用:用于输出信息,即将参数p2到pn按参数p1给定的格式输出。

区别:$display自动地在输出后进行换行,而$write并非如此,在一行中输出多个信息。

 

格式说明:由%和格式字符组成,且总是有%开始。用于将输出的数据转换成指定的格式输出。

 

 

普通字符:需要输出原样的字符,部分特殊字符由下表的转换序列来输出

 

 

输出数据的显示宽度:

  1. 在$display中,输出列表中的数据的显示宽度是自动按照输出格式进行调整。
  2. 在显示输出数据时,在经过格式转换以后,总是用表达式的最大可能值所占的位数来说显示表达式的当前值。
  3. 在使用十进制格式输出时,输出结果前面的0值用空格来代替;对于其他进制,仍显示前面的0;
  4. 可以通过在%和表示进制的字符中间插入一个0可以自动调整显示输出数据宽度的方式,会使用最少的位数来显示表达式当前值(MSB不为0)。

输出结果中包含不确定值或高阻值时的显示结果:

(1)输出格式为十进制时

  1. 如果所有位均为不定值,则输出结果为小写的x;
  2. 如果所有位均为高阻值,则输出结果为小写的z;
  3. 如果部分位为不定值,则输出结果为大写的X;
  4. 如果部分位为高阻值,则输出结果为大写的Z;

(2)输出格式为十六进制或八进制时

  1. 每4位二进制数为一组代表十六进制数,每3位二进制数为一组表示以为八进制数;
  2. 如果表达式值相对的某进制数的所有位均为不定值,则该进制数的输出结果为小写的x;
  3. 如果表达式值相对的某进制数的所有位均为高阻值,则该进制数的输出结果为小写的z;
  4. 如果表达式值相对的某进制数的部分位为不定值,则该进制数的输出结果为大写的X;
  5. 如果表达式值相对的某进制数的部分位为高阻值,则该进制数的输出结果为大写的Z;

文件输出

verilog的结果通常输出到标准输出和文件verilog.log中;同时也允许将输出输出到指定的文件。

打开文件:$fopen

  用法:<文件句柄> = $fopen("<文件名>");

写文件:$fdisplay、 $fmonitor、$fwrite、$fstrobe

  用法:

    $fdisplay(<文件描述符>, p1, p2, ...);

    $fmonitor(<文件描述符>, p1, p2, ...);

关文件:$fclose

  用法:$fclose(<文件描述符>)

 

显示层次

作用:通过任何显示任务如$display、 $write、$monitor、 $strobe任务重的%m选项的方式可以显示任何级别的层次。

例如,当一个模块的实例执行同一段代码时,%m会区分哪个模块实例在输出,在显示任务中的%m选项无需参数。

可以显示全层次路径名,包括模块实例、任务、函数和命名块。

 

选通显示

由关键字$strobe的系统任务完成,与$display有细微差异。

作用:如果$display与其他语句在同一时间单位执行,那么这些语句之间的执行顺序时不确定的,而使用$strobe则总是在同时刻其他赋值语句执行完成后执行,确保了所有在同一时间沿幅值的其他语句执行完后才显示数据。

 

其他系统任务和函数

verilog所有的系统函数和任务前面都用一个$标识符加以确认,具体可查阅verilog语言参考手册和《IEEE标准Verilog硬件描述语言》

posted @ 2020-03-20 17:37  Pent°  阅读(513)  评论(0编辑  收藏  举报