概述
类似于C语言,函数(function)和任务(task)可以提高代码的复用性和整洁度。
它们的目的都在于将大型的过程块切分为更细小的片段,而便于阅读和代码维护。
函数与任务之间有相同点和不同点。
函数function
函数的首要目的在于为运算表达式提供返回值,这样既便于简化原有的代码,也便于大型代码的维护。
SV的函数定义与使用与C类似:
- 函数可以指定输入变量和输出变量
- 函数可以返回数值,也可以不返回数值(不返回数值用void函数)
可以声明的函数的参数方向
- input(默认值)
- output
- inout
- ref
没有声明方向,默认情况为输入方向input 。
声明参数列表方式:
- 将参数和方向在参数列表中声明
- 将参数和方向在函数体中声明
示例:
function logic [15:0] myfunc1(int x, int y);
...
endfunction
function logic [15:0] myfunc2;
input int x;
input int y;
...
endfunction
SV的函数返回的方式 同C一致
- 使用return直接返回值(return会立即返回)
- 将值赋给与函数同名的变量(赋值给函数同名变量后将继续执行后续代码)
函数可以调用函数,但是必须立即返回。即不能发生阻塞等待的行为。
示例:
//myfunc1,返回值经过计算,赋予了与函数同名的变量
function [15:0] myfunc1 (input [7:0] x, y) ;
myfunc1 = x *y - 1;
endfunction
//myfunc2,将计算的返回值,通过return返回,并且立即退出函数
function [15:0] myfunc2 (input [7:0] x, y);
return x *y - 1;
endfunction
void函数
void函数不会返回数值。
如果调用具有返回值的函数,但是又不使用该返回值时,我们建议为其添加void’()进行类型转换。【这一步骤可以消除在编译时带来的警告信息】
void' (some_function());
任务task
与函数类似,任务也有参数列表 。但是任务没有返回值。
任务的定义可以指定声明参数
- input
- output
- inout
- ref
任务声明参数方式(和函数一样)
- 将参数和方向在参数列表中声明
- 将参数和方向在任务体中声明
示例:
task mytask1 (output int x, input logic y);
...
endtask
task mytask2;
output x;
input y;
int x;
logic y;
...
endtask
任务可以消耗仿真时间。可以内置一些阻塞语句。(与函数不同)
任务可以调用其它任务或者函数。
虽然task不会返回值,但是我们依然可以利用return来使task结束。
示例:
在任务light,需要先获取参数,再来决定需要等待多少个时钟周期,继而将clock置为off。
light内置了阻塞任务,要封装这样的操作,只能通过任务,而不是函数。
任务light,在always过程块中被调用,将其最终的结果赋值给red
logic red;
parameter red_tics = 100;
always begin //控制灯光
red = on; //打开红灯
light(red,red_tics) ; //等待end
//等待‘tics’时钟上升沿的任务
//在关掉‘color’灯之前
task light (output color,input [31:0] tics) ;
repeat (tics)@ (posedge clock);
color = off;//关灯
endtask: light
任务和函数的区别
- 函数不会消耗仿真时间,而任务则可能会消耗仿真时间。(最大的区别)
- 函数无法调用任务,而任务可以调用函数。(函数需要立即返回,无法内置阻塞语句或者可能带有阻塞行为的任务,因此函数只能调用函数,无法调用任务。反过来,任务可以调用函数,任务也可以调用任务,因为任务是可以内置阻塞语句的)
- 一个函数只能返回一个数值,而任务不会返回数值。(如果一个函数具备返回值,而不是void返回类型时,函数调用后的结果可以做为一个表达式中的操作数)
- 函数可以作为一个表达式中的操作数,而该操作数的值即函数的返回值。
参数传递
参数类型
input参数
input参数方向在方法调用时,属于值传递。
即传递的过程中,外部变量的值会经过拷贝,赋值给输入参数。
output、inout 参数
与input参数类似,output、inout也会在传入或者传出的时候发生值传递的过程。
值传递的过程只发生在方法的调用时和返回时。
ref参数
与前面三个参数不同。
ref参数在传递时不会发生值拷贝,而是将变量指针传递到方法中,在方法内部对该参数的操作将会同时影响外部变量。
如果为了避免外部传入的ref参数会被方法修改,则可以添加const修饰符,来表示变量是只读变量。
传递参数的方式
- 由参数位置在调用方法时传递参数
- 由参数名字映射的方式来传递参数。(SV允许类似于模块例化)
遵循参数位置传递参数
指定参数的默认值
SV允许方法声明输入参数时指定参数的默认值。
带有参数默认值的方法被调用时,如果这些参数没有被传递值,那么编译器将会为这些参数传入对应的默认值。
示例:
调用read()函数,传递的参数方式有很多种。简单概括:
两个输入参数可以使用缺省值,在调用时,至少要传递 1个参数值,还应遵循三个参数前后位置进行传递。
//没有指定方向的情况下,参数都是输入方向
//为参数j 、data指定了默认值
task read(int j = 0, int k, int data = 1 );
...
endtask
read( , 5 );//等同于read ( 0,5,1 );
read ( 2,5 );//等同于read( 2,5,1 );
read( , 5,);//等同于read ( 0,5,1 );
read( , 5,7 );//等同于read( 0,5,7 );
read( 1,5,2 );//等同于read( 1,5,2 );
read ( );//错误;k没有默认值
read( 1,, 7 );//错误;k没有默认值
由参数名字映射的方式来传递参数
SV允许类似于模块例化,可由参数位置在调用方法时传递参数,也可以由参数名字映射的方式来传递参数。
示例:
function int fun( int j = 1,string s = "no”);
...
endfunction
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