【system verilog】task和function的区别与使用方法
一、systemveilog中函数和任务的使用方法:
1、一般情况下,不带参数的子程序在定义或调用时不需要带空括号();
2、begin…end为可选的,因为task…endtask和function…endfunction关键词足以定义这些程序的边界, 但是在verilog-1995中,除了单行以外的子程序是必须的;
3、可以采用简明的c语言风格,缺省的类型和方向是logic input;但是在verilog中,对参数要进行两次声明,一次是方向声明,一次是类型声明;如下面代码示例。
//systemverilog风格
task test(a, b, output bit [15:0] c, d);
//其中a,b为input logic a,b; c,d为output bit [15:0];
//verilog风格
task test2;
output [31:0] x;
reg [31:0] x;
input y;
endtask
4、可以在参数列表中指定输入参数(input),输出参数(output)、输入输出参数(inout)或引用参数(ref);只有数组变量可以在形式参数列表中声明为ref类型,而线网类型不能声明为ref类型;在使用ref时,有时候为了保护数据对象不被写入,可以同通过const方式来限定ref声明的参数;详细关于systemverilog中ref的具体用法见本文最后一节;
但是在verilog中,对参数的处理方式很简单,在子程序的开头把input或inout的值复制给本地变量,在子程序退出时,则复制output或inout的值。除了标量以外,没有任何把存储器传递给verilog子程序的方法;
5、数组可以作为形式参数传递;
6、可以为参数指定一个缺省值,如果在调用时不指明参数,则使用缺省值。如下面代码示例;
function void print_checksum(ref bit [31:0] a[], input bit [31:0] low, input int high = -1);
bit [31:0] checksum = 0;
if(high = -1 || high >= a.size())
high = a.size() - 1;
for(int i = low, i < high; i++)
checksum += a[i];
$display("The array checksum is %0d", checksum);
endfunction
print_checksum(a); //a[0:size()-1]中所有元素的校验和---缺省情况
print_checksum(a, 2, 4); //a[2:4]中所有元素的校验和
print_checksum(a, 1); //a[1;$]中所有元素的校验和
print_checksum(a,,2) //a[0:2]中所有元素的校验和
7、采用名字进行参数传递;
task many(input int a = 1, b = 2; c = 3, d = 4);
$display("%0d %0d %0d %0d", a, b, c, d);
endtask
initial begin
many(6, 7, 8, 9); //a b c d的值分别为 6 7 8 9
many(); //a b c d全部使用默认值
many(.c(8)); //c的值为8,其他全部用默认值
many(,6, , d(8)); // 1 6 3 8混合指定方式
end
8、input端口是输入端口;output是输出端口;还有inout端口。
inout端口用于双向连接。如果使用多个inout端口驱动一个信号,sv将会根据所有驱动器的值,驱动强度来计算最终的值。
9、需要注意的其他问题:
task sticky(ref int array[10], int a, b);
上述a和b参数类型会采用与前一个参数一致的ref类型,对简单的int变量使用ref并无必要,但编译器不会对此做出任何反应,可能连警告都没有,这是使用了一个错误的方向类型。
如果你在子程序中使用了非缺省输入类型的参数,应该明确指明所有参数的方向,如下所示:
task sticky(ref int array[10], input int a, b);
二、task介绍
task的特点
- task 可以内置耗时语句,也就是当调用task时,这个结果可以不是立即返回的;
- task 既可以调用function,也可以调用task;
- task不可以用 return 返回值,因此只能通过output, inout或者ref参数来返回,但task可以用return来结束此task执行,提前返回,见下面代码实例1;
- task 可以在参数列表中定义参数方向,以此来返回值,可以返回一个或多个值;
- task的参数列表中的参数,如果不规定方向,默认是input;
- 缺省的参数数据类型是 logic类型;
- task可以内置耗时语句,而function不能。常见的耗时语句包括@event, wait event, #delay等。
实例1:
task load_array(int len, ref int array[]);
if(len < 0) begin
$display("bad len");
return;
//其他代码
endtask
task的参数方向
参数方向可以是:input、output、inout、ref(即引用,后面会介绍使用方法)
如下,是两种带方向定义的task;
//input a,b ;默认是input方向
//output logic [15:0] u,
//output logic v;
task mytask1(a, b, output logic [15:0] u, v);
#10ns;
endtask
task mytask2;
input a;
input b;
output logic [15:0] u;
output logic v;
...
endtask
实例:
module traffic_lights;
logic clock, red, green;
parameter on = 1, off = 0;
parameter red_tics = 350, green_tics = 200;
initial red = off;
initial green = off;
always begin // waveform for the clock
#100 clock = 0;
#100 clock = 1;
end
always begin
red = on; // 打开红灯
light(red, red_tics); // 等待red_tics周期后,关闭红灯
green = on; // 打开绿灯
light(green, green_tics); // 等待green_tics周期后,关闭绿灯
end
//首先会等到tics周期的时钟,然后会将传递进来相应颜色的灯关闭
task light (output color, input [31:0] tics);
repeat (tics) @ (posedge clock);
color = off; //关闭相应颜色的灯
endtask: light
endmodule: traffic_lights
task中ref的使用
以下是直接将数组传递到task中。这种方式会直接将这个a,b数组复制到堆栈中,然后再传递到方法中。
//input [3:0][7:0] a
//input [3:0][7:0] b [3:0]
//output [3:0][7:0] y[1:0]
task mytask3(input [3:0][7:0] a, b[3:0], output [3:0][7:0] y[1:0]);
...
endtask
如果数组占用内存比较小,倒还无所谓。如果是一个很大的数组,那就会影响内存了。这时候就可以使用 ref 来传递数组。
ref是变量(但不能是net)的引用,它的值是该变量最后一次赋值的值。
但是如果将一个变量连接到多个ref端口,就可能产生竞争的问题,因为多个模块的端口都有可能更新同一个变量;
使用ref的优点:
参数传递的方式是引用,而不是复制。如果不用ref,直接引用数组,会被复制在堆栈区,影响性能;
在任务中修改变量的结果对调用它的函数随时可见;
const ref 可以使引用的对象在子程序中不被改变;
静态和动态task
关于静态和动态的可以先看我的博客:【system verilog】静态/动态变量/方法概念。
在module, interface, program 和 package的 task 默认是static的。
如果一个task被声明为static,那么在整个仿真过程中,这个task都是可以被其他可见的,会一直存在。
如果一个task是static/automatic 的,那么其内所有的成员变量也是static/automatic ;
如果想在module内声明一个automatic的task,需要显示的加上automatic修饰,如下:
task automatic my_auto_task(x,output y);
...
endtask
三、function介绍:
function的特点
- function中不可以内置耗时语句,被调用时是立即返回的,即不消耗仿真时间;
- 函数至少要有一个输入变量,可以无返回值或一个返回值;
- 函数参数列表中的变量缺省数据类型为logic类型;
- 函数参数参数列表中的变量如果没有规定反向,默认是 input方向;
- 如果函数有返回值,函数的返回值类型需要在函数定义时指定,且只能通过return返回一个值,或者通过赋值给与函数同名的内部变量(用的很少);
- 如果函数没有返回值,要在函数定义时指定为 void ;
- 函数不可以调用task,因为task可能含有耗时语句,而函数不支持耗时语句。
- 在systemveilog中,允许函数调用任务,但是只能在由fork…join_none语句生成的线程中调用。——此条待试验确定
- function使用场景:处理一些数据运算处理,给输入,然后返回一个结果;
function int add(input int a, b);
int c;
c = a + b;
return c;
endfunction
function可以返回void;
progrem
int a, b, c;
function void add();
c = a + b;
endfunction
initial begin
a = 1;
b = 2;
add();
end
endprogrem
function的参数方向
参数方向可以是:input、output、inout、ref ;
下面是一些示例:
// input int x ;
// input int y;
function logic [15:0] myfunc1(int x, int y);
...
endfunction
function logic [15:0] myfunc2;
input int x;
input int y;
...
endfunction
//返回的参数类型logic [15:0]
function logic [15:0] myfunc3(int a, int b, output logic [15:0] u, v);
...
endfunction
function的返回值
有返回值时,function定义如下:
//方式一:通过将一个值赋给与函数同名的内部变量;即function中可以使用return得到函数的返回值,不使用return时,与函数名字相同的变量的值就是返回值。
//这种用的很少,在SV绿皮书中介绍深拷贝章节就是用的这种做法
function [15:0] myfunc1 (input [7:0] x,y);
myfunc1 = x * y - 1; //
endfunction
//方式二:用return返回
function [15:0] myfunc2 (input [7:0] x,y);
return x * y - 1; // 用return返回值
endfunction
a = b + myfunc1(c, d);
无返回值时,则应该声明函数为void function,function定义如下:
myprint(a);
function void myprint (int a);
...
endfunction
function void print_state();
$display("@%0t: time = %s", $time);
endfunction
如果不想要函数的返回值,还可以使用强制转换的方式,将一个有返回值的函数丢弃其返回值,如下:
void'($cast(tr,h));
//正常情况下,如果动态转换cast失败与成功,都会有返回值的。
//当我们不关心这个返回值时,就可以采用强制转换为void形式
从函数返回一个数组:
有三种方法:
第一种方法是定义一个数组类型,然后在函数的声明中使用该类型。
program
typedef int fixed_array5[5];
fixed_array5 f5;
function fixed_array5 init(int start);
foreach(init[i]) begin
init[i] = i + start;
end
endfunction
initial begin
f5 = init(5);
foreach(f5[i[) begin
$display("fd[%0d] = %0d", i, f5[i]);
end
end
endprogram
上述代码存在的问题是,函数init创建了一个数组,给数组的值被复制到数组f5中。如果数组很大,那么可能会引起一个性能上的问题。
第二种方法是通过引用来进行数组参数的传递,如下面代码所示。
program
function void init(ref int f[5], input int start);
foreach(f[i]) begin
f[i] = i + start;
end
endfunction
int fa[5];
initial begin
init(fa, 5);
foreach(fa[i] begin
$display("fa[%0d] = %0d", i, fa[i]);
end
end
endprogram
第三种从函数中返回数组的方式是将数组包装到一个类中,然后返回对象的句柄。
静态和动态function
可以参考我之前的文章:【system verilog】静态/动态变量/方法概念。
四、task和function的区别总结:
-
task可以消耗时间(即可以置入耗时语句),而function不能。function里不能带有诸如 #100 的时延语句或诸如 @ (posedge clock)、wait (ready) 的阻塞语句。
-
task无法通过return返回结果(task中使用return可以实现立即退出此task),因此只能通过output、inout或ref的参数来返回。function可以返回也可以不返回结果。返回,则需用关键词 return;如不返回,则应在声明function时采用 void function()。另外,Verilog中的function必须有返回值,并且返回值必须被使用,例如用到赋值语句中。
-
task中可以调用task,task中也可以调用function;
-
function中可以调用function,但不建议调用task(因为task可能内置阻塞等待语句,如#1ns),如果function调用了某个task,如果这个task中没有阻塞等待语句,则VCS编译只报warning,如果这个task中包含了阻塞等待语句,那么VCS编译将会报Error;
task和function的选择建议:
-
初学者建议使用傻瓜式用法,即全部采用task来定义方法,因为它可以内置常用的耗时语句。有经验者可以对这两种方法加以区分,以便遇到这两种方法定义时,就知道function只能运用于纯粹的数字或逻辑运算,而task则可能会被运用于需要耗时的信号采样或驱动场景。如果你有一个不耗时的SystemVerilog任务,你可以把它定义为void函数,这种函数没有返回值。这样它就能被任何任务或函数所调用。
-
在不耗时定义为function,在内置耗时语句时使用task;function一般用于纯粹的数字或逻辑运算,task可能被运用于需要耗时的信号采样或者驱动场景;
-
function可以被function和task调用,内置有耗时语句的task只能由task来调用,不然会报错的(请见例题2);
【例题2】:关于systemverilog中,function 可以调用 没有时间延迟的task。
`timescale 1ns/1ps
import uvm_pkg::*;
//`include "uvm_pkg.sv"
//`include "uvm_macros.svh"
module tb_top();
initial begin
run_test("helloworld_test") ;
end
initial begin
$fsdbDumpfile("tb_top.fsdb");
$fsdbDumpvars(0,"tb_top");
#0;
//$finish();
end
endmodule
`ifndef HELLOWORLD_TEST
`define HELLOWORLD_TEST
`timescale 1ns/1ps
import uvm_pkg::*;
//`include "uvm_pkg.sv"
//`include "uvm_macros.svh"
//import uvm_pkg::*;
class helloworld_test extends uvm_test;
`uvm_component_utils(helloworld_test)
function new(string name="" , uvm_component parent);
super.new(name,parent);
endfunction:new
extern function void start_of_simulation_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
extern task display_string(string strs);
endclass:helloworld_test
task helloworld_test::display_string(string strs);
`uvm_info(get_type_name(),$psprintf("/%s/ Function#display_string is start",strs),UVM_NONE);
#20;
`uvm_info(get_type_name(),$psprintf("/%s/ Function#display_string is ended",strs),UVM_NONE);
endtask:display_string
function void helloworld_test::start_of_simulation_phase(uvm_phase phase);
super.start_of_simulation_phase(phase);
display_string("<start_of_simulation_phase>");
endfunction
task helloworld_test::main_phase(uvm_phase phase);
super.main_phase(phase);
phase.raise_objection(this,"");
display_string("<main_phase>");
phase.drop_objection(this,"");
endtask
`endif
运行编译报错:一个warning , 一个 error.
我们稍作修改,去掉task dispaly_string 中的#20; 只报了warning。
五、ref使用方法详解
ref介绍
在module,function或者task中有input,output,inout等具有方向的声明变量,它们在作为参数传递的过程中是作为参数值传递的,效率较低,数据在每一次方法的调用过程中均需要被复制。而ref参数传递的则为传递参数的引用地址,本文介绍了ref的用法和注意事项。
ref用法
在使用ref参数以后,不同时刻hao的变化会被transfer_ref方法中的xiaohao所看见,并通过display打印得到,即子程序中调用函数的变量可以被方法中的变量修改。
module ref_1;
int hao;
initial begin
hao = 0;
#10 hao = 4;
#10 hao = 8;
#10 $stop;
end
initial begin
transfer_ref(hao);
end
task automatic transfer_ref(ref int xiaohao);
forever begin
#5 $display("transfer_ref: xiaohao is %0d", xiaohao);
end
endtask
endmodule
仿真结果如下:
同样,子程序中的函数同样可以监测到方法中参数的变化,如下,display函数中的hao参监测到了方法中的xiaohao参数变化并实现了打印。
module ref_1;
int hao;
initial begin
hao = 0;
fork
while(1) begin
#5 $display("transfer_ref: hao is %0d", hao);
end
transfer_ref(hao);
join_any
#20 $stop;
end
task automatic transfer_ref(ref int xiaohao);
#10 xiaohao = 4;
#10 xiaohao = 8;
endtask
endmodule
仿真结果如下:
若不想要方法内对子程序中调用方法的引用参数进行修改,可以使用const ref,示例如下,加const以后,方法内再对引用参数进行修改,仿真即会报错。
module ref_1;
int hao;
initial begin
hao = 0;
fork
while(1) begin
#5 $display("transfer_ref: hao is %0d", hao);
end
begin
transfer_ref(hao);
end
join_any
#20 $stop;
end
task automatic transfer_ref(const ref int xiaohao);
#10 xiaohao = 4;
#10 xiaohao = 8;
endtask
endmodule
ref使用注意事项
- ref指定的形参类型在方法被调用时,需要与被引用的参数的实参类型保持一致。
- 只有自动(automatic)任务和函数才可以使用ref参数;
关键:ref完就是实参,让函数外可以调用到函数里面的参数
————————————————————————————————
本文总结仅为学习只用,无其他目的。
本文部分内容转载自以下博主文章,表示感谢,有了你们的总结,让我受益匪浅!
如有侵权,请告知删除!
————————————————
版权声明:本文为CSDN博主「LynnOvO」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/LynnOvO/article/details/124068533
————————————————
版权声明:本文为CSDN博主「借问众神明.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_56242485/article/details/122931928
https://www.codeleading.com/article/45135989078/
————————————————————————————————