本文主要是介绍面相对象编程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
OOP术语
创建对象
静态变量和全局变量
类的方法
继承
蓝图模式
类型向下转换和虚方法
对象的复制
抽象类和虚方法
回调
参数化的类
OOP术语
(1)类(class):包含变量和子程序的基本构建块。Verilog 中与之对应的是模块( rmodule)。
(2)对象(object):类的一个实例。在 Verilog中,你需要实例化一个模块才能使用它。
(3)句柄( handle);指向对象的指针。在 Verilog中,你通过实例名在模块外部引用信号和方法。一个OOP句柄就像一个对象的地址,但是它保存在一个只能指向单一数据类型的指针中。
(4)属性(property):存储数据的变量。在 Verilog中,就是寄存器(reg)或者线网( wire)类型的信号。
(5)方法( method):任务或者函数中操作变量的程序性代码。Verilog模块除了initial和always块以外,还含有任务和函数。
(6)原型( prototype):程序的头。包括程序名、返回类型和参数列表。程序体则包含了执行代码。
创建对象
在声明句柄tr的时候,它被初始化为特殊值null。接下来,调用new ()函数来创建Transaction对象。new函数为Transaction分配空间,将变量初始化为默认值(二值变量为0,四值变量为X),并返回保存对象的地址。对于每一个类来讲,SystemVerilog创建一个默认的new函数来分配并初始化对象。
构造函数
构造函数除了分配内存之外,它还初始化变量。在默认情况下﹐它将变量设置成默认数值——二值变量为0,四值变量为X,等等。可以通过自定义new函数将默认值设成想要的数值。这就是为什么new函数也称为“构造函数”。但是new函数不能有返回值,因为构造函数总是返回一个指向类对象的句柄,其类型就是类本身。
静态变量和全局变量
静态变量
在System Verilog中,可以在类中创建一个静态变量。该变量将被这个类的所有实例所共享,并且它的使用范围仅限于这个类。对于静态变量的访问,无需使用句柄,可以使用类名加上::,即类作用域操作符。
类的方法
类中的方法默认使用自动存储,所以你不必担心忘记使用autonatic修饰符。
继承
当需要的是一种跟原始类很相像的新类,它增加了一些新的变量和方法。继承就可以达到这种效果。继承允许从一个现存的类得到一个新的类并共享其变量和子程序。原始类被称为基类或者超类,而新类因为它扩展了基类的功能,被称为扩展类。继承通过增加新的特性提供了可重用性,并且不需要修改基类,例如对现有的类增加错误注人功能。
通过使用super前缀调用基类中的函数,即super可以调用上一层类的成员,但是SystemVerilog不允许用类似super.super.new的方式进行多层调用,因为这种调用风格跨越了不同的层次,也跨越了不同的边界,自然违反了封装的规则。
应该将类中的子程序定义成虚拟的,这样它们就可以在扩展类中重定义。这一点适用于所有的任务和函数,除了new函数。因为new函数在对象创建时调用﹐所以无法扩展。SystemVerilog始终基于句柄类型来调用new函数。
构造函数的规则:如果基类的构造函数有参数,那么扩展类必须有一个构造函数而且必须在其构造函数的第一行调用基类的构造函数。
蓝图模式
OOP中一种常用的技术,当你想要构建一个事务发生器的时候,在验证环境中预留一个父类Transaction的指针,通过子类Transaction继承自父类Transaction来修改Transaction中的约束和变量,在实例化验证平台时,可以根据业务需要实例化父类Transaction还是子类Transaction来调整Transaction的类型,这种技术就称为蓝图模式。可以简单理解为“换汤不换药”,框架(父类指针)不变,实例改变(父类实例或子类实例)。
类型向下转换和虚方法
$cast
类型向下转换或者类型变换是指将一个指向基类的指针转换成一个指向派生类的指针。
使用$cast作类型向下转换。
可以将一个派生类句柄赋值给一个基类句柄,并且不需要任何特殊的代码
当一个类被扩展时,所有的基类变量和方法将被继承。
但是,当你试图做反方向的赋值,即将一个基类对象烤贝到个扩展类的句柄中时,这种操作会失败,因为有些属性仅存在于扩展类中,基类并不具备。
将一个基类句柄赋值给一个扩展类句柄并不总是非法的。当基类句柄确实指向一个派生类对象时是允许的。
$cast子程序会检查句柄所指向的对象类型,而不仅仅检查句柄本身。
$cast(bad2,tr);//检查对象类型并拷贝,如果类型失配则在仿真时报错。如果成功,bad2就指向tr所引用的对象。
if(!$cast(bad2,tr)) //检查类型失配,如果类型失配,在仿真时也不会输出错误信息
$display("Cant assign tr to bad2");
当将$cast作为一个任务来使用的时候,SystemVerilog会在运行时检查源对象类型,如果跟目的对象类型不匹配则给出一个错误报告。当将$cast作为函数使用时,SystemVerilog仍然做类型检查,但是在失配时不再输出错误信息。如果类型不兼容,$cast函数返回0.如果类型兼容则返回非零值。
虚方法
当需要决定调用哪个虚方法的时候,SystemVerilog根据对象的类型,而非句柄的类型来决定调用什么方法。如果方法没有使用virtual修饰符,SystemVerilog会根据句柄的类型,而不是对象的类型﹐就会导致不是你想要的结果。
OOP中多个子程序使用一个共同的名字的现象叫做“多态( polymorphism)"。它解决了计算机架构设计师面临的一个问题,即如何在物理内存很小的情况下让处理器能够对一个很大的地址空间寻址。
使用虚方法也存在一些劣势——一旦定义了一个虚拟的子程序,所有带有该虚拟子程序的扩展类就必须使用相同的“签名”例如相同类型和个数的参数。在扩展类的虚拟子程序中不能增加或者删除参数。这就意味着你必须提前做好计划。
对象的复制
带有copy虚函数的事务基类中,copy虚函数的返回为基类对象,当扩展基类,生成扩展类时,copy函数仍然需要返回一个基类对象,这是因为扩展类的虚函数必须跟基类的copy虚函数向匹配,包括所有的参数和返回类型。
抽象类和虚方法
OOP语言,例如SystemVerilog,允许你使用两种构造方法来创建一个可以共享的基类。第一种是抽象类,即可以被扩展但是不能被直接实例化的类,它使用“virtual”关键词进行定义。第二种即纯虚(pure virtual)方法,这是一种没有实体的方法原型。一个由抽象类扩展而得来的类只有在所有虚方法都有实体的时候才能被例化。关键词"pure”表明一个方法声明是原型定义,而不仅仅是空的虚方法。最后,纯虚方法只能在抽象类中定义,但是抽象类中也可以定义非纯方法。应当指出的是语言参考手册(LRM)允许你定义一个不带有实体的虚方法----------------你可以调用它但是它会立即返回。
回调
Driver::run任务在无限循环中调用一个transmit任务。在发送事务之前,如果存在前回调(pre_callback)任务,则run进行调用。在发送事务之后,如果存在后回调(postcallback)任务,它也会调用。默认情况下是没有回调任务的,所以run仅仅调用transnit.
你可以将Driver : :run定义为一个虚方法,然后在可能的扩展类MyDriver: : run中覆盖其行为。这样做的缺点是如果你想增加新的行为,可能需要在新方法中重复原方法的所有代码。一旦你对基类作了修改﹐你需要记住将它传播到所有派生类中去。此外,你可以增加一个回调任务而无需修改构成原对象的代码。
一个回调任务应该在顶层测试中创建,在环境中的最低级即驱动器中调用。驱动器无须知道关于测试的任何信息——它只需要使用一个可以在测试中扩展的通用类。驱动器使用一个队列来保存回调对象,这样就可以增加多个对象。回调基类是一个抽象类,使用前必须先进行扩展。
需要指出的是虽然Driver_cbs是一个抽象类,但pre_tx和post_tx不是纯虚方法。这是因为一个典型的回调任务只会使用它们中的一个。只要类中存在一个没有实现的纯虚函数,OOP的规则就不允许例化这个类。
参数化的类
例:class generator #(type T=BaseTr);
在建立参数化类的时候,你应当从非参数化类开始,仔细地调试,然后增加参数。这种分开的做法可以减少你之后的调试时间。
宏是参数化类的一种替代形式。例如,可以为发生器定义一个宏,然后用它传递事务数据类型。宏相对于参数化的类来说更难调试。
如果你需要定义若干相关的类使它们共享相同的事务类型,可以使用参数化类或者是一个大的宏。总之.传入类的类型比类的定义更加重要。
你的事务类中的通用虚方法集可以帮助你创建参数化类。例如Generator类使用copy方法,并总是使用相同的签名。类似地,当事务穿过你的测试平台组件时,display方法允许你方便地对它们进行调试。
这篇关于面相对象编程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!