资料:
-UVM Primer(英文原版)
-UVM Primer(中译版)
前言:
-本部分将包括UVM Primer的第4章-第9章,仅整理OOP特性,不会涉及DUT及验证平台构建的相关内容。
正文:
本书的4-9章以一个动物园为例,讲述了OOP的不同特性,包括:
-类
-继承
-多态
-静态变量和方法
-factory模式
第六章:多态
多态(polymorphism),简单的说就是具有多种形态,调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
-In programming language theory and type theory, polymorphism is the use of a single symbol to represent multiple different types.
现在让我们从最初开始,通过分类的方法理解多态。
class animal;
int age = 0;
function new(int a);
age = a;
endfunction : new
// A function with a fatal hint
function void make_sound();
$fatal(1,"General animals don't have noise!");
endfunction : make_sound
endclass
这是一个普通的动物类,包含一切动物,包括会叫的和不会叫的。因此虽然类的内部定义了方法,但却不应该在总体的animal中使用。
那么还像之前那样,我们基于动物大类创造狮子类,并在其中重载make_sound()
函数:
class lion extends animal
function new(int age);
super.new(age);
endfunction
// Override make_sound
function void make_sound();
$display("Lion Roar!");
endfunction
endclass : lion
类似的,我们可以创造鸡类,并赋予其不同的叫声。值得注意的一点是,由于SV要求我们显式的写出带有参数的构造函数,不能简单的继承基类中的构造函数。
那如果我们创建一个狮子,然后使用animal_h句柄指向它呢?
animal_h = lion_h;
animal_h.make_sound();
$display("The animal is %0d years old", animal_h.age);
这时候,编译器会报错"General animals don't have noise!",这是由于编译器按照animal的类型调用了make_sound()
。
这个时候我们会想到,狮子显然是动物的一个子集,因此从逻辑上来说,使用animal_h句柄指向它是不应该有问题的。所以最根本的问题是,应该如何让编译器知道,我们想调用的是lion内的方法呢?
这时候,我们就可以将基类中的方法变为虚方法(virtual),这意味着,基类中的make_sound()
方法仅起到指示作用,这会告诉编译器“后续继承的类中有这么个方法,但是需要子类自己去进行重载”,如果子类不进行重载,那么基类的fatal将会报错!
class animal;
int age=-1;
function new(int a);
age = a;
endfunction : new
// Notice the virtual keyword
virtual function void make_sound();
$fatal(1, "Generic animals don't have a sound.");
endfunction : make_sound
endclass : animal
如果更进一步,我们可以将animal定义为抽象类(virtual class),这意味着其只能作为基类,且无法例化;而抽象类内部的方法,可以定义为纯虚方法( pure virtual),这意味着编译器会要求子类强制重载这个方法,并将进行检查。
// Notice virtual class
virtual class animal;
int age=-1;
function new(int a);
age = a;
endfunction : new
// This is a pure virtual method now without fatal
pure virtual function void make_sound();
endclass : animal
如果尝试例化抽象类,编译器会报错:
在这里,我在使用之前看到的一个帖子当作例子:
作者的疑问是,在句柄“A1”指向B1后,原先例化的基类A1的内存就应当被释放了,那么为什么A1还能够访问基类的方法呢?
这是由于在创建一个子类的实例时,子类会继承父类的属性和方法。这意味着子类的实例可以访问父类中定义的属性和方法。在访问这些属性和方法时,实际上是在子类的实例上执行操作,但是这些操作可能会涉及到父类中的属性和方法。
在没有显式创建父类实例的情况下,子类实例如何访问父类的属性和方法呢?这是因为在创建子类实例时,系统会隐式地为父类分配内存,并且子类实例中包含了父类的部分。这是通过子类的构造函数(通常是 new()
函数)来实现的。
具体来说,当你创建子类的实例时(例如 B B1 = new();
),子类的构造函数会被调用。在子类的构造函数中,通常会隐式地调用父类的构造函数(通常是 super.new()
)。这样,父类的内存就被分配了,并且子类的实例中包含了父类的属性和方法。因此,子类实例可以通过自己的指针访问父类的属性和方法,因为这些属性和方法已经在子类实例中了。