在软件工程的早期,史诗般的挣扎着项目的复杂性,增长以及管理大型开发团队的挑战。面向对象编程(OOP)为软件社区引入了一场革命,以帮助解决这些问题。 OOP专注于模块化,改变容忍度,代码重用,易于理解和分布式开发。今天大多数项目都使用面向对象的概念。
由于C ++引入了OOP,因此用户体验已经产生了关于如何最好地利用OOP的更多知识。有很多关于面向对象的协议和更多的争论。应该鼓励哪些功能? C ++是否定义了哪些功能是面向对象的。
随着重用和验证IP在硬件验证中变得流行,OOP是适用的。事实上,功能验证为重用提供了理想的条件,因为标准协议,系统甚至测试和有趣的事务序列都利用了OOP。
OOP面临的挑战之一是定义对象和它们之间的交互。最初的协议是让个人和团队合作的关键。这些对象通过商定的公共接口(合同)提供服务。其他对象可以使用这个公共API。对象内部实现可以独立开发和完善。语言提供信息隐藏设施,以限制对象使用公共方法。下图演示了两个在OOP环境中工作的开发人员,使用单独对象之间的协议接口。
OOP接口对象启用并行开发
类定义了对象的抽象特征(属性)和行为(方法)。这是一个蓝图,允许创建一个或多个相同类型的对象。例如,可能有一个汽车类定义了汽车对象可以包含的所有东西。然后,一辆特殊的汽车,比如...... Plorsche,是汽车类的一个子类(专业化),具有超越普通汽车类的特殊属性。最后,汽车物体是具有特定颜色和引擎属性的汽车的特定实例,例如银色Plorsche轿跑车。可能有许多Plorsche汽车共享这些属性;所有这些都将成为Plorsche子类或汽车专业化的对象。
对象保存运行时数据并用作程序的构建块。程序或应用程序实例化对象并触发它们的交互。
注: SystemVerilog类与动态特性的Verilog模块实例不同。模块实例,它们的编号和层次结构是在Verilog中进行阐述时创建的,并且在整个模拟过程中都是静态的。对象可以在运行时根据请求创建。这允许创建模块化和优化的数据结构。在功能验证中,测试平台的构建过程是动态的,这使得它更加灵活。它使用程序流控制来实例化所需的测试台结构。根据需要,动态对象用于表示数据项(如数据包,帧或事务),并且通常会考虑其他环境变量值面向对象编程使我们能够在软件设计中做同样的事情。您可以定义泛型类的概念,并使用继承来创建该抽象类的特化。跑车可能是通用汽车概念的专业化。除了拥有所有的汽车属性外,它还具有更大的马力,更好的操控性,并且经常吸引人们的注意力。用户可以表达所需的功能 - 例如,驾驶()汽车 - 而无需知道特定汽车中驾驶方式的具体细节。
使用继承允许具有足够类似接口的对象共享实现代码。一个永远不应该实例化的父类 - 意味着它仅用于建模目的以便重用和抽象 - 被声明为一个虚类.
如果您在对象之间有重叠,请考虑创建一个抽象类来保存通用功能并派生一个类来捕获变体。这将减少开发和维护所需的代码量。例如,请求和响应数据包可能具有相似的属性,但请求中生成的数据是在响应中收集的,反之亦然。
例3-2继承和多态
想象一下,一辆具有飞行能力的新车被发明出来,而其他车辆则无法飞行。一个汽车使用者不希望发现他们的汽车在驶入鸿沟后不能飞行。在开始驾驶之前需要提前警告,以确保您的汽车能够飞行。在这种情况下,您需要在模拟开始之前获得编译时错误消息。例如,您不希望非法分配在执行两天后中断仿真。编译器强制用户在使用其一个子类型特征或属性之前明确地向下转换泛型引用。 (术语向下意味着在继承树中下降。)
注:在我们的汽车示例中,用户需要明确声明指针用fly()方法持有汽车。将通用句柄分配给特定的句柄称为downcast。
对象具有保存其特定数据和状态的属性。例如,一个Plorsche状态可以被“驱动”,而一个不同的Plorsche可以被“停放”。但是,SystemVerilog支持属于一个类而不是特定实例的静态属性。即使没有实例存在,也可以访问这些属性,并允许多实例预订和设置。例如,您可以使用静态属性来计算特定类的实例数量。您也可以拥有可以操纵静态属性的静态方法。静态方法不能访问非静态属性,因为这些属性可能不存在。
调用静态方法不涉及特定的实例,可以通过使用class_name :: method_name()来实现。 例如,我们可以使用这样的汽车静态方法:car::increment_counter()
当需要用于不同数据类型或大小的类似逻辑时,使用参数化类。参数化类的经典用法适用于容器。以下示例显示了不同类型对象的容器。
典型的应用程序利用来自多个来源的许多类。有可能某些类型,类或函数名称可能会发生冲突并导致编译错误。修改类和类型名称以获得唯一性的成本可能很大。此外,当使用加密的代码时,如验证IP(VIP),这个问题更具挑战性。
为了避免全局名称空间发生冲突,面向对象的语言将添加包或名称空间功能。这为每个包定义创建了一个不同的范围。在不存在冲突的情况下,用户可以使用import命令将所有定义导入联合名称空间。如果存在冲突并且不可能包含导入,则用户可以导入特定类型或使用类的完全限定类型名称(包名称和类名称的组合)。
例如,假设在我们的系统中,开发了两种类型的汽车类别(sports_car),一种是品牌A,另一种是品牌Z.没有包装,两个类别称为“sports_car”(来自品牌A和品牌Z各一个)无法编译
在以下示例中,我们将包导入单个模块范围。 这是可能的,因为在包内与名称没有冲突。 您可以导入包中的所有符号,或者只使用不碰撞的选定符号。
为所有可重用代码使用包至关重要,以便与其他包中的其他类似类共存并防止代码修改。软件问题的创造性解决方案可以产生随机的好处和后果。另外,已经证实的解决方案可以产生更一致的结果我们不反对创造力,但大多数验证挑战需要创造性和常识性,而不需要重新发明轮子。设计模式是软件设计中常见问题的一般可重复解决方案。 1995年出版的“设计模式:可重用面向对象软件的元素”(通常被称为“四人帮”或“GoF”一书)一书创造了术语设计模式,并成为面向对象的软件的重要来源,面向设计理论与实践。这些最为人所知的做法是在包含姓名,挑战,代码样本等的模板中捕获的。有关更多信息,请阅读Gamma,Helm,Johnson和Vlissides的“设计模式:可重用面向对象软件的元素”一书。
软件设计模式:单例模式
许多系统都要求系统资源只应该实例化一次。这可能是内存管理员,全球协调员或其他系统级协调员。问题是如何创建一个具有内置限制的类只能实例化一次?单例解决方案通过使构造函数专用于类来工作,从而防止类被实例化。实例化类的唯一方法是调用静态方法,该方法在首次调用时分配类的本地实例并返回调用者的对象句柄。对此静态方法的连续调用将返回实例化的实例句柄,而不分配新的类。 UVM库中的单例的一个示例是报告服务器具有受保护的构造函数,以将服务器的创建限制为单个实例
上面提到的是一些书籍,它们记录了多年来在面向对象编程方面积累和证实的经验。那么,为什么我们需要更多的方法?大多数通用概念适用于功能验证任务并且有效。但功能验证引入了独特的挑战,并且与软件开发不同。与每六个月发布的程序相反,功能验证的结果是最终测试,其中几项可能会在一天内创建。考虑下图中的测试平台以及使用它的三个测试。每个测试涉及测试平台的不同方面。它可能会改变配置,引入不同的序列,在生成的数据项上使用约束等来实现其目标。面向对象的编程不是为了这样的极端重用,也没有考虑到通过测试进一步修改类的开放性。