当使用继承来扩展类的功能的时候,需要一些OOP技巧来控制对象和功能。
如果将子类句柄赋值给父类句柄时,编译器则认为赋值是合法的,但分别利用子类句柄和父类句柄调用相同对象的成员时,将可能有不同的表现。
例1:不加virtual的function
class Transaction;
rand bit [31:0] src;
function void display(input string = ""); //没有virtual
$display(%s Transaction: src = %d", prefix, src);
endfunction
endclass
class BadTr extends Transaction;
bit [31:0] src;
bit bad_crc;
function void display(input string prefix = ""); //没有virtual
$display("%s BadTr: bad_src = %b", prefix, bad_crc);
super.display(prefix);
endfunction
endclass
Transaction tr;
BadTr bad, bad2, bad3;
//第一种情况
bad = new(); //构建BadTr扩展对象
tr = bad; //将子类句柄赋值给父类句柄
$display(tr.src); //显示基类对象的成员变量
tr.display(); //Transaction::display()
tr.src; //基类的src
bad.src //子类的src
bad.bad_src; //子类的bad_src
bad.super.src; //基类的src
通过tr找不到子类的src,虽然tr指向子类的对象,但是tr能查找到的范围只是父类范围的对象;
当一个类被扩展时,所有的基类变量和方法被继承,所以整数变量src存在于扩展类对象中。
//第二种情况
tr = new(); //创建一个父类
bad2 = tr; //编译错误
$display(bad2.bad_crc); //bad_crc成员不在父类对象中
如果你将一个基类对象直接拷贝到一个扩展类的句柄中,会发生操作失败,因为有些属性仅仅存在于扩展类中,基类并不具备。systemverilog编译器对句柄类型做静态检查。
//此时可以通过\$cast实现,$cast(bad, tr),如下所示:
//第三种情况
bad = new();
tr = bad;
$cast(bad2, tr);
$display(bad2.bad_crc); //bad2指向的对象包括bad_crc
bad2.display(); //子类的BadTr::display()
类型向下转换或者类型变换是指将一个指向基类的指针转换成一个指向派生类的指针。
1、将一个父类句柄赋值给一个子类句柄并不总是非法的;但是systemverilog编译器对这种直接赋值的做法是禁止的,也就是无论父类句柄是否真正指向一个子类对象,赋值给子类句柄时,编译(静态)都将出现错误。因此需要$cast(target, source)来实现句柄类型的动态转换;
2、$cast不仅仅检查句柄本身,还会检查句柄所指向的的对象类型。注意:指向父类对象的父类句柄不能转换为子类句柄,指向子类对象的父类句柄可以转换为子类句柄。
3、一旦源对象跟目的句柄是同一类型,或者是目的句柄的扩展类,$cast()函数执行成功会返回1,否则返回0。
虚方法:
例2: 加virtual的function函数
(将类中的子程序定义成virtual, 这样就可以在扩展类中重定义。这一点适用于所有的任务和函数,除了new函数。因为new函数在对象创建时调用,所以无法扩展。systemverilog始终基于句柄类型来调用new函数。)
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 : Transaction
class BadTr extends Transaction;
rand bit bad_crc;
virtual function void calc_crc;
super.calc_crc();
if (bad_crc) crc = ~ crc;
endfunction
endclass :BadTr
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
当需要决定调用哪个虚方法时,systemverilog根据对象的类型,而不是句柄的类型来决定调用什么方法。如例2最后一句tr.calc_crc.
如果没有virtual修饰符,systemverilog会根据句柄的类型而不是对象的类型来决定调用什么方法。如例1 tr.display().