全局变量和局部变量
在讨论静态变量和动态变量之前,我们先说全局变量和局部变量的概念。
局部变量的生命周期同其所在域,例如function/task中的变量,在方法调用结束后,这些变量的也将消失,所以它们是动态生命周期;
全局变量是从仿真开始到结束一直存在的,例如module中的变量默认情况下全部为全局变量,这也可以理解为module中的变量是硬件电路中实际存在的信号和连接,所以它们是静态生命周期;
静态变量和动态变量
变量可以分为动态(automatic)和静态(static);
静态变量的特点:
该变量将被这个类的所有实例所共享,并且使用范围仅限这个类。
静态变量在声明时就应该对其初始化,它只初始化一次,也就是在仿真0时刻就存在。
可以认为声明在类中的静态成员变量,它是保存在类中,而不是在对象中,它会一直存在的,不会因为对象被销毁而消失。
静态变量可以在类没有被实例化的时候调用,通过 class::static_variable 的方式获取静态变量。
对于普通的实例类属性,每一个类实例的每一个变量都有自己的copy(单独的内存空间),相互独立。但是有时会要求一个类的所有实例都共享变量的一个版本,也就是说所有实例都共享同一个copy,该变量对所有实例都是可见并相同的。
这样的类属性就是静态属性(静态变量),使用关键字static产生。通过下面的例子可以清楚的看见静态属性的特点。
class Packet;
bit [15:0] addr;
bit [7:0] data;
static int static_ctr = 0;
int ctr = 0;
function new (bit [15:0] ad, bit [7:0] d);
addr = ad;
data = d;
static_ctr++;
ctr++;
$display ("static_ctr=%0d ctr=%0d addr=0x%0h data=0x%0h", static_ctr, ctr, addr, data);
endfunction
endclass
module tb;
initial begin
Packet p1, p2, p3;
p1 = new (16'hdead, 8'h12);
p2 = new (16'hface, 8'hab);
p3 = new (16'hcafe, 8'hfc);
end
endmodule
ncsim> run
static_ctr=1 ctr=1 addr=0xdead data=0x12
static_ctr=2 ctr=1 addr=0xface data=0xab
static_ctr=3 ctr=1 addr=0xcafe data=0xfc
ncsim: *W,RNQUIE: Simulation is complete.
静态属性还有一个重要的特点是其类无需实例化,就可直接使用静态属性,此时需要利用类作用域操作符::。
动态变量的特点:
动态变量是类在实例化时,即调用构造函数new( )才会初始化。
它的声明周期随着对象而存在,当对象被销毁时,这个变量就也就消失了。
必须要在类被实例化之后才可以调用。
如下,使用静态变量count来计数所创建的实例数目:
class Transaction;
static int count = 0;
int id;
function new();
id = count++;
endfunction
endclass
Transaction t1,t2;
initial begin
t1 = new(); // t1 中:count = 1,id =0
t2 = new(); // t2 中:count = 2,id =1
$display("t2中count=%0d和id=%0d",t2.count,t2.id);
$display("Transaction中count=%0d",Transaction::count);
end//Transaction类中:count = 2
解释代码:
- 每例化一次transaction,count就自加1
- 第一次在例化时,cout值为0,所以对象 t1 中的 id = 0;
- 类的构造函数调用完后,使得count自加1,count = 1;
- 第二次在例化时,cout值为1,所以对象 t2 中的 id = 1;
- 可以通过Transaction::count的方式,访问类中的静态变量,此时类中的count = 2;
静态方法和动态方法
static 和 automatic 除了可以修饰类中的成员变量,还可以修饰 function 和 task,被static修饰的方法称为静态方法。
静态方法的特点:
如果方法被static修饰,那么其内部所有的声明的变量都是 static 的;
静态方法可以在类没有被实例化时被调用,通过 :: 操作符获取,具有全局的静态生命周期;
如果方法被声明为static,那么在仿真开始时即会被创建,且可以被多个进程和方法共享;
使用关键字static可以将类方法声明成静态方法。
一个静态方法遵守所有的类范围和访问规则,但是它可以在类的外部被调用,即使没有该类的实例。外部调用的方法同样需要使用范围操作符::。
一个静态方法不能访问非静态的属性或方法,但是可以直接访问静态属性,或者调用同一个类中的静态方法,原因很简单因为静态属性和静态方法可以在类没有实例化时被调用,具有全局的静态生命周期,而普通的非静态成员无法做到这点。
在一个静态方法内部访问非静态成员或者使用this句柄都是非法的。
静态方法不能是虚拟的。
class Packet;
static int ctr=0;
function new ();
ctr++;
endfunction
static function get_pkt_ctr ();
$display ("ctr=%0d", ctr);
endfunction
endclass
module tb;
Packet pkt[6];
initial begin
for (int i = 0; i < $size(pkt); i++) begin
pkt[i] = new;
end
Packet::get_pkt_ctr(); // Static call using :: operator
pkt[5].get_pkt_ctr(); // Normal call using instance
end
endmodule
ncsim> run
ctr=6
ctr=6
ncsim: *W,RNQUIE: Simulation is complete.
注意上述代码中对get_pkc_ctr()的调用方式,一种是使用范围操作符::,一种是使用普通的层次化引用方式。两者结果没有区别,并且pkt[]的6个成员的调用结果都会是一样的(操作对象是只有一个copy的静态属性)。
动态方法的特点:
如果方法被修饰为 automatic,那么其内部所有的声明的变量默认都是 automatic的;
如果被修饰为 automatic,那么在进入该方法后,automatic变量会被创建,而离开该进程/方法后就被销毁;
静态方法和动态方法的区别
静态方法和动态方法的区别,如下:
module static_test;
function automatic int cnt1(input a); // 函数内所有的变量均为automatic
int cnt = 0; //这个变量在每次调用时都会初始化
cnt += a ;
return cnt;
endfunction
function static int cnt2(input a); // 函数内所有的变量均为static
static int cnt = 0; //这个static 变量 cnt只会初始化一次
cnt += a ;
return cnt;
endfunction
function int cnt3(input a); // module中的方法默认为static
int cnt = 0; //这个static 变量 cnt只会初始化一次
cnt += a ;
return cnt;
endfunction
endmodule
initial begin
$display("%0d",cnt1(1)); // 输出1
$display("%0d",cnt1(1)); // 输出1
$display("%0d",cnt2(1)); // 输出1
$display("%0d",cnt2(1)); // 输出2
$display("%0d",cnt3(1)); // 输出1
$display("%0d",cnt3(1)); // 输出2
end
解释代码:
- 函数声明为automatic或static时,其块内所有的变量都是automatic或static的;
- automatic类型的变量随函数调用结束后就销毁了,下次再调用时,又会初始化;
- static类型的变量随函数调用结束后不会被销毁,下次调用时也不需要重新初始化;
- module内的方法默认是static的;
生命周期属性
在上面的静态属性和静态方法声明中,都使用到了关键字static,但是SystemVerilog还有另外一个含义的static,它与automatic一起用来决定数据的生命周期属性。
static声明的数据(并不是类属性)具有静态的生命周期(在整个确立和仿真时间中存在)
automatic具有调用期或者激活期类的声明周期。
在类外使用static声明的数据和在类内部使用static声明的类属性都有着静态生命周期(无需实例化分配内存),这两者的声明方式没有区别都是在数据前面加上static。只不过前者如果是全局作用范围(即在模块,接口,函数和任务外声明的),那么其使用方式就是直接使用,但是静态类属性也可以全局范围内引用,不过需要使用范围操作符::。
static和automatic可以将一个任务或者函数显式地声明成静态或者自动的:一个自动任务、函数或块内声明的数据缺省情况下具有调用期或激活期内的生命周期,并且具有本地的作用范围; 一个静态任务、函数或块内声明的数据缺省情况下具有静态生命周期并具有本地的作用范围。
此外静态任务或函数或块内的数据可以被显式地声明成automatic的,而automatic的任务、函数或块内的数据也可以被显式地声明成静态的。
其实将一个任务函数或块声明成static或者automatic还是为了决定其内部数据的生命周期属性,这与静态方法的static有本质上的不同。而且两者的声明方式也有区别:
- 如果想声明一个静态的任务函数或块:
class test;
//具有静态变量生命周期的非静态方法
task static bar();
……
endtask
endclass
- 如果想声明一个静态方法:
class test;
//具有自动变量生命周期的静态方法
static task bar();
……
endtask
endclss
由上可见两个static不仅含义不同,声明方式也不同。
注意第二句话"具有自动变量生命周期的静态方法",也就是说这个任务虽然是静态任务,但是其内部数据的缺省情况下的生命周期是自动的,这是因为对于模块、接口或程序中定义的任务函数或块可以用生命限定符static和automatic来指定其中声明的所有变量的缺省生命周期,默认的生命限定符是static, 然而对类方法以及for循环声明的循环变量缺省的生命限定符是自动的automatic。举个例子:
program automatic test;
int i; // 由于没有在一个过程块中,所以i还是静态的
task foo(int a); // 在foo内的自变量和变量是自动的
...
endtask
endmodule
补充两点:
在模块、接口、任务或函数外声明的任何数据都有全局的作用范围和静态生命周期
在模块或接口内但是在任务、函数或进程外声明的数据具有本地的作用范围,并具有静态生命周期。
总之:
- 在module、program、interface、task和function之外声明的变量拥有静态的生命周期,即存在于整个仿真阶段,同C定义的静态变量一样。
- 在module、interface和program内部声明,且在task、process或者function外部声明的变量也是static变量,且作用域在该块中。
- 在module、program和interface中定义的task、function默认都是static类型。
- 在过程块中(task、function、process)定义的变量均跟随它的作用域,即过程块的类型,如果过程块为static,则它们也默认为static,反之亦然。这些变量也可以由用户显式声明为automatic或者static。
————————————————————————————————
本文总结仅为学习只用,无其他目的。
本文部分内容转载自以下博主文章,表示感谢,有了你们的总结,让我受益匪浅!
如有侵权,请告知删除!
版权声明:本文为CSDN博主「ucanredo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43249032/article/details/84758128
版权声明:本文为CSDN博主「小小verifier」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/SummerXRT/article/details/120056366