IEEE SystemVerilog Chapter13 : Tasks and functions (subroutines)

13.2 Overview

        任务和函数提供了从描述中的几个不同位置执行通用过程的能力。它们还提供了一种将大型过程分解为小型过程的方法,以便更容易地阅读和调试源代码描述。本小节讨论了任务和函数之间的区别,描述了如何定义和调用任务和函数,并给出了每种任务和函数的示例。任务和函数统称为子例程
        以下规则将任务与函数区分开来,但在13.4.4中指出的例外情况除外:
                --函数主体中的语句应以一个仿真时间单位执行;任务可能包含时间控制语句
                --函数无法启用任务;一个任务可以启用其他任务和函数
                --非虚拟函数应返回单个值;任务或void函数不应返回值
                --非虚拟函数可以用作表达式中的操作数;该操作数的值是函数返回的值
例如:
        可以定义任务或函数来交换16位字中的字节。该任务将在输出参数中返回切换后的单词;因此,启用名为switch_bytes的任务的源代码可能看起来像以下示例:

switch_bytes (old_word, new_word);

        任务switch_bytes将取old_word中的字节,颠倒它们的顺序,并将颠倒的字节放在new_word中。单词切换函数将返回切换后的单词作为函数的返回值。因此,函数switch_bytes的函数调用可能看起来像以下示例:

new_word = switch_bytes (old_word);

13.3 Tasks

        应通过定义要传递给任务的参数值和接收结果的变量的语句启用任务。任务完成后,应将控制权返回启用进程。因此,如果任务内部有时序语句,那么启用任务的时间可能与返回控件的时间不同。一个任务可以启用其他任务,而这些任务又可以启用其他的任务——启用的任务数量没有限制。无论启用了多少任务,在所有启用的任务完成之前,控制都不会返回。

任务声明的语法如下语法13-1所示。
 

task_declaration ::= task [ lifetime ] task_body_declaration // from A.2.7
task_body_declaration ::=
[ interface_identifier . | class_scope ] task_identifier ;
{ tf_item_declaration }
{ statement_or_null }
endtask [ : task_identifier ]
| [ interface_identifier . | class_scope ] task_identifier ( [ tf_port_list ] ) ;
{ block_item_declaration }
{ statement_or_null }
endtask [ : task_identifier ]
tf_item_declaration ::=
block_item_declaration
| tf_port_declaration
tf_port_list ::=
tf_port_item { , tf_port_item }
tf_port_item23 ::=
{ attribute_instance }
[ tf_port_direction ] [ var ] data_type_or_implicit
[ port_identifier { variable_dimension } [ = expression ] ]
tf_port_direction ::= port_direction | const ref
tf_port_declaration ::=
{ attribute_instance } tf_port_direction [ var ] data_type_or_implicit list_of_tf_variable_identifiers ;
lifetime ::= static | automatic // from A.2.1
signing ::= signed | unsigned // from A.2.2.1
data_type_or_implicit ::=
data_type
| implicit_data_type
implicit_data_type ::= [ signing ] { packed_dimension }

                                Syntax 13-1—Task syntax (excerpt from Annex A)
在a tf_port_item中,省略显式port_identifier是非法的,除非在函数原型或任务原型中。
        任务声明的形式参数要么在括号中(如ANSI C),要么在声明和方向中

task mytask1 (output int x, input logic y);
    ...
endtask

task mytask2;
    output x;
    input y;
    int x;
    logic y;
        ...
endtask

每个形式参数都有以下方向之一:

input // copy value in at beginning
output // copy value out at end
inout // copy in at beginning and out at end
ref // pass reference (see 13.5.2) 

        如果未指定方向,则存在默认的input方向。一旦指定了方向,后续形式将默认为相同的方向。在以下示例中,形式参数a和b默认为input,u和v都是output:

task mytask3(a, b, output logic [15:0] u, v);
    ...
endtask

        每个形式参数都有一个数据类型,可以显式声明或从上一个参数继承。如果数据类型没有显式声明,那么如果它是第一个参数或显式指定了参数方向,则默认数据类型为logic。否则,数据类型将继承自上一个参数。数组可以指定为任务的形式参数
例如:

// the resultant declaration of b is input [3:0][7:0] b[3:0]
task mytask4(input [3:0][7:0] a, b[3:0], output [3:0][7:0] y[1:0]);
    ...
endtask

        任务声明和endtask之间可以编写多条语句。语句是按顺序执行的,就像它们被括在一个begin ... end中一样。没有任何声明也是合法的。到达结束任务时,任务将退出。return语句可用于在endtask关键字之前退出任务。对任务的调用也称为任务启用(有关调用任务的更多详细信息,请参阅13.5)。
        示例1:以下示例说明了具有五个参数的任务定义的基本结构:

task my_task;
    input a, b;
    inout c;
    output d, e;
        . . . // statements that perform the work of the task
        . . .
    c = a; // the assignments that initialize result outputs
    d = b;
    e = c;
endtask

或者使用任务声明的第二种形式,任务可以定义如下:

task my_task (input a, b, inout c, output d, e);
    . . . // statements that perform the work of the task
    . . .
    c = a; // the assignments that initialize result variables
    d = b;
    e = c;
endtask

以下语句调用该任务:

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

        任务调用参数(v、w、x、y和z)对应于任务定义的参数(a、b、c、d和e)。在调用时,input和inout类型参数(a、b和c)接收在v、w和x中传递的值。因此,调用的执行有效地导致以下分配:

a = v;
b = w;
c = x;

        作为任务处理的一部分,my_task的任务定义将计算出的结果值放入c、d和e中。任务完成后,将执行以下分配,将计算值返回给调用进程:

x = c;
y = d;
z = e;

示例2:以下示例通过描述红绿灯序列器来说明任务的使用:

module traffic_lights;
    logic clock, red, amber, green;
    parameter on = 1, off = 0, red_tics = 350,
                amber_tics = 30, green_tics = 200;
// initialize colors
    initial red = off;
    initial amber = off;
    initial green = off;
    always begin // sequence to control the lights
    red = on; // turn red light on
    light(red, red_tics); // and wait.
    green = on; // turn green light on
    light(green, green_tics); // and wait.
    amber = on; // turn amber light on
    light(amber, amber_tics); // and wait.
 end
// task to wait for 'tics' positive edge clocks
// before turning 'color' light off
task light (output color, input [31:0] tics);
    repeat (tics) @ (posedge clock);
    color = off; // turn light off.
endtask: light
    always begin // waveform for the clock
    #100 clock = 0;
    #100 clock = 1;
end
endmodule: traffic_lights

13.3.1 Static and automatic tasks

        在模块、接口、程序或包中定义的任务默认为静态的,所有声明的项都是静态分配的。这些项目应在同时执行的任务的所有用途中共享。可以通过以下两种方式将任务定义为使用自动存储:
        --使用可选的automatic关键字作为任务声明的一部分显式声明。
        --通过在定义为automatic的模块、接口、程序或包中定义任务来隐含声明

        类中定义的任务总是automatic的(见8.6)。automatic任务中声明的所有项都会为每次调用动态分配。所有形式参数和局部变量都存储在堆栈中分层引用无法访问automatic任务项automatic任务可以通过使用其层次结构名称来调用。特定的局部变量可以在静态任务中声明为automatic变量,也可以在automatic任务中宣布为静态变量

13.3.2 Task memory usage and concurrent activation

        一个任务可以同时启用多次。automatic任务的所有变量应在每次并发任务调用中复制,以存储该调用特定的状态。
        静态任务的所有变量都应是静态的,因为无论任务的并发激活次数如何,模块实例中的每个声明的局部变量都应对应一个变量。但是,模块的不同实例中的静态任务应具有彼此独立的存储。
        静态任务中声明的变量,包括输入、输出和inout类型参数,应在调用之间保留其值。应将它们初始化为6.8中所述的默认初始化值。
        automatic任务中声明的变量,包括输出类型参数,应在执行进入其范围时初始化为默认初始化值。input和inout类型参数应初始化为从任务启用语句中列出的这些参数对应的表达式传递的值。

        由于在自动任务中声明的变量在任务调用结束时被释放,因此它们不应用于此后可能引用它们的某些构造中:
        --不得使用非阻塞赋值或程序连续赋值对其赋值。
        --程序性的连续任务或程序性的强制声明不得引用它们。
        --它们不应在非阻塞分配的分配内事件控制中引用。
        --它们不应使用$monitor和$dumpvars等系统任务进行跟踪。

13.4 Functions

        函数的主要用途是返回要在表达式中使用的值。void函数也可以代替任务来定义在单个时间步长内执行和返回的子例程。本条款的其余部分解释了如何定义和使用函数。

        函数有一些限制,可以确保它们在不挂起启用它们的进程的情况下返回。除13.4.4中注明的例外情况外,以下规则应适用于其使用:
        a)函数不应包含任何时间控制语句。也就是说,任何包含#, ##, @, fork-join, fork-join_any, wait, wait_order, or expect。
        b)函数不应启用任务,无论这些任务是否包含时间控制语句
      c) 函数可以使细粒度过程控制方法能够暂停自己的或另一个进程(见9.7)。

语法13-2给出了定义函数的语法。

function_declaration ::= function [ lifetime ] function_body_declaration // from A.2.6
function_body_declaration ::=
function_data_type_or_implicit
[ interface_identifier . | class_scope ] function_identifier ;
{ tf_item_declaration }
{ function_statement_or_null }
endfunction [ : function_identifier ]
| function_data_type_or_implicit
[ interface_identifier . | class_scope ] function_identifier ( [ tf_port_list ] ) ;
{ block_item_declaration }
{ function_statement_or_null }
endfunction [ : function_identifier ]
function_data_type_or_implicit ::=
data_type_or_void
| implicit_data_type
data_type ::= // from A.2.2.1
integer_vector_type [ signing ] { packed_dimension }
| integer_atom_type [ signing ]
| non_integer_type
| struct_union [ packed [ signing ] ] { struct_union_member { struct_union_member } }
{ packed_dimension }13
| enum [ enum_base_type ] { enum_name_declaration { , enum_name_declaration } }
{ packed_dimension }
| string
| chandle
| virtual [ interface ] interface_identifier [ parameter_value_assignment ] [ . modport_identifier ]
| [ class_scope | package_scope ] type_identifier { packed_dimension }
| class_type
| event
| ps_covergroup_identifier
| type_reference
signing ::= signed | unsigned
lifetime ::= static | automatic // from A.2.1.3

                                Syntax 13-2—Function syntax (excerpt from Annex A)
13) 当packed 维度与struct或union关键字一起使用时,也应使用packed 关键字
14) 当在net声明中使用type_reference时,它前面应该有一个net type关键字;当它用于变量声明时,它前面应该有var关键字
        为了指示函数的返回类型,它的声明可以包括显式data_type_or_void,也可以使用仅指示packed 维度范围的隐式语法,以及可选的有符号性。使用隐式语法时,返回类型与隐式语法前面紧跟着logic关键字的情况相同。特别是,隐式语法可以为空,在这种情况下,返回类型是logic标量。函数也可以是void的,没有返回值(见13.4.1)。函数声明的形式参数要么在括号中(如ANSI C),要么在声明和方向中,如下所示:

function logic [15:0] myfunc1(int x, int y);
    ...
endfunction

function logic [15:0] myfunc2;
    input int x;
    input int y;
    ...
endfunction

函数可以具有与任务相同的形式参数。函数参数方向如下:

input // copy value in at beginning
output // copy value out at end
inout // copy in at beginning and out at end
ref // pass reference (see 13.5.2)

        如果未指定方向,则函数声明默认为形式方向输入。一旦指定了方向,后续形式将默认为相同的方向。在以下示例中,形式参数a和b默认为输入,u和v都是输出:

function logic [15:0] myfunc3(int a, int b, output logic [15:0] u, v);
    ...
endfunction

        每个形式参数都有一个数据类型,可以显式声明或从上一个参数继承。如果数据类型没有显式声明,那么如果它是第一个参数或显式指定了参数方向,则默认数据类型为logic。否则,数据类型将继承自上一个参数。数组可以指定为函数的形式参数,例如:

function [3:0][7:0] myfunc4(input [3:0][7:0] a, b[3:0]);
    ...
endfunction

        在事件表达式、过程连续赋值中的表达式或不在过程语句中的表达式中调用具有output、inout或ref参数的函数是非法的。但是,在这种情况下,const-ref函数参数是合法的(见13.5.2)。
函数头和endfunction之间可以编写多条语句。语句是按顺序执行的,就好像它们被包含在一个begin—end组中一样。完全没有语句也是合法的,在这种情况下,函数返回与函数同名的隐式变量的当前值。

13.4.1 Return values and void functions

        函数定义应隐式声明函数内部的变量,该变量与函数名称相同。此变量的类型与函数返回值的类型相同。函数返回值可以通过两种方式指定,一种是使用return语句,另一种是为与函数同名的内部变量赋值。例如:

function [15:0] myfunc1 (input [7:0] x,y);
    myfunc1 = x * y - 1; // return value assigned to function name
endfunction

function [15:0] myfunc2 (input [7:0] x,y);
    return x * y - 1; //return value is specified using return statement
endfunction

        return语句应覆盖分配给函数名称的任何值。使用return语句时,非void函数应指定带有return的表达式函数返回可以是结构或联合。在这种情况下,函数内部使用的以函数名称开头的层次结构名称被解释为返回值的成员。如果函数名称在函数之外使用,则该名称表示整个函数的范围。如果函数名称在层次结构名称中使用,它还指示整个函数的范围。函数可以声明为类型void,该类型没有返回值。函数调用可以用作表达式,除非类型为void,否则为语句:

a = b + myfunc1(c, d); // call myfunc1 (defined above) as an expression
myprint(a); // call myprint (defined below) as a statement
function void myprint (int a);
    ...
endfunction

        返回值的函数可以用于赋值或表达式。调用一个无返回值的非虚拟函数是合法的,但应该发出警告。函数可以用作语句,通过将函数调用强制转换为void类型,返回值将被丢弃而不会发出警告

void'(some_function()); 

        在声明或显式导入函数的范围内声明与该函数同名的另一个对象是非法的。在函数范围内声明与函数同名的另一个对象也是非法的

13.4.2 Static and automatic functions

        在模块、接口、程序或包中定义的函数默认为静态的,所有声明的项都是静态分配的。这些项目应在同时执行的函数的所有使用中共享。可以通过以下两种方式定义使用自动存储的功能:
        --使用可选的automatic关键字作为函数声明的一部分显式声明
        --通过在定义为automatic的模块、接口、程序或包中定义函数来隐含声明
        类中定义的函数总是automatic的(见8.6)。一个automatic函数是可重入的,所有的函数声明都是为每个并发函数调用动态分配的。分层引用无法访问automatic函数项。automatic函数可以通过使用其层次结构名称来调用。特定的局部变量可以在静态函数中声明为automatic变量,也可以在automatic函数中声明为静态变量
        以下示例定义了一个名为factorial的函数,该函数返回一个整数值。阶乘函数被迭代调用,结果被打印出来。

module tryfact;
// define the function
    function automatic integer factorial (input [31:0] operand);
        if (operand >= 2)
            factorial = factorial (operand - 1) * operand;
        else
            factorial = 1;
    endfunction: factorial
// test the function
    integer result;
    initial begin
        for (int n = 0; n <= 7; n++) begin
            result = factorial(n);
            $display("%0d factorial=%0d", n, result);
                    end
            end
endmodule: tryfact

仿真结果如下:

0 factorial=1
1 factorial=1
2 factorial=2
3 factorial=6
4 factorial=24
5 factorial=120
6 factorial=720
7 factorial=5040

13.4.3 Constant functions

常数函数是应满足以下约束的正规函数的子集:
        --常量函数不应具有output、inout或ref参数
        --void 函数不应为常数函数
        --DPI导入函数(见35.2.1)不应为常量函数
        --常量函数不应包含直接安排事件在函数返回后执行的语句
        --常量函数不应包含任何fork构造
        --常量函数不应包含层次引用
        --常量函数中调用的任何函数都应是当前模块本地的常量函数
        --调用constant_expression中允许的任何系统函数都是合法的(见11.2.1)。这包括$bits和数组查询函数。对其他系统函数的调用是非法的
        --应忽略常量函数中的所有系统任务调用
        --函数中使用的所有参数值应在使用调用常量函数调用之前定义(即,在评估常量函数调用时使用的任何参数都构成在原始常量函数调用位置使用该参数)。常量函数可以引用包或$unit中定义的参数
        --所有非参数或函数的标识符应在本地声明为当前函数。
        --如果常量函数使用任何直接或间接受defparam语句影响的参数值(见23.10.1),则结果应为未定义。这可能会产生错误,或者常量函数可能返回不确定的值
        --常量函数不应在生成块内声明(见第27条)。
        --常量函数本身不应在任何需要常量表达式的上下文中使用常量函数。
        --常量函数可能具有默认参数值(见13.5.3),但任何此类默认参数值都应为常量表达式。常量函数调用用于支持在elaboration时构建复杂的值计算(见3.12)。
        常量函数调用是调用模块本地的常量函数的函数调用,或者是来自包或$unit的常量函数调用,其中函数的参数都是常量表达式(见11.2.1)。
        常量函数调用在elaboration时进行评估。它们的执行对仿真时使用的变量的初始值没有影响,也不会对elaboration时函数的多次调用产生影响。在每种情况下,变量都会像正常仿真一样进行初始化。
        以下示例定义了一个名为clogb2的函数,该函数返回一个整数,该整数的值为对数基数2的上限值。

module ram_model (address, write, chip_select, data);
    parameter data_width = 8;
    parameter ram_depth = 256;
    localparam addr_width = clogb2(ram_depth);
    input [addr_width - 1:0] address;
    input write, chip_select;
    inout [data_width - 1:0] data;
    //define the clogb2 function
    function integer clogb2 (input [31:0] value);
        value = value - 1;
        for (clogb2 = 0; value > 0; clogb2 = clogb2 + 1)
        value = value >> 1;
    endfunction

    logic [data_width - 1:0] data_store[0:ram_depth - 1];
        //the rest of the ram model
endmodule: ram_model

此ram_model的一个实例(已分配参数)如下所示:

ram_model #(32,421) ram_a0(a_addr,a_wr,a_cs,a_data); 

13.4.4 Background processes spawned by function calls(函数调用产生的后台进程)

        函数应立即执行。因此,调用函数的进程应立即返回。非阻塞的语句应允许在函数内部使用;具体地说,非阻塞分配、事件触发器、时钟驱动器和fork-join_none构造应该被允许在函数内部。
        如果调用函数的线程是由initial进程、always 进程或其中一个进程的fork块创建的,并且在允许副作用的上下文中,则应允许调用试图调度事件的函数,该事件在该函数返回之后才能变为活动。
        当不满足这些规定时,实现应在编译时或运行时发出错误。在函数中,fork-join_none构造可以包含任务中合法的任何语句。函数中合法和非法使用fork-join_none的示例如下:

class IntClass;
    int a;
endclass

IntClass address=new(), stack=new();
    function automatic bit watch_for_zero( IntClass p );
        fork
            forever @p.a begin
            if ( p.a == 0 ) $display (“Unexpected zero”);
        end
        join_none
        return ( p.a == 0 );
    endfunction

function bit start_check();
    return ( watch_for_zero( address ) | watch_for_zero( stack ) );
endfunction

bit y = watch_for_zero( stack ); // illegal
    initial if ( start_check() ) $display ( “OK”); // legal

    initial fork
        if (start_check() ) $display( “OK too”); // legal
    join_none

13.5 Subroutine calls and argument passing

        任务和void函数被称为过程块中的语句(见9.2)。非虚拟函数调用可以是表达式中的操作数。将子程序作为语句调用的语法如语法13-3所示:

subroutine_call_statement ::= // from A.6.9
    subroutine_call ;
    | void ' ( function_subroutine_call ) ;
subroutine_call ::= // from A.8.2
    tf_call
    | system_tf_call
    | method_call
    | [ std :: ] randomize_call
tf_call37 ::= ps_or_hierarchical_tf_identifier { attribute_instance } [ ( list_of_arguments ) ]
list_of_arguments ::=
    [ expression ] { , [ expression ] } { , . identifier ( [ expression ] ) }
    | . identifier ( [ expression ] ) { , . identifier ( [ expression ] ) }
ps_or_hierarchical_tf_identifier ::= // from A.9.3
    [ package_scope ] tf_identifier
    | hierarchical_tf_identifier

                        Syntax 13-3—Task or function call syntax (excerpt from Annex A)

        除非子例程是任务、void函数或类方法,否则在tf_call中省略括号是非法的。如果子例程是非void类函数方法,则如果调用是直接递归的,则省略括号是非法的
        如果子例程中的参数被声明为input,那么子例程调用中对应的表达式可以是任何表达式。参数列表中表达式的求值顺序未定义
        如果子程序中的自变量被声明为output或inout,则子程序调用中的相应表达式应限制为过程赋值左侧有效的表达式(见10.4)。
        子程序调用的执行应传递调用参数中列出的表达式的输入值。子程序返回的执行应将output和inout类型参数的值传递给子程序调用中的相应变量
        SystemVerilog提供了两种向任务和函数传递参数的方法:通过值和通过引用参数也可以通过名称和位置进行绑定子例程参数也可以被赋予默认值,从而允许对子例程的调用不传递参数

13.5.1 Pass by value

        值传递是将参数传递给子例程的默认机制。这种参数传递机制通过将每个参数复制到子例程区域来工作。如果子例程是automatic的,则子例程在其堆栈中保留参数的本地副本。如果参数在子例程内发生了更改,则这些更改在子例程外不可见。当参数较大时,可能不希望复制这些参数。此外,程序有时需要共享一段未声明为全局的公共数据。
        例如,每次调用以下函数都会复制1000个字节。

function automatic int crc( byte packet [1000:1] );
    for( int j= 1; j <= 1000; j++ ) 
        begin
            crc ^= packet[j];
        end
endfunction

13.5.2 Pass by reference

        通过引用传递的参数不会复制到子例程区域,而是将对原始参数的引用传递到子例程。然后,子例程可以通过引用访问参数数据。通过引用传递的参数应与等效的数据类型匹配(见6.22.2)。不允许转换。为了指示参数通过引用传递,参数声明前面有ref关键字。对于生命周期为静态的子例程,使用通过引用传递的参数是非法的。一般语法如下:

subroutine( ref type argument );

例如,前面的例子可以写如下:

function automatic int crc( ref byte packet [1000:1] );
    for( int j= 1; j <= 1000; j++ ) begin
        crc ^= packet[j];
    end
endfunction

        如前面的示例所示,除了添加ref关键字之外,不需要进行任何更改。编译器知道数据包现在是通过引用来寻址的,但用户不需要在被调用者或调用点显式地进行这些引用。换句话说,对crc函数的任一版本的调用保持不变:

byte packet1[1000:1];
int k = crc( packet1 ); // pass by value or by reference: call is the same

         当参数通过引用传递时,调用方和子例程共享参数的相同表示形式;因此,在调用程序或子例程中对参数所做的任何更改都应该是彼此可见的通过引用传递的变量赋值的语义是,在子程序返回之前,可以立即在子程序外部看到更改。只有以下引用传递参数是合法的:
           -- 一个变量;
            --一个类的属性;
           -- 一个非打包结构的成员(unpacked structure);
            --一个非打包数组的元素(unpacked array);
        不得通过引用传递net变量和net变量的部分选择由于引用传递的变量可能是automatic变量,因此在任何禁止automatic变量使用的上下文中都不应使用ref参数
        通过引用传递的动态数组、队列和关联数组的元素可能会从数组中删除,或者在调用的子例程完成之前调整数组的大小。通过引用传递的特定数组元素应继续存在于被调用子程序的范围内,直到它们完成为止。如果在进行更改之前从数组中删除了数组元素,则被调用的子例程对数组元素值所做的更改在这些子例程的范围之外不可见。
        这些引用应称为过时引用。对可变大小数组的以下操作将导致对该数组元素的现有引用变为过时引用

        —使用隐式或显式new[]调整动态数组的大小。
        —使用delete()方法删除动态数组。
        —正在引用的关联数组的元素将使用delete()方法删除。
        —包含引用元素的队列或动态数组通过赋值进行更新。
        —正在引用的队列的元素由队列方法删除。
      
  通过引用传递参数是一个唯一的参数传递限定符,不同于input、output或inout。将ref与任何其他方向限定符组合在一起是非法的。例如,以下声明会导致编译器错误:

task automatic incr( ref input int a );// incorrect: ref cannot be qualified

        ref参数类似于inout参数,不同之处在于inout参数被复制两次:当调用子例程时,从实际值到参数一次,当子例程返回时,从参数到实际值一次。传递对象句柄也不例外,并且在作为ref或inout参数传递时具有类似的语义。因此,除了修改对象的内容外,对象句柄的ref还允许更改对象句柄(例如,分配新对象)。
        为了保护通过引用传递的参数不被子例程修改,
const限定符可以与ref一起使用,以表明该参数虽然通过引用传递,但却是只读变量

task automatic show ( const ref byte data [] );
    for ( int j = 0; j < data.size ; j++ )
        $display( data[j] ); // data can be read but not written
endtask

        当形式参数被声明为const ref时,子例程无法更改变量,尝试这样做会产生编译器错误。

13.5.3 Default argument values

        为了处理常见情况或允许未使用的参数,SystemVerilog允许子例程声明为单个参数指定默认值。在子例程中声明默认参数的语法如下:

subroutine( [ direction ] [ type ] argument = default_expression);

        可选方向可以是input、inout、output或ref。
        每次使用默认值进行调用时,都会在包含子例程声明的作用域中计算default_expression。如果未使用默认值,则不会计算默认表达式。只有ANSI样式声明才允许使用默认值。
       
 当调用子例程时,可以从调用中省略具有默认值的参数,编译器应插入它们相应的值。未指定(或空)的参数可以用作默认参数的占位符。如果未指定的参数用于没有默认值的参数,则应发出编译器错误。

task read(int j = 0, int k, int data = 1 );
    ...
endtask

        本例声明了一个带有两个默认参数j和data的任务read()。然后可以使用各种默认参数调用该任务:

read( , 5 ); // is equivalent to read( 0, 5, 1 );
read( 2, 5 ); // is equivalent to read( 2, 5, 1 );
read( , 5, ); // is equivalent to read( 0, 5, 1 );
read( , 5, 7 ); // is equivalent to read( 0, 5, 7 );
read( 1, 5, 2 ); // is equivalent to read( 1, 5, 2 );
read( ); // error; k has no default value
read( 1, , 7 ); // error; k has no default value 

        以下示例显示了具有默认表达式的输出参数:

module m;
logic a, w;
    task t1 (output o = a) ; // default binds to m.a
        ...
    endtask :t1

    task t2 (output o = b) ; // illegal, b cannot be resolved
        ...
    endtask :t2

    task t3 (inout io = w) ; // default binds to m.w
        ...
    endtask :t1

  endmodule :m

module n;
    logic a;
        initial begin
        m.t1(); // same as m.t1(m.a), not m.t1(n.a);
            // at end of task, value of t1.o is copied to m.a
        m.t3(); // same as m.t3(m.w)
        // value of m.w is copied to t3.io at start of task and
        // value of t3.io is copied to m.w at end of task
    end
endmodule :n

13.5.4 Argument binding by name
        SystemVerilog允许通过名称和位置绑定任务和函数的参数。这允许指定非连续的默认参数,并轻松指定要在调用时传递的参数
        例如:

function int fun( int j = 1, string s = "no" );
    ...
endfunction

fun函数可以调用如下:

fun( .j(2), .s("yes") ); // fun( 2, "yes" );
fun( .s("yes") ); // fun( 1, "yes" );
fun( , "yes" ); // fun( 1, "yes" );
fun( .j(2) ); // fun( 2, "no" );
fun( .s("yes"), .j(2) ); // fun( 2, "yes" );
fun( .s(), .j() ); // fun( 1, "no" );
fun( 2 ); // fun( 2, "no" );
fun( ); // fun( 1, "no" );

        如果参数具有默认值,则将它们视为模块实例的参数。如果参数没有默认值,则应给出它们,或者编译器应发出错误。如果在单个子例程调用中同时指定了位置参数和命名参数,则所有位置参数都应位于命名参数之前。然后,使用与上面相同的示例:

fun( .s("yes"), 2 ); // illegal
fun( 2, .s("yes") ); // OK

13.5.5 Optional argument list

        当void函数或类函数方法没有指定参数时,子例程名称后面的空括号()应是可选的。当所有参数都指定了默认值时,对于需要参数的任务、void函数和类方法也是如此。在没有层次限定的直接递归非虚拟类函数方法调用中省略括号是非法的。

13.6 Import and export functions

        SystemVerilog提供了一个直接编程接口(DPI),允许将foreign language子程序(如C函数)导入SystemVerilog。导入的子例程的调用方式与SystemVerilog子例程相同。SystemVerilog任务和函数也可以导出为foreign language。有关DPI的详细信息,请参见第35条。

13.7 Task and function names

        任务和函数名称的解析规则与其他引用略有不同。即使用作简单名称,任务或函数名称也遵循向上分层名称解析规则的修改形式。这意味着可以解析对稍后在同一范围或封闭范围中定义的任务或函数的“转发”引用。有关管理任务和函数名称解析的规则,请参见23.8.1。

13.8 Parameterized tasks and functions

        SystemVerilog提供了一种创建参数化任务和函数的方法,也称为参数化子例程。参数化子例程允许用户一般性地指定或定义实现。当使用该子程序时,可以提供完全定义其行为的参数。这只允许编写和维护一个定义,而不是使用不同数组大小、数据类型和可变宽度的多个子例程。
        实现参数化子例程的方法是通过在参数化类中使用静态方法(见8.10和8.25)。下面的通用编码器和解码器示例显示了如何使用静态类方法以及类参数化来实现参数化子例程。该示例有一个类,其中包含两个子例程,在本例中,这两个子例程共享参数化。该类可以被声明为虚拟的,以防止对象构造并强制该方法的严格静态使用

virtual class C#(parameter DECODE_W, parameter ENCODE_W = $clog2(DECODE_W));
    static function logic [ENCODE_W-1:0] ENCODER_f
        (input logic [DECODE_W-1:0] DecodeIn);
        ENCODER_f = '0;
            for (int i=0; i

        类C包含两个静态子例程,ENCODER_f和DECODER_f。每个子程序都是通过重用类参数DECODE_W和ENCODE_W来参数化的。参数ENCODE_W的默认值通过使用系统函数$clog2确定(见20.8.1)。这些参数在子程序中用于指定编码器的大小和解码器的大小。

module top ();
logic [7:0] encoder_in;
logic [2:0] encoder_out;
logic [1:0] decoder_in;
logic [3:0] decoder_out;
// Encoder and Decoder Input Assignments
    assign encoder_in = 8'b0100_0000;
    assign decoder_in = 2'b11;
        // Encoder and Decoder Function calls
    assign encoder_out = C#(8)::ENCODER_f(encoder_in);
    assign decoder_out = C#(4)::DECODER_f(decoder_in);
  
      initial begin
        #50;
        $display("Encoder input = %b Encoder output = %b\n",
        encoder_in, encoder_out );
        $display("Decoder input = %b Decoder output = %b\n",
        decoder_in, decoder_out );
            end
endmodule

        顶层模块首先定义本例中使用的一些中间变量,然后将常数值分配给编码器和解码器输入。通用编码器的子程序调用ENCODER_f使用表示编码器的特定实例的解码器宽度值的专用类参数值8,同时传递输入编码值ENCODER_in。此表达式使用静态类作用域解析运算符::(请参见8.23)来访问编码器子例程。表达式被分配给一个输出变量,以保存运算的结果。通用解码器的子程序调用decoder_f类似,使用参数值4。

你可能感兴趣的:(Systemverilog,SystemVerilog)