目录
Overriding class members 覆盖类的成员
Data Hiding and Encapsulation 数据隐藏和封装
Abstract Classes and Virtual Methods 抽象类和虚方法
Scope Resolution Operator :: 作用域操作符
class
类包括数据以及操作数据的任务和函数,在类中任务和函数统称为方法。类可以通过对象句柄来动态创建、删除、分配和访问对象。
类的定义方式
下面的例子中,类包括变量x和两个方法 。
class sv_class;
//class properties
int x;
function new(int num);//构造方法,在创建对象时自动调用,一般用于初始化
x=num;
endfunction
//method-1
task set(int i);
x = i;
endtask
//method-2
function int get();
return x;
endfunction
endclass
类的实例化和对象的创建
类可以认为是用户定义的一种数据类型,因此可以像定义其他数据类型那样来定义类:
sv_class class_1;//句柄就是指针,它指向了一个对象,在没有赋值前默认值为null
上面的代码相当于是定义了sv_class类的句柄class_1,这个句柄指向sv_class的一个对象。
也可以创建句柄数组:
sv_class class_1[3];//创建了一个句柄数组,该数组包括3个元素,没有对象数组的说法
类中的属性和方法只有创建类的对象之后才能被引用,下面的语句创建了一个对象,并把该对象的句柄赋给了class_1
class_1 = new();//new函数会创建一个对象,并返回对象的句柄,这里将返回的句柄赋给class_1
也可以在创建句柄的同时创建对象,并将对象的句柄赋给创建的句柄
sv_class class_1 = new();
// Create a new class with a single member called
// count that stores integer values
class Packet;
int count;
endclass
module tb;
// Create a "handle" for the class Packet that can point to an
// object of the class type Packet
// Note: This "handle" now points to NULL
Packet pkt;//默认值为null
initial begin
if (pkt == null)
$display ("Packet handle 'pkt' is null");
// Call the new() function of this class
pkt = new();
if (pkt == null)
$display ("What's wrong, pkt is still null ?");
else
$display ("Packet handle 'pkt' is now pointing to an object, and not NULL");
// Display the class member using the "handle"
$display ("count = %0d", pkt.count);
end
endmodule
结果:
创建了对象后就可以引用类中的属性和方法
class_1.set(10); //calling set method to set value 10 to x
$display("Vlaue of x = %0d",class_1.get(););
下面看一个完整的过程:
class sv_class;
//class properties
int x;
//method-1
task set(int i);
x = i;
endtask
//method-2
function int get();
return x;
endfunction
endclass
module sv_class_ex;
sv_class class_1; //Creating Handle
initial begin
sv_class class_2 = new(); //Creating handle and Object
class_1 = new(); //Creating Object for the Handle
//Accessing Class methods
class_1.set(10);
class_2.set(20);
$display("\tclass_1 :: Value of x = %0d",class_1.get());
$display("\tclass_2 :: Value of x = %0d",class_2.get());
end
endmodule
运行结果如下:
class myPacket;//定义类
bit [2:0] header;
bit encode;
bit [2:0] mode;
bit [7:0] data;
bit stop;
function new (bit [2:0] header = 3'h'1, bit [2:0] mode = 5);//构造方法
this.header = header;
this.encode = 0;
this.mode = mode;
this.stop = 1;
endfunction
function display ();//成员方法
$display ("Header = 0x%0h, Encode = %0b, Mode = 0x%0h, Stop = %0b",
this.header, this.encode, this.mode, this.stop);
endfunction
endclass
//顶层模块实例化类
module tb_top;
myPacket pkt0 [3];//创建句柄数组
initial begin
for(int i = 0; i < $size (pkt0); i++) begin
pkt0[i] = new ();//调用构造方法实例化对象
pkt0[i].display ();
end
end
endmodule
结果:
类的构造方法new
当一个类实例化对象时,这个过程是通过自动调用构造方法来实现的,构造方法还可以初始化对象。构造方法可以显示定义,也可以不定义。不定义时,编译器会自动为类创建一个不带参数的构造方法,该构造方法不进行任何初始化,作用仅仅是创建对象。构造方法没有返回值,也没有返回值类型,但是可以有参数.
下面来看构造方法显示定义和隐式定义的情况:
// Define a class called "Packet" with a 32-bit variable to store address
// Initialize "addr" to 32'hfade_cafe in the new function, also called constructor
class Packet;
bit [31:0] addr;
function new ();//显示定义构造方法,并在方法中对addr进行初始化
addr = 32'hfade_cafe;
endfunction
endclass
module tb;
// Create a class handle called "pkt" and instantiate the class object
initial begin
// The class's constructor new() fn is called when the object is instantiated
Packet pkt = new;
// Display the class variable - Because constructor was called during
// instantiation, this variable is expected to have 32'hfade_cafe;
$display ("addr=0x%0h", pkt.addr);
end
endmodule
结果:
可以看到实例化对象后,addr有初始值.
// Define a simple class with a variable called "addr"
// Note that the new() function is not defined here
class Packet;//没有显示定义构造方法
bit [31:0] addr;
endclass
module tb;
// When the class object is instantiated, then the constructor is
// implicitly defined by the tool and called
initial begin
Packet pkt = new;
$display ("addr=0x%0h", pkt.addr);
end
endmodule
结果:
没有构造方法,所以addr为默认值0.
再来看子类继承父类的情况;
// Define a simple class and initialize the class member "data" in new() function
class baseClass;
bit [15:0] data;
function new ();
data = 16'hface;
endfunction
endclass
// Define a child class extended from the above class with more members
// The constructor new() function accepts a value as argument, and by default is 2
class subClass extends baseClass;
bit [3:0] id;
bit [2:0] mode = 3;
function new (int val = 2);
// The new() function in child class calls the new function in
// the base class using the "super" keyword
super.new ();//子类继承父类时,要在子类的构造方法中调用父类的构造方法,也就是创建了父类对象
// Assign the value obtained through the argument to the class member
id = val;
endfunction
endclass
module tb;
initial begin
// Create two handles for the child class
subClass sc1, sc2;
// Instantiate the child class and display member variable values
sc1 = new ();
$display ("data=0x%0h id=%0d mode=%0d", sc1.data, sc1.id, sc1.mode);
// Pass a value as argument to the new function in this case and print
sc2 = new (4);
$display ("data=0x%0h id=%0d mode=%0d", sc2.data, sc2.id, sc2.mode);
end
endmodule
在子类构造方法中调用父类的构造方法采用关键字super.new, 子类中调用父类的成员都需要用到关键字super,结果如下:
构造方法的修饰符只能是local或protected,而不能是virtual和static.看下面例子;
class ABC;
string fruit;
// Note that the constructor is defined as "virtual" which is not allowed
// in SystemVerilog. Class constructor cannot be "static" either
virtual function new ();
fruit = "Apple";
endfunction
endclass
// Instantiate the class object and print its contents
module tb;
initial begin
ABC abc = new();
$display ("fruit = %s", abc.fruit);
end
endmodule
this关键字
this关键字用于引用类的属性。看下面例子:
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
bit write;
string pkt_type;
//constructor
function new(bit [31:0] addr,data,bit write,string pkt_type);//new是构造函数,用于对象的初始化
addr = addr;//方法中局部变量与类的成员变量同名
data = data;
write = write;
pkt_type = pkt_type;
endfunction
//method to display class prperties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0h",addr);
$display("\t data = %0h",data);
$display("\t write = %0h",write);
$display("\t pkt_type = %0s",pkt_type);
$display("---------------------------------------------------------");
endfunction
endclass
module sv_constructor;
packet pkt;
initial begin
pkt = new(32'h10,32'hFF,1,"GOOD_PKT");
pkt.display();
end
endmodule
运行结果如下:
可以看出,参数输出了自己的默认值,这是由于方法的局部变量的变量名与类的成员变量名相同导致的,解决这一问题的办法就是使用this关键字。
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
bit write;
string pkt_type;
//constructor
function new(bit [31:0] addr,data,bit write,string pkt_type);
this.addr = addr;//关键字this其实就是类的指针,它指向了类的对象,所以可以通过this.成员变量名来访问类的成员变量
this.data = data;//this.data代表类的成员变量,而等号右边的data为方法的局部变量
this.write = write;
this.pkt_type = pkt_type;
endfunction
//method to display class prperties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0h",addr);
$display("\t data = %0h",data);
$display("\t write = %0h",write);
$display("\t pkt_type = %0s",pkt_type);
$display("---------------------------------------------------------");
endfunction
endclass
module sv_constructor;
packet pkt;
initial begin
pkt = new(32'h10,32'hFF,1,"GOOD_PKT");
pkt.display();
end
endmodule
输出结果如下:
类的静态属性和静态方法
可以在类的属性和方法名前加上关键字static,这表示静态属性和静态方法。静态属性和静态方法归类所有,表明在内存中单独开辟了一块区域来存储这些静态成员。静态成员可以通过"类名::静态成员名"来访问,而不需要创建对象。也就是说对于类的每一个对象,类的静态成员都是使用同一块内存地址!!即静态成员是每个类的对象共有的!!
请注意,对于静态方法,它只能访问类的静态成员而不能访问非静态成员,否则会报错。
静态变量一般在定义时初始化,要保证在创建第一个对象之前静态变量就已经完成了初始化。
首先来看非静态的例子:
class Packet;
bit [15:0] addr;
bit [7:0] data;
function new (bit [15:0] ad, bit [7:0] d);
addr = ad;
data = d;
$display ("addr=0x%0h data=0x%0h", 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
对于非静态成员,类的每一个对象都会为它们单独开辟一块存储区域,每一个对象的同名成员都互不干扰,所以结果如下:
看下面静态成员的例子:
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
bit write;
string pkt_type;
bit pkt_id;
//static property to keep track of number of pkt's created
static int no_of_pkts_created;//定义静态变量
//constructor
function new(bit [31:0] addr,data,bit write,string pkt_type);
this.addr = addr;
this.data = data;
this.write = write;
this.pkt_type = pkt_type;
//increment pkt count on creating an object
no_of_pkts_created++;
pkt_id = no_of_pkts_created;
endfunction
//method to display class properties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0h",addr);
$display("\t data = %0h",data);
$display("\t write = %0h",write);
$display("\t pkt_type = %0s",pkt_type);
$display("---------------------------------------------------------");
endfunction
//static method to display next_pkt id 静态方法
static function void next_pkt_id;
$display("---------------------------------------------------------");
$display("\t no of pkt's created = %0d",no_of_pkts_created);
$display("\t next pkt id = %0d",no_of_pkts_created+1);
$display("---------------------------------------------------------");
endfunction
endclass
module static_properties_method;
packet pkt[3];
packet pkt_2;
initial begin
foreach(pkt[i]) begin
pkt[i] = new(32'h10*i,32'hF*i,1,"GOOD_PKT");
pkt[i].display();
end
pkt[1].next_pkt_id;
//static class properties and methods are accessed without
//creating an object for pkt_2 handle.
pkt_2.next_pkt_id;//调用静态方法
end
endmodule
运行结果:
对比静态变量和非静态变量:
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
结果:
静态变量static_ctr是3个对象共有的,所有在p3时输出为3;而ctr、addr、data每个对象都有,所以输出每个对象各自的值.
普通成员只能通过例化对象来进行外部访问,而静态成员可以通过类名加上作用域操作符"::"来访问,格式如下:
类名::静态成员名;
举例:
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(); // 普通成员只能通过例化对象访问
end
endmodule
结果:
Class Assignment 类的赋值
看下面代码:
packet pkt_1;//定义句柄
pkt_1 = new();//实例化对象
packet pkt_2;
pkt_2 = pkt_1;
由于将pkt_1赋值给了pkt_2,两个变量都是句柄,所以最后两个句柄都指向同一个对象;所以通过pkt_1改变对象的任何属性,都会立即反映到pkt_2上。看下图的解释:
看下面例子:
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
bit write;
string pkt_type;
//constructor
function new();
addr = 32'h10;
data = 32'hFF;
write = 1;
pkt_type = "GOOD_PKT";
endfunction
//method to display class properties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0d",addr);
$display("\t data = %0h",data);
$display("\t write = %0d",write);
$display("\t pkt_type = %0s",pkt_type);
$display("---------------------------------------------------------");
endfunction
endclass
module class_assignment;
packet pkt_1;
packet pkt_2;
initial begin
pkt_1 = new();
$display("\t**** calling pkt_1 display ****");
pkt_1.display();
//assigning pkt_1 to pkt_2
pkt_2 = pkt_1;//将句柄1赋值给pkt_2
$display("\t**** calling pkt_2 display ****");
pkt_2.display();
//changing values with pkt_2 handle
pkt_2.addr = 32'hAB;
pkt_2.pkt_type = "BAD_PKT";
//changes made with pkt_2 handle will reflect on pkt_1 通过句柄2改变的属性会反映到句柄1上,因为两个句柄指向同一个对象
$display("\t**** calling pkt_1 display ****");
pkt_1.display();
end
endmodule
运行结果如下:
Shallow Copy 简单复制对象
看下面代码:
packet pkt_1;
pkt_1 = new();
packet pkt_2;
pkt_2 = new pkt_1;//通过关键字new来复制句柄
请注意,对象并不能复制,只能复制它们的句柄。上面通过关键字new将pkt_1复制给pkt_2,从而创建了一个新的对象,只是这个对象的内容与pkt_1相同,但是两个句柄的值是不同的,这需要注意。
具体的操作情况看下面的图 :
可以看出,复制之后改变pkt_2的属性并不会改变pkt_1的属性,即两者之间没有关联。
可以看出采用这种复制方式,只会对最高层次的类的对象进行复制;如果这个类中还包括指向其他类的句柄,那么下层的对象并不会复制,只会复制下层对象的句柄。所以此时,复制之后的两个对象共享一个下层对象,如上图中的pkt_1.ad_r和pkt_2.ad_r位同一个对象。
看下面具体例子:
//-- class ---
class address_range;
bit [31:0] start_address;
bit [31:0] end_address ;
function new();
start_address = 10;
end_address = 50;
endfunction
endclass
//-- class ---
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
address_range ar; //class handle 类的句柄
//constructor
function new();
addr = 32'h10;
data = 32'hFF;
ar = new(); //creating object
endfunction
//method to display class prperties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0h",addr);
$display("\t data = %0h",data);
$display("\t start_address = %0d",ar.start_address);
$display("\t end_address = %0d",ar.end_address);
$display("---------------------------------------------------------");
endfunction
endclass
// -- module ---
module class_assignment;
packet pkt_1;
packet pkt_2;
initial begin
pkt_1 = new(); //creating pkt_1 object
$display("\t**** calling pkt_1 display ****");
pkt_1.display();
pkt_2 = new pkt_1; //creating pkt_2 object and copying pkt_1 to pkt_2 复制对象
$display("\t**** calling pkt_2 display ****");
pkt_2.display();
//changing values with pkt_2 handle
pkt_2.addr = 32'h68;
pkt_2.ar.start_address = 60;//修改下层对象的属性
pkt_2.ar.end_address = 80;
$display("\t**** calling pkt_1 display after changing pkt_2 properties ****");
//changes made to pkt_2.ar properties reflected on pkt_1.ar, so only handle of the object get copied, this is called shallow copy
pkt_1.display();
$display("\t**** calling pkt_2 display after changing pkt_2 properties ****");
pkt_2.display(); //
end
endmodule
运行结果如下:
可以看出在pkt_2修改了对象ar中的属性start_address后,pkt_1和pkt_2中的ar的属性都改变了,也就是说这种简单复制并没有将下层对象复制,只是将最高层次的对象进行了复制,此时下层对象为两者共享。
请注意这种简单复制与上面类的赋值的不同,看下图:
类的赋值只是重新定义了一个句柄,该句柄与原来的句柄指向同一个对象;而上面的简单复制是重新创建了一个对象,只是这个对象的内容和原来对象的内容相同。
deep copy 深层复制
对于简单复制,对象并没有复制,复制的只是句柄。为了进行深层复制,需要自定义复制函数,在自定义函数中,将创建新对象,并将所有类属性复制到新句柄,并返回新句柄。
看下面这个例子,在这个例子中copy()方法被添加到每一层的类中,具体如下:
//-- class ---
class address_range;//定义下层类
bit [31:0] start_address;
bit [31:0] end_address ;
function new();//构造方法初始化对象
start_address = 10;
end_address = 50;
endfunction
//copy method
function address_range copy;//下层类中的copy函数
copy = new();
copy.start_address = this.start_address;
copy.end_address = this.end_address;
return copy;//返回值的类型为该类
endfunction
endclass
//-- class ---
class packet;
//class properties
bit [31:0] addr;
bit [31:0] data;
address_range ar; //class handle
//constructor
function new();
addr = 32'h10;
data = 32'hFF;
ar = new(); //creating object 例化下层类的对象,一般在上层类的构造方法中实现
endfunction
//method to display class prperties
function void display();
$display("---------------------------------------------------------");
$display("\t addr = %0h",addr);
$display("\t data = %0h",data);
$display("\t start_address = %0d",ar.start_address);
$display("\t end_address = %0d",ar.end_address);
$display("---------------------------------------------------------");
endfunction
//copy method
function packet copy();//上层类中的copy函数
copy = new();
copy.addr = this.addr;
copy.data = this.data;
copy.ar = ar.copy;//calling copy function of tr
return copy;
endfunction
endclass
// -- module ---
module class_assignment;
packet pkt_1;
packet pkt_2;
initial begin
pkt_1 = new(); //creating pkt_1 object
$display("\t**** calling pkt_1 display ****");
pkt_1.display();
pkt_2 = new(); //creating pkt_2 object
$display("\t**** calling pkt_2 display ****");
pkt_2.display();
pkt_2 = pkt_1.copy(); //calling copy method 调用上层类的copy函数
//changing values with pkt_2 handle
pkt_2.addr = 32'h68;//修改赋值后的属性
pkt_2.ar.start_address = 60;
pkt_2.ar.end_address = 80;
$display("\t**** calling pkt_1 display after changing pkt_2 properties ****");
pkt_1.display();
$display("\t**** calling pkt_2 display after changing pkt_2 properties ****");
pkt_2.display();
end
endmodule
运行结果:
可以看出,包括下层类在内都已经实现了复制,复制之后两个对象在不同的地址中,互不影响。
Parameterised Classes 常数类
看下面例子:
class packet #(parameter int ADDR_WIDTH = 32,DATA_WIDTH = 32);
bit [ADDR_WIDTH-1:0] address;
bit [DATA_WIDTH-1:0] data ;
function new();
address = 10;
data = 20;
endfunction
endclass
常数类类似于verilog中的常数module,常数可以用于描述类中的属性,例如上面的位宽。常数值在类例化对象时可以进行修改并覆盖。
packet pkt; //创建一个对象并且该对象的成员变量固定的位宽
packet #(32,64) pkt; //用修改后的参数值改变固定的位宽
Classes Inheritance 类的继承
新的类可以在已存在的类的基础上被创建,也就是继承。被继承的类为父类,新产生的类为子类,子类可以继承并修改父类的所有属性和方法,也可以创建自己的属性和方法。看下面一个简单的例子:
class parent_class;
bit [31:0] addr;
endclass
class child_class extends parent_class;
bit [31:0] data;
endclass
module inheritence;
initial begin
child_class c = new();
c.addr = 10;
c.data = 20;
$display("Value of addr = %0d data = %0d",c.addr,c.data);
end
endmodule
运行结果如下:
Overriding class members 覆盖类的成员
父类的成员可以在子类中修改并覆盖(这是重写不是重载,注意两者的区别),看下面的例子:
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();//覆盖父类中的display方法
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
child_class c=new();
c.addr = 10;
c.data = 20;
c.display();
end
endmodule
super关键字
子类可以通过关键字super引用父类中的成员,当父类的成员被子类成员覆盖时,必须用super来访问父类成员。看下例:
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
super.display();//获取父类的display方法
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
child_class c=new();
c.addr = 10;
c.data = 20;
c.display();
end
endmodule
运行结果:
class Packet;
int addr;
function display ();
$display ("[Base] addr=0x%0h", addr);
endfunction
endclass
class extPacket extends Packet;
function display();
super.display(); // Call base class display method
$display ("[Child] addr=0x%0h", addr);
endfunction
function new ();
super.new ();
endfunction
endclass
module tb;
Packet p;
extPacket ep;
initial begin
ep = new();
p = new();
ep.display();
end
endmodule
结果:
cast对象转型
动态转换关键字$cast可以强制将父类句柄(或引用)转换为子类的句柄(或引用)。如果强制转换无效,这是因为所指向的对象的实际类型不是所需子类的类型,则转换失败。
我们来看它的用法:
一般来说将一个子类的句柄赋给一个父类的句柄(向上转型),这通常是合法的;但是将一个父类句柄赋值给一个子类的句柄(向下转型),这通常是非法的。这时我们就可以利用关键字$cast将一个父类句柄强制转换为子类句柄。看下面的例子:
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
super.display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
parent_class p=new();
child_class c=new();
c.addr = 10;
c.data = 20;
p = c; //将子类句柄赋给父类句柄,这不会有问题
c.display();
end
endmodule
module tb;
Packet bc; // bc stands for BaseClass
ExtPacket sc; // sc stands for SubClass
initial begin
sc = new (32'hfeed_feed, 32'h1234_5678);
bc = sc;//将子类句柄赋给父类句柄,此时bc指向一个子类对象,但是bc本身还是一个父类句柄
bc.display ();//子类和父类中都有display方法
sc.display ();
end
endmodule
结果:
可以看出虽然bc在被赋给子类句柄后,但是bc.display()调用的方法还是父类的display()方法,这是因为普通方法(不包括虚方法)调用取决于句柄类型而不是句柄所指向的对象类型.
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
super.display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
parent_class p=new();
child_class c=new();
c.addr = 10;
c.data = 20;
c = p; //将父类句柄赋给子类句柄,这会报错
c.display();
end
endmodule
可以看出这样直接将父类句柄赋给子类,编译器会报错;
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
super.display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
parent_class p;//注意这里并没有实例化对象,只是创建了句柄,注意这里和声明例子的区别
child_class c=new();
child_class c1;
c.addr = 10;
c.data = 20;
p = c; //p 是父类的句柄,但是指向了子类的对象
c1 = p; //将父类句柄赋给子类
c1.display();
end
endmodule
将父类指向子类对象的句柄赋给子类句柄还是会报错,结果如下:
class parent_class;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
class child_class extends parent_class;
bit [31:0] data;
function display();
super.display();
$display("Data = %0d",data);
endfunction
endclass
module inheritence;
initial begin
parent_class p;
child_class c=new();
child_class c1;
c.addr = 10;
c.data = 20;
//$cast(c,p) 在没有执行p=c,也就是将p指向一个子类对象之前,这样强制转换是错误的
p = c; //p 指向子类对象
$cast(c1,p); //强制将p转换为子类句柄c1,此时c1指向p指向的对象
//$cast(a,b)方法的条件是句柄a、b指向的对象类型要相同
c1.display();
end
endmodule
没有报错
Data Hiding and Encapsulation 数据隐藏和封装
将类中的数据隐藏,并且只可以通过方法来访问的这种技术叫做封装。在默认的情况下,类中的所有成员都可以通过对象的句柄来访问,但是有些情况下这可能会破坏某些类成员的值。为了限制外部对象对类内部成员的访问,可以在成员名前加上前缀:
- local
- protected
(1)local
可以通过声明成员为本地成员来避免对类成员的外部访问(父类的对象也不能访问,这种访问也是属于外部访问),任何违规都可能导致编译错误,local成员对于子类来说也不可见,所以不能通过子类来访问local成员。语法如下:
local integer x;
看下例:
class parent_class;
local bit [31:0] tmp_addr;//声明为本地成员
function new(bit [31:0] r_addr);
tmp_addr = r_addr + 10;
endfunction
function display();
$display("tmp_addr = %0d",tmp_addr);
endfunction
endclass
// module
module encapsulation;
initial begin
parent_class p_c = new(5);
p_c.tmp_addr = 20; //修改本地成员是违法的
p_c.display();
end
endmodule
编译错误,类的本地成员禁止外部访问;
local成员只能在类的内部访问,看下例:
class parent_class;
local bit [31:0] tmp_addr;
function new(bit [31:0] r_addr);
tmp_addr = r_addr + 10;//内部访问本地成员
endfunction
function display();
$display("tmp_addr = %0d",tmp_addr);
endfunction
endclass
// module
module encapsulation;
initial begin
parent_class p_c = new(5);
p_c.display();
end
endmodule
此时没有报错
(2)protected
有时候需要定义一些只能被子类访问的成员(父类的对象也不能访问,这种访问也是属于外部访问),这时需要在这些成员前面加上protected关键字,语法如下:
protected integer x;
看下面例子:
class parent_class;
protected bit [31:0] tmp_addr;//受保护的属性
function new(bit [31:0] r_addr);
tmp_addr = r_addr + 10;
endfunction
function display();
$display("tmp_addr = %0d",tmp_addr);
endfunction
endclass
class child_class extends parent_class;
function new(bit [31:0] r_addr);
super.new(r_addr);//调用父类的构造方法,一般这需要在子类的第一句声明
endfunction
function void incr_addr();
tmp_addr++;//子类访问受保护对象
endfunction
endclass
// module
module encapsulation;
initial begin
parent_class p_c = new(5);
child_class c_c = new(10);
// 父类的对象访问受保护的属性,这是非法的
p_c.tmp_addr = 10;
p_c.display();
c_c.incr_addr(); //Accessing protected variable in extended class
c_c.display();
end
endmodule
在父类对象中访问受保护的属性是违法的,所以报错。
class parent_class;
protected bit [31:0] tmp_addr;
function new(bit [31:0] r_addr);
tmp_addr = r_addr + 10;
endfunction
function display();
$display("tmp_addr = %0d",tmp_addr);
endfunction
endclass
class child_class extends parent_class;
function new(bit [31:0] r_addr);
super.new(r_addr);
endfunction
function void incr_addr();
tmp_addr++;//通过子类来访问受保护的属性
endfunction
endclass
// module
module encapsulation;
initial begin
child_class c_c = new(10);
c_c.incr_addr(); //Accessing protected variable in extended class
c_c.display();
end
endmodule
运行结果:
Abstract Classes and Virtual Methods 抽象类和虚方法
在类名前加上关键字virtual的类称为抽象类,抽象类不能实例化,只能被继承。语法如下:
virtual class packet;
抽象类中可以有虚方法,虚方法是一种基本的多态构造,它覆盖所有父类中的方法。
//abstract class
virtual class packet;
bit [31:0] addr;
function display();
$display("Addr = %0d",addr);
endfunction
endclass
module virtual_class;
initial begin
packet p;
p = new();//实例化抽象类会报错
p.display();
end
endmodule
virtual method
在上面讲继承的时候我们看到,即使将一个子类句柄赋给一个父类句柄后,当调用子类和父类中同名的方法时,赋值后的父类句柄调用的方法还是父类的方法,这是因为普通方法的调用取决于调用该方法的句柄类型而不是句柄指向对象的类型。但是假如在方法名前加virtual关键字修饰变成虚方法后呢?情况会如何?
第一种普通方法情况:
class Packet;
int addr;
function new (int addr);
this.addr = addr;
endfunction
function display ();
$display ("[Base] addr=0x%0h", addr);
endfunction
endclass
class ExtPacket extends Packet;
int data;
function new (int addr, data);
super.new (addr);
this.data = data;
endfunction
function display ();
$display ("[Child] addr=0x%0h data=0x%0h", addr, data);
endfunction
endclass
module tb;
Packet bc;
ExtPacket sc;
initial begin
sc = new (32'hfeed_feed, 32'h1234_5678);
bc = sc; //将子类句柄赋给父类句柄
bc.display ();
end
endmodule
可以看出此时bc调用的display()方法为父类中的方法;下面将父类的display()方法加上virtual:
class Packet;
int addr;
function new (int addr);
this.addr = addr;
endfunction
virtual function display ();
$display ("[Base] addr=0x%0h", addr);
endfunction
endclass
结果:
可以看出此时bc调用的display()方法为父类中的方法;需要值得注意的是,如果想在子类中覆盖父类的方法,最好在父类的方法前加上关键字virtual,也就是将父类方法变为虚方法(子类的方法加不加virtual都行,但是假如子类的方法被下一层次的子类覆盖,此时子类方法也要变成虚方法).也就是说对于虚方法的调用取决于句柄指向的对象类型而非句柄类型!
pure virtual method 纯虚方法
抽象类中的虚方法可以用关键字pure定义为纯虚方法,纯虚方法没有方法体,只能在继承抽象类的子类中实现.看下例:
virtual class BaseClass;//定义抽象类
int data;
pure virtual function int getData();//抽象类中的纯虚方法
endclass
class ChildClass extends BaseClass;//继承抽象类
virtual function int getData();//重写抽象类的纯虚方法。重写后的方法必须与原方法有相同的返回值
data = 32'hcafe_cafe;
return data;
endfunction
endclass
module tb;
ChildClass child;
initial begin
child = new();
$display ("data = 0x%0h", child.getData());
end
endmodule
结果:
Scope Resolution Operator :: 作用域操作符
类作用域操作符:: 用于唯一地标识特定类的成员 ,允许从类的外部访问·,以及在子类中访问public或protected成员。
//class
class packet;
bit [31:0] addr;
static bit [31:0] id;
function display(bit [31:0] a,b);
$display("Vlaues are %0d %0d",a,b);
endfunction
endclass
module sro_class;
int id=10;
initial begin
packet p;
p = new();
packet::id = 20;//访问静态成员
p.display(packet::id,id);
end
endmodule
Extern Methods 外部方法
方法的方法体可以在类的外部定义;此时需要在类内部定义完整的方法名和参数列表以及加上关键字extern;在类的外部需要在方法名前加上类名和类作用域操作符。具体看下例:
//class with extern function
class packet;
bit [31:0] addr;
bit [31:0] data;
//function declaration - extern indicates out-of-body declaration
extern virtual function void display();//声明外部方法
endclass
//function implementation outside class body
function void packet::display();//外部方法
$display("Addr = %0d Data = %0d",addr,data);
endfunction
module extern_method;
initial begin
packet p;
p = new();
p.addr = 10;
p.data = 20;
p.display();
end
endmodule
typedef class 定义类
有些情况下某个类需要在它被定义之前声明,此时因为编译器还不认识这个新的类就会报错,这种情况下就需要用到关键字typedef来声明这个类名。看下例:
typedef class c2;//声明类c2
//class-1
class c1;
c2 c; //using class c2 handle before declaring it.
endclass
//class-2定义类c2
class c2;
c1 c;
endclass
module typedef_class;
initial begin
c1 class1;
c2 class2;
end
endmodule