【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的区别总结:

  1. task可以消耗时间(即可以置入耗时语句),而function不能。function里不能带有诸如 #100 的时延语句或诸如 @ (posedge clock)、wait (ready) 的阻塞语句。

  2. task无法通过return返回结果(task中使用return可以实现立即退出此task),因此只能通过output、inout或ref的参数来返回。function可以返回也可以不返回结果。返回,则需用关键词 return;如不返回,则应在声明function时采用 void function()。另外,Verilog中的function必须有返回值,并且返回值必须被使用,例如用到赋值语句中。

  3. task中可以调用task,task中也可以调用function;

  4. function中可以调用function,但不建议调用task(因为task可能内置阻塞等待语句,如#1ns),如果function调用了某个task,如果这个task中没有阻塞等待语句,则VCS编译只报warning,如果这个task中包含了阻塞等待语句,那么VCS编译将会报Error;

task和function的选择建议:

  1. 初学者建议使用傻瓜式用法,即全部采用task来定义方法,因为它可以内置常用的耗时语句。有经验者可以对这两种方法加以区分,以便遇到这两种方法定义时,就知道function只能运用于纯粹的数字或逻辑运算,而task则可能会被运用于需要耗时的信号采样或驱动场景。如果你有一个不耗时的SystemVerilog任务,你可以把它定义为void函数,这种函数没有返回值。这样它就能被任何任务或函数所调用。

  2. 在不耗时定义为function,在内置耗时语句时使用task;function一般用于纯粹的数字或逻辑运算,task可能被运用于需要耗时的信号采样或者驱动场景;

  3. 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/
————————————————————————————————

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值