SystemVerilog中面向对象编程
类
简介
相关术语:类(class):包含变量和子程序的基本构建块。
对象(object):类的一个实例。
句柄(handle):指向对象的指针。
属性(property):存储数据的变量。
方法(method):任务或者函数中操作变量的程序性代码。
原型(prototype):程序的头,包括函数名、返回值类型和参数列表。
关键字:class,endclass
在搭建验证平台的过程中,具体需要四部分来完成:
激励生成器(stimulus generator):生成激励内容
驱动器(driver):将激励以时序形式发送DUT
监测器(monitor):监测信号并且记录数据
比较器(checker):比较数据
验证环境的不同组件其功能和所需要处理的数据内容是不相同的,不同环境的同一类型组件其所具备的功能和数据内容是相似的;
类是将相同的个体抽象出来的描述方式,对象是实体。
具有相同属性和功能的对象属于同一类,不同类之间可能有联系(继承)或者没有联系。
类的定义核心是属性声明和方法定义,故类是数据和方法的自洽体,它既可以保存数据,也可以处理数据。
类与结构体在数据保存方面的重要区别:结构体只是单纯的数据集合,而类可以对数据做出符合要求的处理
verilog中的module可以实例化一个对象,但是该对象在编译时就已经例化,而类中的对象只有在new()对象时才被例化。
-
和所有的编程语言中的面向对象编程的准则一样,systemverilog中的类的属性和方法默认权限是public,子类和外部均可访问成员。
-
如果指明了该访问类型是protected,则只有该类或者子类可以访问成员,外部无法访问。
-
如果访问类型是local,则只有该类可以访问成员,子类和外部均无法访问。
在类的编写中,对类中属性的权限类型应该是local的,通过在类内编写set和get函数获取属性的权限。
对方法的编写,最好的处理是类内声明,类外定义(后面写)。
构造函数new()
和别的构造函数不同,systemverilog中构造函数用关键字new()去创建一个对象。
- new()可以初始化类中属性的默认值,也可以传递想要的值。
- new()没有返回值,因为构造函数总是返回一个指向类对象的句柄,类型是类本身。
class Transcation;
logic [31:0] addr,crc,data[8];//crc未被初始化,则默认值是x
function new;//不带参数的构造函数
addr=3;
foreach(data[i])
data[i]=5;
endfunction
endclass
class Transaction;
logic [31:0] addr,crc,data[8];
function new(logic[31:0]a=3,d=5); //带有参数值的构造函数
addr=a;
foreach(data[i])
data[i]=d;//给data[8]初始化
endfunction
endclass
initial begin
Transaction tr;//创建一个句柄
tr=new(10);//让句柄指向新创建的对象
end
tr = new(.a(10));//a=10,让句柄指向新的对象。使用端口传值,将a端口赋成10,d默认是5
句柄的传递
句柄可以用来创建多个对象,也可以指向不同的对象。
Transaction t1,t2;//创建两个句柄t1和t2
t1=new();//创建对象并将其指针赋给t1
t2=new();//创建对象并将其指针赋给t2
t1=t2;//将t2的值赋给t1,此时t1和t2指向同一个对象,t1所指向的对象被释放
t2-null;//将t2赋值为空,不知想任何对象
此时指针悬空
对象的复制
在new了一个对象并将句柄指向该对象时,相当于指向了一块class类型的内存空间。
复制分为浅复制和深复制。
浅复制仅仅复制对象的所有属性,不会调用new函数,也就是当类中的属性是另一个类时,new对象开辟内存空间会开辟两块内存空间,而浅复制仅仅复制对象的所有属性,不会重新开辟一片空间。相当于C++中的浅拷贝和深拷贝,浅复制的语法:tr2=new tr1
如上代码中,定义了两个类,transaction类中具有Packet类的对象。在验证代码中,将tr1赋给tr2后。试图通过tr2改变tr1中pkt的内存地址,结果显示,除了属性改变成功,pkt中的内存值并未改变。
深复制是创建一个新的和原始句柄指向内容相同的字段,是两个一样大的数据段,所以两个句柄指向的空间是不同的,但是内容相同。之后新对象中的句柄指向的内容发生改变不会引起原始对象所指向内容的改变。
通过copy函数去进行深复制。
对象销毁
软件编程的好处在于可以动态的开辟空间,在资源闲置或者不再需要时可以回收空间。
SV采用和java相同的自动回收空间的处理方式,无需担心软件空间的开销,不像C++一样用完必须销毁
类的封装
封装是面向对象编程的核心思想,是指将对象的属性和行为封装起来,封装的载体是类,类通常对用户隐藏实现的细节。
采用封装的思想保证类内部的数据结构的完整性。
类与结构体的异同
二者都是容器。
类在声明变量之后需要构造函数new()才会构建对象,而结构体在变量声明时就已经开辟内存。
类除了可以声明数据变量成员还可以声明方法或者任务,但是结构体不行。
从根本来讲,类是某一类事物的抽象模板,而结构体是用户根据实际需要定义的符合数据类型。
类的继承
类继承包括继承父类的成员变量和方法。关键字:extends
在子类重写new函数时,需要先调用父类的new函数(super.new()),如果父类的new函数没有参数,子类也可以省略该调用,系统在编译时自动添加super.new()。
对象的初始化顺序:
-
子类的实例化对象在初始化时首先会调用父类的构造函数
-
父类的构造函数执行完,会将子类实例对象中的各个成员变量按照定义时显式的默认初始化
-
在成员变量默认初始化后,才会进入用户定义的new函数中执行剩余初始化语句。
typedef enum {WHITE,BLACK}color_t;
class ca
类的多态
多态是为了更好的实现代码的复用性,子类继承父类的属性和成员方法,这是继承的一个特性,在继承了父类的方法后对父类方法进行重写实现多态。其中包括父类指针指向子类对象,子类指针指向父类对象,即类型转换。
子类重写父类的方法时需要将父类方法置为虚方法,即在父类的方法前加上关键字virtual实现对父类方法的重写。
super关键字
super关键字,主要用于子类访问父类中的属性和方法。一般情况下如果子类对于父类中的属性或者方法进行重写,此时如果要引用父类中被重写的属性或者方法时,就需要明示super。
super.new()方法是为了调用父类中的new方法
子类在构造时首先执行的是其new函数中的super.new(),即父类的构造函数,在父类的构造函数被调用时,首先会对父类的属性进行初始化,父类属性初始化完毕后,在执行父类中new函数中的相关语句,父类中的new函数执行完毕后返回子类,此时,首先进行的是子类中的属性初始化,然后再执行子类中new函数中除了super.new()以外的语句,从而完成子类的实例化.
super关键字对非new和方法和属性使用
虚方法
关键字:virtual
虚方法为具体的实现提供了一个原型,即在派生类中,重写该方法时必须用一致的参数和返回值。
虚方法可以重写其所有基类中的方法,然而普通的方法被重写后只能在本身和派生类中有效。
每一个类的继承关系中只有一个虚方法的实现,而且是在最后一个派生类中。
在上述代码中,my_packet类继承了packet类,packet中有一个虚方法和正常的方法,p1是父类的句柄,p2是子类的句柄,当p1指向p2所指的空间时。
-
当父类的句柄调用方法时,发现display_a是个正常的方法,则调用自己的display_a方法;父类的句柄调用到display_b时,发现是个虚方法,则调用子类的方法。
-
当子类的句柄指向正常方法时,调用自己的方法,遇到虚方法时也是调用自己的方法。
类型转换
从父类到子类的类型转换称为向下类型转换,从子类到父类的类型转换称为向上类型转换,前者是安全的的,后者是不安全的。
向上转换(子–>父,父类的指针指向子类开辟的空间)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PbtKDPG3-1692509287085)(C:\Users\史飞洋\AppData\Roaming\Typora\typora-user-images\1684035692676.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODvWV3pL-1692509287087)(C:\Users\史飞洋\AppData\Roaming\Typora\typora-user-images\1683986996262.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-obyhA4Iw-1692509287091)(C:\Users\史飞洋\AppData\Roaming\Typora\typora-user-images\1684035662914.png)]
上述代码中,当父类的句柄指向父类对象时,首先调用父类的方法和属性。当父类句柄指向子类对象时,如果子类中没有相应的方法,则会调用自己的方法,
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VIdDfqEW-1692509287094)(C:\Users\史飞洋\AppData\Roaming\Typora\typora-user-images\1684036302566.png)]
向下转换(父–>子,子类的指针指向父类开辟的空间)
借助cast函数实现
$cast函数是一个类型检查的函数。用在动态类型转换中。
向下类型转换指的是子类句柄指向了父类的对象。由于子类继承了父类的属性和方法,故子类中包含所有父类中的属性和方法(前提不能设置为loca权限),即就是当子类使用父类的属性和方法时(父类的句柄指向子类的对象时)是不会出现任何意外的。但是当子类的句柄指向父类的对象时,如果调用父类中的属性和方法时会出现父类中没有而子类中有的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtg9XI9d-1692509287097)(C:\Users\史飞洋\AppData\Roaming\Typora\typora-user-images\1684039095869.png)]
Coach001
static静态变量和静态函数
静态变量
在类中,每一个对象都有自己的局部变量,这些变量不与其他的对象共享。但是如果有多个对象需要一个某种类型的变量,此时使用静态变量最好,静态变量是开始于编译阶段,贯穿整个仿真阶段。
关键字:static
引用方式:
直接引用:类名::静态变量;
对象引用:对象名.静态变量。
初始化:在声明时初始化,即在类中定义时就需要初始化,不能在构造函数中对静态变量进行初始化。
静态变量的另一个用途时在类的每一个实例都需要从同一个对象中获取信息时。
在使用静态变量时需要注意共享资源的保护。
静态方法
在使用更多的静态变量时,操作的代码会迅速增长,此时可以定义以静态方法用于读写该静态变量,甚至可以在第一个实力产生之前读写静态变量。
静态方法无法读取非静态变量。
静态方法中可以声明和使用动态变量,但是无法使用类的动态变量。因为在调用静态方法时,可能没有创建具体的对象,因此也没有为动态变量开辟空间,因此在静态方法中使用类的动态变量是禁止的,但是静态方法可以使用静态变量,静态方法和静态变量一样在编译阶段就为其分配好了空间。
静态方法的引用:
类名::方法名或者对象名.方法名。
通过对象名.方法名也可以对静态属性进行修改。
在类内声明方法,在类外定义方法
在SystemVerilog中所有类的方法都可以定义于类内,也可以定义于类外。一般将比较复杂的方法的实现放在类外,这样可以增加代码的可读性,而比较简单的方法在类内实现。如果什么方法的定义都放于类内部的话,那么查找一个方法将会比较麻烦。所以在实际使用时,经常将方法定义于类外,方法定义于类外一般分为两步实现:
第一步:在类内完成方法原型的声明,此时在方法原型前要使用关键字extern;
第二步:在类外实现方法,此时方法名前需要加上“类名::”,用于限定了该方法属于哪个类;
在完成了上述两步后,就完成了类内方法在类外实现的步骤。在完成了类内声明和类外定义之后,在使用时还需要注意类内和类外方法的匹配问题,也就是方法原型与实现的声明匹配问题,下面将通过示例说明常遇到的声明匹配问题。
但是在类内声明方法和类外定义方法时要注意参数列表的匹配问题,必须要参数列表相同。
抽象类
抽象类:可以被继承但不能直接实例化的类。
关键字:在class前加virtual。
纯虚方法:在抽象类中,可以定义没有实体的方法原型,被称为纯虚方法,使用pure virtual修饰。
纯虚方法只能在抽象类中定义。
virtual class Packet;//抽象类的定义
pure virtual function int send(bit[31:0] data);//抽象类中的纯虚方法
endclass
抽象类无法直接被创建对象,但是可以声明抽象类的句柄,必须要先扩展该类(就是让别的类对其继承)并且对所有的纯虚方法进行实现。相当于抽象类提供了某一类事务的一个类的最初的模板和模板方法。使用别的类对其继承并且实现里面的纯虚方法,就可以使用。
1、抽象类不能被直接实例化;
2、一个由抽象类扩展而来的类,要想被实例化(即成为一个非抽象类),只能在所有的纯虚方法都有实体,变成虚方法后才能实例化;
3、纯虚方法是一个原型,不是一个空的虚方法。空的虚方法如下,可以调用它,但它会立即返回:
pure virtual function int send(bit[31:0] data);//抽象类中的纯虚方法
endclass
抽象类无法直接被创建对象,但是可以声明抽象类的句柄,必须要先扩展该类(就是让别的类对其继承)并且对所有的纯虚方法进行实现。相当于抽象类提供了某一类事务的一个类的最初的模板和模板方法。使用别的类对其继承并且实现里面的纯虚方法,就可以使用。
1、抽象类不能被直接实例化;
2、一个由抽象类扩展而来的类,要想被实例化(即成为一个非抽象类),只能在所有的纯虚方法都有实体,变成虚方法后才能实例化;
3、纯虚方法是一个原型,不是一个空的虚方法。空的虚方法如下,可以调用它,但它会立即返回:
4、纯虚方法只能在抽象类中定义,但抽象类中也能定义非纯虚方法
-----------------------------------------------------------------------------------------------------------------------------
学习中部分内容来源网上博客!!!