一、类
1、具有(相同或者类似)属性和行为的一系列对象的集合。关键字:class、endclass
2、类的特征:
- 封装:将对象的属性和行为封装起来,对用户隐藏类实现的细节。
- 继承:一个类获取另外一个类的属性和行为的方式。
- 多态:同一个行为具有多个不同表现形式或形态的能力,当同一操作作用于不同对象,能有不同的解释从而产生不同的结果。
3、验证中,为什么要有类的存在?
①激励生成器(stimulus generator):生成激励内容
②驱动器(driver):将激励以时序形式发送至DUT
③监测器(monitor):监测信号并记录数据
④比较器(checker):比较数据
同一验证环境中,验证环境的不同组件其功能和所需要处理的数据内容是不相同的,不同环境的同一类型的组件其所具备的功能和数据内容是相似的(这提供了继承的可能性,使新的项目组件可以继承旧的项目组件)。
二、对象
对象:就是对类的实例化,SV采用oop编程(Object Oriented Programming)
类是成员变量和成员方法的载体,类的成员访问类型:
- public:如果没有指明访问类型,默认是public,子类和外部均可以访问成员
- protected:只有该类或者子类可以访问成员,外部不可访问
- local:只有该类可以访问成员,子类和外部均无法访问
举例说明类和对象里的一些问题:
class Transaction;
bit [31:0] addr,crc,data;
function void display();
$write("TR:addr=%0d,crc=%0d,data=%0d",addr,crc,data);//write打印没有换行
$display();//打印换行
endfunction
endclass
class PCI_Tran;
bit [31:0] addr,data;
function new(logic [31:0] addr1=3,data1=5);//定义的带参数的且有默认值的new函数
addr = addr1;
data = data1;
endfunction
function void display();
$display("PCI:addr=%0d,data=%0d",addr,data);
endfunction
endclass
Transaction t;//声明句柄, 此时被初始化为null
PCI_Tran pc;
module test15();
initial begin
t = new();//分配存储空间
t.display();
pc = new(,8);//注意逗号对应前后的形参
pc.display();
end
endmodule
打印结果:
TR:addr=0,crc=0,data=0
PCI:addr=3,data=8
说明:(重点理解)
句柄可以理解为C语言中的指针
1、在声明句柄t,pc的时候,该句柄被初始化为特殊值null。接下来,你调用new()函数来创建Transaction,PCI_Tran对象,且new函数为创建的对象分配空间,将变量初始化为默认值(二值变量为0,四值变量为X),并返回保存对象的地址给对应的句柄。对于每一个类来讲,System Verilog创建一个默认的new函数来分配并初始化对象。
2、new函数:构造函数,SV中类有一个自带的new函数,若没有自己定义,系统将自动调用自带的new函数,new函数主要就是起初始化类的成员属性和分配地址空间的作用,自己定义的new函数可以是不带参数的,也可以是带参数的,如上例,New函数没有返回值,因为构造函数总是返回一个指向该类的对象的句柄,其类型就是类本身。
二、this指针
在面向对象语言中,this表示对当前对象的一个引用,可以理解为指向对象本身的一个指针,并且常作为类中方法的隐含形参,通过该形参可以访问对象内部的属性和方法,因此,一般方法对于编译器来说,原型结构如下:
[return_type] func_name(class_type const this,other_args)
- 其中 return_type为方法返回类型
- class_type当前方法所属的类
- other_args当前方法其他形参
说明:
- 当一个对象创建时,不同对象变量大小有很大的可能是不同的,所以我们定义的时候编译器就为其开辟了空间,但是他们调用各自函数时调用的都是同一个函数,那么也就没有必要再为同一个类的每个对象的函数单独开辟空间,所以此时的函数是保存在公共代码区的,即所有方法放在同一空间,类内的成员变量和成员方法分开存储,当对象调用方法时候是把对象的地址给this指针,通过this指针去调用方法的,因为类中的方法有一个隐含的this形参,this指针在调用方法时就存在,当方法调用结束时其生命周期也就结束了,this指针就不再指向对应方法的区域了,在方法中不必显式的使用this 指针来访问该方法所属对象的属性和其他方法,此时对于这些没有采用this前缀进行访问的属性和方法都被假定为通过this实现了引用。
- 非静态成员变量,属于类对象,占用类对象空间
- 非静态成员函数 不属于类对象,不占用类对象空间
- 静态成员函数,静态成员变量都不属于类对象,都是单独的存储空间
三、静态变量
1、静态变量就是在函数调用前后的值不会被销毁的变量,在内存中单独存储
class Transaction;
static int count=0;
int id;
function new();
id=count++;//id首先使用count=0的值,再++
endfunction
endclass
program example;
Transaction t1,t2;
initial begin
t1=new;//第一个实例,id=0,count=1
t2=new;//第二个实例,id=1,count=2
end
endprogram
域作用符“::”
- 全局作用域符(::name):局部变量与全局变量命名相同都要调用冲突时,表示变量前加作用域为全局命名空间。
- 类作用域符(class::name):用于表示指定name变量是属于哪个类的,如:Transaction::count
- 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间
- 类外声明函数,复制该方法的第一行,包括方法名和参数,然后在开始处添加关键词extern。然后将整个方法移至类定义的后面,并在方法名前加上类名和两个冒号(::作用域操作符)。
class Transaction;
bit [ 31:0]addr ,crc, data [8];
extern function void display();
endclass
function void Transaction::display () ;
$display ("@% 0t : Transaction addr=%h, crc=%h",
$time , addr , crc);
$write("\tdata [0-7]=");
foreach (data[i])$write (data [ i] );
$dispiay();
endfunction
四、句柄传递、浅复制、深复制
1、句柄传递
Transaction t1,t2; // 声明句柄
t1 = new(); // 创建对象并将其指针赋予t1
t2 = new(); // 创建对象并将其指针赋予t2
t1 = t2;//句柄传递
// 将t2的值赋予t1,t1和t2指向同一对象,同一片地址区域
// t1之前指向的对象被释放
t2 = null;
// 将t2赋值为‘空’即不指向任何对象,
// 此时指针‘悬空’,悬空的指针很‘危险’
2、浅复制
1、方法:tr2 = new tr1;浅复制复制对象的所有属性,在new复制一个对象时不会调用自己写的new函数,所以也不会对更深层次对象复制,即复制不包括属性成员中句柄指向的对象,示例:
class packet;
bit [7:0] payload;
endclass
class transaction;
bit [7:0] addr;
bit [7:0] data;
packet pkt;
function new();
pkt = new();
endfunction
function void assig(bit [7:0] addr0,bit [7:0] data0);
this.addr = addr0;
this.data = data0;
endfunction
endclass
program test;
transaction tr1;
transaction tr2;
initial begin
tr1 = new();
tr1.assig(8'd0,8'd1);
$display("tr1.addr=%d,tr1.data=%d",tr1.addr,tr1.data);
tr2 = new tr1;
tr2.assig(8'd2,8'd3);
$display("tr2.addr=%d,tr2.data=%d",tr2.addr,tr2.data);
$display("tr1.addr=%d,tr1.data=%d",tr1.addr,tr1.data);
$display();
tr1.pkt.payload = 8'd12;
$display("tr1.pkt.payload=%d",tr1.pkt.payload);
$display("tr2.pkt.payload=%d",tr2.pkt.payload);
tr2.pkt.payload = 8'd25;
$display("tr1.pkt.payload=%d",tr1.pkt.payload);
$display("tr2.pkt.payload=%d",tr2.pkt.payload);
end
endprogram
打印结果:
tr1.addr= 0,tr1.data= 1
tr2.addr= 2,tr2.data= 3
tr1.addr= 0,tr1.data= 1
tr1.pkt.payload= 12
tr2.pkt.payload= 12
tr1.pkt.payload= 25
tr2.pkt.payload= 25
示意图:指明了句柄复制前后的指向
2、深复制
1、深复制就是创建一个新的和原始句柄指向的内容相同的字段,是两个一样大的数据段,复制包括对象的所有属性和更深层次的对象的复制,但需要自己创建copy函数实现,示例
class packet;
bit [7:0] payload;
function packet copy;
copy = new();
copy.payload = this.payload;
return copy;
endfunction
endclass
class transaction;
bit [7:0] addr;
bit [7:0] data;
packet pkt;
function new();
pkt = new();
endfunction
function transaction copy;
copy = new();
copy.addr = this.addr;
copy.data = this.data;
copy.pkt = pkt.copy;
return copy;
endfunction
endclass
program test;
transaction tr1;
transaction tr2;
initial begin
tr1 = new();
tr2 =tr1.copy;
end
endprogram
说明:
- function transaction copy类似于function返回数组的操作,即函数名可以当成变量用
示意图:指明了句柄复制前后的指向