class
类包括数据以及操作数据的任务和函数,在类中任务和函数统称为方法。类可以通过对象句柄来动态创建、删除、分配和访问对象。
类的定义方式
下面的例子中,类包括变量x和两个方法 。
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
类的实例化和对象的创建
类可以认为是用户定义的一种数据类型,因此可以像定义其他数据类型那样来定义类:
sv_class class_1;
上面的代码相当于是定义了sv_class类的句柄class_1,这个句柄指向sv_class的一个对象。
类中的属性和方法只有创建类的对象之后才能被引用,下面的语句创建了一个对象,并把该对象的句柄赋给了class_1
class_1 = new();
也可以在创建句柄的同时创建对象,并将对象的句柄赋给创建的句柄
sv_class class_1 = new();
创建了对象后就可以引用类中的属性和方法
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
运行结果如下:
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;
//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 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
运行结果:
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
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;
p = c; //p 指向子类对象
$cast(c1,p); //强制将p转换为子类句柄c1,此时c1指向p指向的对象
c1.display();
end
endmodule
没有报错
Data Hiding and Encapsulation 数据隐藏和封装
将类中的数据隐藏,并且只可以通过方法来访问的这种技术叫做封装。在默认的情况下,类中的所有成员都可以通过对象的句柄来访问,但是有些情况下这可能会破坏某些类成员的值。为了限制外部对象对类内部成员的访问,可以在成员名前加上前缀:
- local
- protected
(1)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
编译错误,类的本地成员禁止外部访问;
本地成员只能在类的内部访问,看下例:
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
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