0
点赞
收藏
分享

微信扫一扫

System Verilog — 面向对象编程基础

目录

一、类的概述

句柄(空间指针)
对象(存储空间)

Transaction t1, t2;//声明句柄t1,t2
t1 = new();//例化对象,将其句柄赋予t1
t2 = t1;//将t1的值赋予t2,即t1和t2指向同一个对象
t1 = new();//例化第二个对象,并将其句柄赋予t1 
//此时,t1指向第二个创建的对象
//t2指向第一个创建的对象
  1. SV在没有任何一个句柄指向一个对象时会自动回收其空间。
  2. 句柄可以用来创建多个对象,也可以前后指向不同对象。
  3. 与硬件域如module不同的是,class中声明的变量默认类型为动态变量,生命周期始于对象创建,终于对象销毁。
    如果使用关键字static来声明class内的变量时,则其为静态变量。生命开始于编译阶段,贯穿于整个仿真。
  4. 可以通过extern关键字在类之外定义方法。

二、句柄的使用

调用方法时,传递的是对象的句柄而非对象本身。
在程序执行时,可以在任何时刻为句柄创建新的对象,并将新的指针赋值给句柄。

三、对象的拷贝

1.概念

  1. 拷贝对象指的是首先创建一个新的对象(开辟新的空间),再将目标对象的成员变量值拷贝给新对象的成员,这就使得新对象与目标对象的成员变量数值保持一致,即完成了对象的拷贝。
  2. 就SV普通的变量拷贝而言,只需要通过赋值操作符“=”就足够了。而对象的拷贝则无法通过“=”来实现,因为这一操作是句柄的赋值,而不是对象的拷贝。

2.浅拷贝与深拷贝

浅拷贝和深拷贝都属于对象拷贝而不是句柄拷贝。
浅拷贝类似于原对象的一个影印本,原对象的值被盲目地抄写到目的对象中,如果类中包含一个指向另一个类的句柄,那么,只有最高一级的对象被复制,下层的对象都不会被复制。

class Transaction;
	bit[31:0]addr, crc, data[8];
	static int count = 0;
	int id;
	Statistics stats;//在Trasaction类中声明一个指向Statistics类对象的句柄
	function new;
		stats = new();
		id = count++;
	endfunction
endclass

Transaction src, dst;
initial begin
	src = new();//创建一个Transaction对象
	src.stats.startT = 42;
	dst = new src;//用new操作符将src拷贝到dst中
	dst.stats.startT = 96;//改变dst和src的stats
	$display(src.stats.startT);
end

在这里插入图片描述

如上图所示,当调用new函数进行复制的时候,Transaction对象(数据变量id和Statistics的句柄的值stats)被复制,但是Statistics的对象的值startT没有被复制,两个句柄指向同一个内存空间,所以使用dst修改startT会影响到src的值。

在使用深拷贝时,通过自己定义copy方法,会复制对象中的所有成员变量以及对象中嵌套的其他类的实例的内容。

class Transaction;
	bit[31:0]addr, crc, data[8];
	static int count = 0;
	int id;
	Statistics stats;//在Trasaction类中声明一个指向Statistics类对象的句柄
	function new;
		stats = new();
		id = count++;
	endfunction
	function Transaction copy;
		copy = new();
		copy.addr = addr;
		copy.crc = crc;
		copy.data = data;
		copy.stats = stats.copy();//调用Statistics::copy函数
		id = count++;
	endfunction
endclass

class Statistics;
	time startT,stopT;
	function Statistics copy;
		copy = new():
		copy.startT = startT;
		copy.stopT = stopT;
	endfunction
endclass

Transaction src, dst;
initial begin
	src = new();//创建一个Transaction对象
	src.stats.startT = 42;
	dst = src.copy();//使用深拷贝将src复制给dst
	dst.stats.startT = 96;//仅改变dst的stats值
	$display(src.stats.startT);
end

在这里插入图片描述

3.使用建议

  1. 将成员拷贝函数和新对象生成函数分为两个方法,这样使得子类函数和方法复用较为容易。
  2. 为了保证父类和子类的成员均可以完成拷贝,将拷贝方法声明为虚方法,且遵循只拷贝该类的域成员的原则,父类的成员拷贝应由父类的拷贝方法完成。
  3. 在实现拷贝函数的过程中应该注意句柄的类型转换,保证转换后的句柄可以访问类成员变量。

四、类的三要素

1.封装

类作为载体,也具备了天生的闭合属性,即将其属性和方法封装在内部,不会直接将成员变量暴漏给外部。

  1. 如果没有指定访问类型,那么成员的默认类型是public,子类和外部均可以访问成员。
  2. 如果指定访问类型是protected,那么只有该类或者子类可以访问成员,而外部无法访问。
  3. 如果指明访问类型是local,只有该类可以访问成员,子类和外部均无法访问。

2.继承

就继承来看,类的继承包括了继承父类的成员变量和成员方法。同时,子类可以在内部通过super来索引父类的同名函数。
默认情况下,如果没有super或者this来指示作用域,则依照从近到远的原则来引用变量即:

  1. 首先看变量是否是函数内部定义的局部变量。
  2. 其次看变量是否是当前类定义的成员变量。
  3. 最后看变量是否是父类或者更底层类的变量。

3.多态

类型转换

1.隐式转化:
不需要进行转换的一些操作,例如赋值语句右侧是4位的矢量,而左侧是5位的矢量,隐式转换会先做位宽扩展,然后再做赋值。
2.显式转换:
需要操作符号或者系统函数介入,静态转换和动态转换均为显示转换。
静态转换在需要转换的表达式前加上单引号即可,该方式并不会对转换值做检查。如果转换失败,我们也无从得知。
动态转换需用系统函数$cast(tgt, src)做转换,在使用时有两种情况:
(1)如果将子类句柄赋值给父类句柄时,编译器认为赋值是合法的。
(2)如果将父类句柄赋值给子类句柄,则需要使用 $cast()函数进行转换,否则会出现编译错误。
原因:
(1)当一个类被扩展时,所有的基类变量和方法将被继承,所以基类句柄可以指向扩展对象,因为任何使用基类句柄的引用都是合法的。
(2)如果做反方向的赋值,即将一个基类对象拷贝到一个扩展类的句柄则会失败。因为有些属性仅存在于扩展类中,基类并不具备。
$ cast
(1) $cast()会检查句柄所指向的对象类型,而不仅仅检查句柄本身。一旦源对象跟目标对象是同一类型,或者是目的类的扩展类,就可以从基类句柄中拷贝扩展对象的地址给扩展对象的句柄了。
(2) $cast()执行成功后返回1,否则返回0。

虚方法

(1)如果使用了virtual修饰符,SV会根据对象的类型,而非句柄的类型来决定调用什么方法。在下面最后两行代码中,tr指向一个扩展类对象(BadTr),所以调用的方法是BadTr::calc_crc。
(2)如果没有使用virtual修饰符,SV会根据句柄的类型,而不是对象的类型来调用方法。就会导致下方代码最后那个语句调用父类的方法Transaction::calc_crc。

class Transaction;
	rand bit[31:0]src, dst, data[8];
	bit[31:0]crc;
	virtual function void calc_crc();
		crc = src^dst^data.xor;
	endfunction
endclass

class BadTr extends Transaction;
	rand bit bad_crc;
	virtual function void calc_crc();
		super.calc_crc();
		if(bad_crc)	crc = ~crc;
	endfunction
endclass
//类方法的调用
Transaction tr;
BadTr bad;
initial begin
	tr = new();
	tr.calc_crc();//调用Transaction::calc_crc

	bad = new();
	bad.calc_crc();//调用BadTr::calc_crc

	tr = bad;//基类句柄指向扩展对象
	tr.calc_crc();//调用BadTr::calc_crc
end

虚方法使用建议:
(1)虚方法通过virtual声明,只需要声明一次即可。
(2)虚方法如果要定义,应该尽量定义在底层父类中。这是因为如果virtual是声明在类继承关系的中间层类中,那么只有从该中间类到其子类的调用链中会遵循动态查找,而最底层类到该中间类的方法调用仍然会遵循静态查找。
(3)在为父类定义方法时,如果该方法日后可能会被覆盖或者继承,那么应该声明为虚方法。
(4)虚方法的继承也需要遵循相同的参数和返回类型。

五、参数化的类

参数化的类的使用可以提高代码的复用率,在SV中,可以为类增加若干个数据类型参数,并在声明类句柄的时候指定类型。

//在类定义时添加参数#(type T = int),这表示后期类在声明变量时,
//如果不指定参数类型,则默认采用int类型。
class Stack # (type T = int);
...
endclass
initial begin
	Stack # (real)rStack;//创建一个存储real类型的Stack
...
end
举报

相关推荐

0 条评论