未闻verilog--- task与function


task 和 function 说明语句分别用来定义任务和函数,利用任务和函数可以把函数模块分成许多小的任务和函数便于理解和调试。任务和函数往往还是大的程序模块在不同地点多次用到的相同的程序段。输入、输出和总线信号的数据可以传入、传出任务和函数。

task

如果传给任务的变量和任务完成后接受结果的变量已经定义,就可以用一条语句启动任务,任务完成以后控制就传回启动过程。如果任务内部有定时设置,则启动的时间可以与控制返回的时间不同。

任务的定义

task<任务名>;                        //注意这里的分号!!!!
    <端口及数据类型声明语句>
    <语句1>
    <语句2>
    ...
    <语句n>
endtask

任务定义中,关键词 task 和 endtask 间的内容即被定义的任务,其任务名是标志当前定义任务的名称标识符。端口及数据类型声明语句 包括此任务的端口定义语句和变量类型定义语句。任务接受的输入值和返回的输出值都通过此端口,而且端口命名的排序也很重要,一旦确定就不要随意改动了。

过程语句 是一段用来完成任务操作的过程语句,因此也标志着任务的调用必须在主程序的过程结构中。任务中的过程语句是顺序语句,若有多条,需用块语句括起来。

任务的调用和变量的传递

任务的调用

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

举个例子

声明一个task

task my_task;
    input a;
    input b;
    inout c;
    output d;
    output e;
    ...
    <语句>        //    执行任务工作的相应语句
    ...
    c=foo1;       //对任务的输出变量赋值
    d=foo2;
    e=foo3;
endtask

当对这个task进行调用时

my_task(v,w,x,y,z);

任务调用变量(v,w,x,y,z)和任务定义的I/O变量(a,b,c,d,e)之间是一一对应的。当任务启动时,由v,w,和x.传入的变量赋给了a,b,和c,而当任务完成后的输出又通过c,d和e赋给了x,y和z。

注意事项

  1. 任务中可以有延时语句、敏感事件控制语句等事件控制语句。
  2. 任务可以没有或可以有一个或多个输入、输出和双向端口。
  3. 任务可以没有返回值,也可以通过输出端口或双向端口返回一个或多个返回值。
  4. 任务可以调用其它的任务或函数,也可以调用该任务本身。
  5. 任务定义结构内不允许出现过程块(initial或always过程块)。在任务中无法描述时序电路,可综合的任务语句结构只能描述组合电路
  6. 任务定义结构内可以出现disable终止语句,这条语句的执行将中断正在执行的任务。在任务被中断后,程序流程将返回到调用任务的地方继续向下执行。
  7. 注意在第一行声明task后面要有分号!!!!
  8. 在任务定义结构内建议使用阻塞赋值,非阻塞赋值会存在不定态的情况(存疑??)

function

函数的目的是返回一个用于表达式的值。

函数的定义

function <返回值的类型或范围>(函数名);        //注意这里的分号!!!
    <端口说明语句>
    <变量类型说明语句>
        begin
            <语句>
            ...
        end
endfunction

在这里,<返回值的类型或范围>可以不定义,如果默认则代表一位寄存器类型数据。

函数返回的值:函数的定义蕴含声明了一个与函数同名的,函数内部的寄存器,其位数与定义的相同;如果在函数的声明语句中<返回值的类型或范围>为默认,则这个寄存器是一位的,否则是与函数定义中<返回值的类型或范围>一致的寄存器,

函数的定义把函数返回值所赋值寄存器的名称初始化为与函数同名的内部变量。

函数返回值即为函数名

当然也可以用c语言的格式进行定义

function <返回值的类型或范围>(函数名)(端口说明语句);        //注意这里的分号!!!
    <变量类型说明语句>
        begin
            <语句>
            ...
        end
endfunction

举个例子

 function  get_data;
    input in;
    reg [1:0] fun_reg;
        begin
            get_data = in;
            task_reg = task_reg + 3'd1;
            fun_reg = fun_reg + 3'd1;
        end
    endfunction  

也可以写成

    function  get_data(input in);
        reg [1:0] fun_reg;
            begin
                get_data = in;
                task_reg = task_reg + 3'd1;
                fun_reg = fun_reg + 3'd1;
            end
    endfunction 

函数的调用和变量的传递

函数的调用是通过将函数作为表达式中的操作数来实现的。

其调用格式如下:

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

注意事项

  1. 函数的定义不能包含有任何的时间控制语句,即任何用#、@、或wait来标识的语句。
  2. function不能包含非阻塞赋值(非阻塞赋值只能用于过程块之中)
  3. 函数中的语句也不能出现always 或initial 引导的过程语句结构,从而函数描述的可综合的逻辑结构也只能是组合电路
  4. 函数不能启动任务。但函数可以调用其他函数。
  5. 定义函数时至少要有一个输入参量。函数允许有多个输入端口,且至少要有一个输入端口。函数不允许有常规意义上的输出端口或者双向端口,它的目的只是返回一个值,用于主程序表达式的计算。此值的位宽和类型在函数中已经定义好了。(function允许有一个或多个输入,不能没有输出,只能有一个输出,即与函数名同名的寄存器)
  6. 在函数的定义中必须有一条赋值语句给函数中的一个内部变量赋以函数的结果值,该内部变量具有和函数名相同的名字。
  7. 函数返回值不能定义为wire

递归函数

Verilog函数不能够递归调用,若某模块在两个不同的地方被同事并发调用,由于这两个调用同时对同一块地址进行操作,那么计算结果将是不确定的

如果在函数声明时使用了关键字automatic ,那么这个函数就是自动的或者可递归的。

仿真器为每一次函数调用动态的分配新的地址空间,每一个函数调用对各自的地址恐怖关键进行操作。

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

举个例子

module test_auto(

    );
    //function declaration
    function automatic integer factorial;
        input [31:0]oper;
        integer i;
        begin
            if(oper >= 2)
                factorial = factorial(oper-1) *oper;
            else
                factorial = 1;
        end
    endfunction
       
    integer result;
    initial
        begin
            result = factorial(4);
            $display("Factoial of 4 is %0d",result);
        end
    
endmodule

task与function的不同

  1. 函数只能与主模块共用同一个仿真的时间单位,而任务可以自己定义自己的仿真时间单位。
  2. 函数不能启动任务,但是可以调用其它函数,但是任务可以调用其他函数和任务;
  3. 函数至少要有一个输入变量,而任务可以没有或者有多个任何类型的变量。
  4. 函数返回一个值(函数名),而任务不返回任何值(可以通过输出端口输出)。

函数的目的值通过一个返回值对输入的信号进行响应。而任务可以支持多种目的,能计算多个结果值,这些值只能通过任务的输出端口或者总线端口输出。

Verilog语法之十一:任务(task)和函数(function) - 知乎 (zhihu.com)

verilog中task与function语句的使用 - 知乎 (zhihu.com)