面向对象编程基本概念

引言

 在软件工程的早期,史诗般的挣扎着项目的复杂性,增长以及管理大型开发团队的挑战。面向对象编程(OOP)为软件社区引入了一场革命,以帮助解决这些问题。 OOP专注于模块化,改变容忍度,代码重用,易于理解和分布式开发。今天大多数项目都使用面向对象的概念。

由于C ++引入了OOP,因此用户体验已经产生了关于如何最好地利用OOP的更多知识。有很多关于面向对象的协议和更多的争论。应该鼓励哪些功能? C ++是否定义了哪些功能是面向对象的。

随着重用和验证IP在硬件验证中变得流行,OOP是适用的。事实上,功能验证为重用提供了理想的条件,因为标准协议,系统甚至测试和有趣的事务序列都利用了OOP。

设计大型软件应用程序

 传统上,程序被视为一系列功能的集合。这适用于小型应用程序,但阻碍重用程序的一个子集或利用分布式并行开发过程,其中不同的工程师拥有程序的不同方面。那么我们如何设计一个大型应用程序呢? OOP建议使用“分而治之”的方法,其中应用程序是一组相关的交互对象。例如,模拟交通的应用程序将涉及汽车,司机和交通信号灯,而不是为整个交通系统设计应用程序,我们应该专注于单独的模块以捕捉汽车,司机和交通灯操作。测试平台或验证项目也是如此。我们专注于最终构成测试平台的数据项或协议特定组件,而不是专注于完整的应用程序。

OOP中的对象是什么?

 一个对象是一个实体,它拥有的数据和对这些数据进行操作的方法。每个对象都可以被看作是一个独立的小机器或者具有不同责任的角色。在我们的交通仿真例子中,汽车,司机和交通灯都是对象。

 分布式开发环境

 OOP面临的挑战之一是定义对象和它们之间的交互。最初的协议是让个人和团队合作的关键。这些对象通过商定的公共接口(合同)提供服务。其他对象可以使用这个公共API。对象内部实现可以独立开发和完善。语言提供信息隐藏设施,以限制对象使用公共方法。下图演示了两个在OOP环境中工作的开发人员,使用单独对象之间的协议接口。


                       OOP接口对象启用并行开发

分离问题

 我们可以使用相同的对象来创建其他系统吗?或复制一个对象并将其用于不同的系统?这需要建立独立的对象。对象之间不应存在功能重叠。例如,我们的交通仿真例子也可能包括一条赛道。在一个典型的赛道上,没有交通信号灯,没有转向灯,而且赛车速度更快。如果一辆汽车的实施假定存在交通灯,则不能参与没有交通信号灯的赛道系统。同样,一个功能验证示例可以拥有一个不应该自行发送或提交给特定协议/驱动程序的数据包。如果是这样,它不能用于需要不同驱动协议或协议分层的环境中。

 类,对象和程序

 类定义了对象的抽象特征(属性)和行为(方法)。这是一个蓝图,允许创建一个或多个相同类型的对象。例如,可能有一个汽车类定义了汽车对象可以包含的所有东西。然后,一辆特殊的汽车,比如...... Plorsche,是汽车类的一个子类(专业化),具有超越普通汽车类的特殊属性。最后,汽车物体是具有特定颜色和引擎属性的汽车的特定实例,例如银色Plorsche轿跑车。可能有许多Plorsche汽车共享这些属性;所有这些都将成为Plorsche子类或汽车专业化的对象。

对象保存运行时数据并用作程序的构建块。程序或应用程序实例化对象并触发它们的交互。


 注: SystemVerilog类与动态特性的Verilog模块实例不同。模块实例,它们的编号和层次结构是在Verilog中进行阐述时创建的,并且在整个模拟过程中都是静态的。对象可以在运行时根据请求创建。这允许创建模块化和优化的数据结构。在功能验证中,测试平台的构建过程是动态的,这使得它更加灵活。它使用程序流控制来实例化所需的测试台结构。根据需要,动态对象用于表示数据项(如数据包,帧或事务),并且通常会考虑其他环境变量值

 使用泛化和继承

 人类使用泛化来感知世界。汽车的抽象概念意味着:四个车轮,一个发动机,至少两个车门,一个方向盘等等。这种抽象能力使我们能够组织数据并实现有效的沟通。例如,你可以说“我开车让我的车昨天工作”,听众不需要你定义一辆车,谈论你驾驶的特定汽车,或者你驾驶的方式。这些细节是不必理解你简单陈述的意图

 面向对象编程使我们能够在软件设计中做同样的事情。您可以定义泛型类的概念,并使用继承来创建该抽象类的特化。跑车可能是通用汽车概念的专业化。除了拥有所有的汽车属性外,它还具有更大的马力,更好的操控性,并且经常吸引人们的注意力。用户可以表达所需的功能 - 例如,驾驶()汽车 - 而无需知道特定汽车中驾驶方式的具体细节。

使用继承允许具有足够类似接口的对象共享实现代码。一个永远不应该实例化的父类 - 意味着它仅用于建模目的以便重用和抽象 - 被声明为一个虚类.


创建紧凑可重用代码

 如果您在对象之间有重叠,请考虑创建一个抽象类来保存通用功能并派生一个类来捕获变体。这将减少开发和维护所需的代码量。例如,请求和响应数据包可能具有相似的属性,但请求中生成的数据是在响应中收集的,反之亦然。



OOP中的多态

 程序可能需要以程序的方式操纵一组对象。例如,您可能想要一个汽车的抽象句柄,并在一个循环中调用数组中所有汽车的run()方法。每个汽车类都有不同的run()方法实现。对抽象类执行run()并执行特定run()的能力称为“多态性”。程序员不必提前知道对象的确切类型。下面的例子3-2演示了Plorsche和Flerrari汽车是如何从一个抽象运动车类派生的。多态性允许程序的print()任务持有一个指向可能包含Plorsche和Flerrari汽车的虚拟类数组的指针,但为每个项目执行正确的print()函数

 例3-2继承和多态


Downcast

 想象一下,一辆具有飞行能力的新车被发明出来,而其他车辆则无法飞行。一个汽车使用者不希望发现他们的汽车在驶入鸿沟后不能飞行。在开始驾驶之前需要提前警告,以确保您的汽车能够飞行。在这种情况下,您需要在模拟开始之前获得编译时错误消息。例如,您不希望非法分配在执行两天后中断仿真。编译器强制用户在使用其一个子类型特征或属性之前明确地向下转换泛型引用。 (术语向下意味着在继承树中下降。)

注:在我们的汽车示例中,用户需要明确声明指针用fly()方法持有汽车。将通用句柄分配给特定的句柄称为downcast。


类库

 类库是用于加快系统实现的类的集合。用户可以通过定制它们来从库类派生新的类,或者只是实例化和使用它们,而不是从头开始实现。例子包括数学图书馆,图形图书馆,甚至是面向验证的图书馆。 UVM有一个类库。类库的一个重要优点是它编写了最佳实践并实现了标准化和重用。使用类库的缺点是需要学习类库规范。虽然类库带有API规范,但通过类库实现并了解功能和建议的实现是一种很好的做法。

静态方法和属性

 对象具有保存其特定数据和状态的属性。例如,一个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库中的单例的一个示例是报告服务器具有受保护的构造函数,以将服务器的创建限制为单个实例

软件设计反模式

 反模式,也称为陷阱,通常是对问题的不良软件解决方案进行彻底改造。这个词是由安德鲁柯尼希1995年创造的,受到了四人帮的启发。使用不良做法有多种原因。在某些情况下,它涉及缺乏计划;在其他情况下,在错误的情况下使用完美的解决方案,使其适得其反。与设计模式一样,反模式具有正式的模板,其名称,原因,含义等等。反模式还包括恢复过程(重构)以修补误用的实践。反模式的一个例子是“意大利面条”代码 - 源代码,它具有复杂和纠结的控制结构。有关更多信息,请阅读Brown,Marley,McCormick和Mowbray撰写的书籍“AntiPatterns:危机中的软件重构,体系结构和项目”

为什么现有的面向对象方法不够?

 上面提到的是一些书籍,它们记录了多年来在面向对象编程方面积累和证实的经验。那么,为什么我们需要更多的方法?大多数通用概念适用于功能验证任务并且有效。但功能验证引入了独特的挑战,并且与软件开发不同。与每六个月发布的程序相反,功能验证的结果是最终测试,其中几项可能会在一天内创建。考虑下图中的测试平台以及使用它的三个测试。每个测试涉及测试平台的不同方面。它可能会改变配置,引入不同的序列,在生成的数据项上使用约束等来实现其目标。面向对象的编程不是为了这样的极端重用,也没有考虑到通过测试进一步修改类的开放性。


面向方面的编程

 最近,软件设计模式已被批评为建议复杂的配方,而不是改进工具或语言。 OOPSLA(面向对象的程序设计,系统,语言和应用)是一个主要在美国举行的年度ACM会议,它主持了一个名为“四人帮”试验的小组。 Verisity引入了e语言来解决验证挑战,并使用这种内置解决方案而非设计模式。在面向方面的特性e面(这是面向对象的超集)的基础上,面向方面的编程提供了许多SystemVerilog语言限制的简单解决方案。在SystemVerilog UVM中,我们使用工厂解决方案,回调,字段宏,配置机制等等。在这些不是必需的。此外,e允许其他功能,例如覆盖范围和枚举类型扩展,用于数据项随机化的更强大的建模功能以及显着简化验证任务的其他语言功能。该行业在向其他HVL提供有限的AOP扩展方面做出了一些努力,但这些扩展受到限制,并且无法理解挑战和解决方案。我们只能希望SystemVerilog能够在某些时候采用这些功能并简化验证工作,从而为SystemVerilog用户群带来益处

概要

 面向对象编程引入了一种处理大型软件应用程序复杂性的方法。它侧重于模块化,适应变化和重用,这是功能验证的关键。了解理论和动机是有益的,但是没有必要为了使用UVM库而掌握理论。使用UVM库的优点之一是它将最好的验证实践编入库基类,使您能够按照推荐的指示完成正确的事情

猜你喜欢

转载自blog.csdn.net/zhajio/article/details/79991151