验证基础技巧记录(五)OOP

1.类成员属性的默认lifetime是automatic的,他们的生命周期与对象相同;

2.与此相对应的,static成员变量的存在不依赖对象,也可通过类名使用解析运算符访问;

3.对于dv来说,代码复用要求有一些,但是低于一般的面向对象语言,所以封装用的少;

4.method的extern修饰符在类的method过多而导致冗长的时候使用;

因为很多时候review的人只关心接口和功能而不关心内部实现,在class里完成声明接口再在外部定义,可以增加代码的可读性;(便于维护)

5.static method用于处理static成员变量,其无法访问动态变量,因为他们生命周期不同;而static method可以在无对象时通过类名+作用域解析运算符访问;

6.method的主要任务是:1.处理内部事务;2.与外界做交互;

SV封装的特性不显著,很多时候可以在外界直接访问,而不是像传统面向对象语言的要求那样希望外部不能通过method以外的手段拿到任何成员变量信息和知晓类的状态;

传统oop中,method作为不同object间的interface使用;

7.创建对象的new和new func其实不同,new的作用是创建对象,分配空间以及返回指针;

*而new func是一个与new捆绑的函数,只是返回值被写死成了new返回的指针,从而使左侧成为句柄;

*new func主要做的是初始化对象,而其可由用户控制,new则是不可控的;

8.handle的初始值是null,其和许多数据类型性质类似,就像int的初始值是0;handle拥有值和地址;

*hanle在创建对象以后,其存储的值其实就是对象地址,将这个值赋予给其他句柄,自然可以通过值找到同一个对象并操作;

9.handle不宜直接使用,其被设计用于访问对象;

10.sv会在程序中没有任何指针指向某个对象时sv会将其的内存释放;

11. 传递句柄就是在传递指针,对handle的操作会反应在外界显现;

*有时也需要传递ref handle_name handle,这种情况可以手动释放句柄;

*sv里ref handle的情况很少,因为sv的语法不支持用句柄参与计算来使其偏移地址;(获得相邻地址的数值)

12.正确的创建多个对象的手段P136 绿皮书5.28;

*虽然5.28是被作为错误例子提出的,但我们应该将其视为一种特性;

*多次循环只创建一个对象是比多次循环创建多个对象运行更快的;(占用过多内容,而且需要释放对象,增加运行时间)

*5.28是紧耦合,5.29是松耦合;

*如果5.28的下一次循环发生在transmit消化完这次存入的句柄,则5.28和5.29没什么差别;

*因此,5.29未必比5.28更好,要结合不同的使用场景来选择;

(对仿真时间要求高,可以用创建更少对象的方法)

13.作用域规则:始终就近,从里到外;

*作用域的设定是基于程序块的;

*精确选择而不依赖作用域寻找的方法:$root,

例如:$root.top_name.class_name.function_name或是$root.top_name.thread_name;

14.复制对象:

浅复制:

浅复制或称为浅拷贝会复制目标对象的所有成员变量,包括句柄;

但是这种复制方式会导致每个复制对象的句柄都指向同一个下层对象;

trans src,dst;
initial begin
src = new();
dst = new src;
end

手写浅复制函数:new一个新对象,复制原变量的成员变量并返回其句柄;这个用法等同于new handle_pre;

深层复制函数:

(1)要写成深层复制函数,首先被复制对象的new中需要包含对其所调用对象的new;

(2)被赋值对象所调用的对象中,也要包含浅复制函数甚至深层复制函数;

(3)深层对象复制函数的编写步骤:

  1.new一个新对象;

  2.拷贝被复制对象的成员变量并完成赋值;

  3.调用被复制对象,所调用的下级对象的浅复制函数或深层复制函数,完成进一步的拷贝;

*这个过程是可以不断嵌套的,这样可以实现新对象所调用的对象也是新的这一目的;

15.继承

*super可以让子类访问基类的member,但是只能访问到其的更上一层结构,也就是没有super.super这种用法;

* 如果子类new的时候没有显式super.new,编译器会自动插入一个隐式的 super.new() 调用,且使用父类构造函数的 默认参数(如果有);

16.$cast

*若是希望将基类句柄赋值给子类句柄,只能使用$cast;

例如:

trans a,b;
trans_extends c,d;

initial begin
    c = new();
    b = c; // 产生了一个指向子类对象的基类方法,但其只能访问基类member;
    $cast(d,b); // 成功赋值,使d指向了b指向的子类对象,也就是c指向的;
     // 直接d = b会编译失败;
end

$cas(target,src);

  当基类句柄-子类句柄时,cast的成功前提是基类句柄被子类句柄赋值过,或意思是其指向一个子类空间;

  当子类句柄-基类句柄时,可以实现转变;

14和16的结合,当子类需要copy函数时,需要将基类的copy写成virtual,并在子类使用super访问;

  考虑拓展的深层复制函数编写:

  基类:

    1.编写一个返回当前类型对象句柄的复制函数,在函数里完成new和成员的复制;

    2.这个函数需要是virtual的;

  子类:

    1.在子类中virtual一个同名函数,此函数的返回类型是子类句柄;

    2.new这个句柄;

    3.在子类的复制函数中调用基类的复制函数,并完成对子类成员的复制;

    4.需要注意,因为使用子类句柄对基类的复制函数进行了输入,并且返回的是一个指向子类对象的基类句柄,因此需要在对子类成员的复制前使用cast

17.virtual

*当使用virtual方法时,即使指向子类对象的是基类句柄,其调用的也是子类方法;

*通过索引对象类型而不是句柄类型来使用method;

18.抽象类:virtual class;

抽象类是一个虚拟类,不能直接在程序中实例化(换句话说,无法创建虚拟/抽象类的对象)。
如果尝试这样做,它会抛出编译时错误。
它们将用作继承任意数量的类的基类。
通常我们使用抽象类来预先定义原型,也就是说,抽象类为一组派生类提供了一个模板。
因此,您知道您已经有一组基本的方法/属性,这使得您更容易专注于创建这个派生类的新意图。
在抽象类中,虚方法可以通过关键字 ‘pure’ 声明,并称为 纯虚方法。
如果使用了任何 纯虚方法,则类将自动将其视为抽象类。它应该只被添加到基类中。
在基类中,我们不允许为纯虚函数添加任何函数定义。重要的是要知道,具有纯虚方法的基类的所有派生类应该分别实现所有纯虚方法。

抽象类,只能被extends,不能被示例化。

其包含虚方法和纯虚方法,非虚方法。

具有纯虚方法的基类的所有派生类应该分别实现所有纯虚方法。

纯虚方法

关键字pure,可以没有body,只有原型prototype;(有名字,返回类型,参数列表,但没有实现)

其只能在抽象类中声明;

纯虚方法主要用于接口,其在uvm的工厂机制是很难使用的。

19.

override

  如果一个method名字,参数列表,返回类型之一不同,那么他们就是不同的method;

  在sv中其实并不支持,如果名字不一样参数列表不同,会报错;

  在其他面向对象的语言中,如果名字相同,会根据输入参数和期望的返回值类型来调用的不同的method;

overwrite:存在于子类和基类之间

  当基类方法没有使用virtual,就会出现overwrite:

  这种方法会根据handle的类型索引不同方法;

  *用的比较少。

overload virtual

  当使用overload时,启用overload;

  这种方法会根据handle指向object的不同,索引调用不同的方法;

  实际上,回调函数的pre_randomize和post_randomize是写死的,而且不是virtual方法。这使得我们无法直接对其override,

  但我们可以在这些方法里调用自己的user_pre_randomize之类的,并将其修饰成virtual,从而间接实现override;

20.callback

  回调函数是将一个函数作为参数传递给另一个函数的方法;

  这个做法在大部分语言都不被支持,但是可以通过callback来实现;

  task pre_callback;

  task post_callback;

  callback并不是一种sv的语法,而是一种编程设计写法。

  通过声明virtual class,并在其中声明virtual method,可以用于extends需求的回调方法;

    1.定义回调virtual类,里面定义virtual method;

    2.extends回调类,在extends中完成对virtual method的overlodad;

    3.new extends的实例,将其加入到存储回调句柄的数组里;

    4.通过foreach或其他方法在某个函数执行前调用句柄数组里的方法;

  子类的指针可以赋值给基类,不同的子类句柄能存进同一个基类句柄队列;

  这使得可以有序调用多种子类的方法;

    通过回调基类来定义回调函数比,直接将回调函数写在调用回调的class里,更灵活,

    因为可以只extends 回调基类class,而不是extends 需要回调的class;

  因此,uvm通过对象传递method,而不是直接传递method;

    使用VIP时,driver是在agent里做的声明,如果直接extends driver就需要用uvm的factory机制来弥补一些差池,这可能导致难以注意的问题;

    但如果使用回调基类,只需要extends回调基类再添加代码就能完成回调,这也是uvm和vip共同支持的做法。

21.参数化类

  参数化类的参数列表不同时,相当于两个完全不同的class;

  这个做法仅仅是简化了写作,减少了代码,这些不同参数的class没有任何关系;

22.面向对象的解决问题的方式

  类的封装:

  面向对象的方法在解决问题时,需要将解决问题需要的操纵分类,也就是使用一个个框架来囊括其的各个部分,然后在每个框架内实现他们各自的功能,最终实现解决问题的目的。

  设计框架时只需要考虑每个框架的输入输出就行了,这种思考方式限定了每次考虑问题的level,在低层次只考虑低层次问题,高层次也只考虑高层次问题。

  例如,实现测试平台一事需要做的是实现验证组件,而需要关注的事情就只有两件:

    1.每个组件是怎么样与其他组件进行通信的;

    2.每个组件是怎么样实现的;

  这种思考问题的方式使我们更加专注,而无需在解决其中一个组件前就考虑下一个组件的内容。

  但客观来说,OOP的思考方式对DE意义不大,因为电路按框架来分类反而影响走线,带来延迟。

  类的多态:

  封装的意义在于隐藏内部实现,仅将接口暴露出来并与其他模块相连;

  不同的需求需要有不同的接口,改动接口的工作量很大。

  引入多态可以通过创建common interface来extends实现不改动接口,专注内部的实现;

  interface应该在产生激励的时候就考虑好,这个东西实际上越早确立越好;

  从而实现减少代码量的目的;

  如何获得对象?

    如何从一件需要做的事情抽取出其对象(一些对象,未必只有一个),并通过class进行建模;

    对于dv来说,uvm已经把各个部件抽取好了,我们需要考虑的是如何实现这些部件。

  如何与对象通信?

    

 

posted @ 2025-07-08 18:42  NoNounknow  阅读(5)  评论(0)    收藏  举报