SV知识点总结-类和包

类和包

  1. 类的基本点

类的创建和基本流程

class,end class;创建完类以后,用创建的类声明句柄,再为该类分配空间(创建该类的对象)

对象的创建,销毁,复制

创建:创建对象可以直接用tr=new()的方式进行对象的创建,也可以通过构造函数在创建对象的同时完成初始化(注意这里的操作是对类里创建的对象进行通过构造函数进行初始化,而不是对句柄进行操作),构造函数也可以定义多个参数,在初始化过程中从外部实现对参数的传递。

销毁:上一个对象在新的对象创建时会自动释放,也可以通过tr=null的方式解除对象的分配。对象创建时一个内存空间开辟的行为,必须伴随着new()函数(构建函数)。SV对对象空间的管理是自动安排的,即如果环境中没有任何一个句柄再指向该对象时,那么其空间将会被回收,从而实现内存动态管理。

需要注意在对类的成员变量进行初始化的时候有两种方式,其一是变量声明的时候初始化,另外一个是先声明变量,随后在new中进行初始化。这两者方式均可以,但是前者在前后者在后,如果两者方式都写的化会出现覆盖的情况,也就是后者覆盖前者。

拷贝(需要区分对象的拷贝和句柄的拷贝,句柄的拷贝是用=,往往指子类到父类的转换和父类到子类的转化):

正常的拷贝情况

属性成员中存在具有类属性的成员时的复制情况:

(1)new复制,浅拷贝:可能会出现最高一级的对象被new拷贝到目的对象中(类中包含一个指向另一个类的句柄).比如,创建对象a,给对象a赋值1 采用语句b=new a将a拷贝到b中,此时a和b指针句柄(这个句柄是外部对象的句柄)均是一样的,并且a和b的值均是1,无论是对a还是b的值进行修改,由于两者句柄是一样的,其变化都是同步的。(2)copy复制,深拷贝,需要通过自己编写复制函数来实现。实现步骤如下:创建自己的copy函数;创建目标 copy=new();拷贝对象的同时把具有类属性的成员也进行拷贝;a=b.copy()实现深层次拷贝。

下面这个例子中,tr1被复制给tr2,此时第一次打印和第二次打印输出的结果是一样的。再通过tr2修改了被复制的pkt中的属性payload为’hBA,但是在第三次和第四次分别打印输出的结果却不一样,除了tr1的变量属性成员不一样外,pkt.payload一样,都是修改后的’hBA。这是因为虽然tr1被复制给了tr2,此时tr1中的句柄pkt也被复制给了tr2,而句柄本身可以理解为指针,那么相当于tr2和tr1使用了相同的指针句柄,此时如果对tr1或者tr2中的句柄指向的内容进行了修改,相当于对于tr1和tr2中pkt句柄共同指向的空间进行了修改,所以此时虽然仅仅修改了tr2,但是tr1中pkt指向的空间的内容也被其同时修改了。而且可以通过实际仿真结果可以看出,tr中new函数中的“pkt = new()”在复制时并没有被执行,因为如果该new函数被执行的话,那么tr1被复制后,tr2中的pkt和tr1中的pkt是会不一样的,即tr1和tr2中的pkt会指向不同的空间

通过深拷贝解决,示例中,tr1通过transaction的copy函数被复制给tr2,此时第一次打印和第二次打印输出的结果是一样的。再通过tr2修改了被复制的pkt中的属性payload为’hBA,在第三次和第四次分别打印输出的结果不一样,除了tr1的变量属性成员不一样外,pkt.payload也不一样,tr2此中的pkt指向的payload值为’hBA,而tr1中的pkt只想的payload的值为’hAB,即此时tr1和tr2中的pkt指向的payload值已经是不一样的了。这是因为虽然tr1使用transaction的copy函数被复制给了tr2时调用了packet的copy函数,即此时将tr1中的pkt指向的对象复制了一份给tr2,此时tr1和tr2中的pkt指向的对象不再是同一个对象了。

function transaction copy/copy.pkt/pkt.copy怎么理解?深拷贝的原理实际就是一种递归拷贝。在这里实际上有两个copy函数,分别是前面packet中的copy和后面transaction中的copy。在对pkt进行copy的时候实际上首先执行transaction中的copy,首先创立了一个对象copy,对对象copy中的pkt进行拷贝。其次后面的pkt.copy实际上是通过transaction类中的对象pkt调用外部的copy,外部的copy也就是packet中的copy,这个copy中创建了一个新的对象,后续通过copy.payload=this.payload进行拷贝,而在后面我们对payload进行赋值(tr2.pkt.payload)实际上是改变了packer类中payload的值,也就是改变了this.payload的数值。在copy.pkt=pkt.copy语句中,pkt.copy实际上返回了一个句柄,将这个句柄返回给了copy.pkt。这个过程实际上是完成了两个对象的创建。

继承过程中的拷贝(浅拷贝只对对象中的成员变量完成数值的拷贝,而深拷贝可以克隆被拷贝的对象的成员句柄所指向的对象):

子类继承于父类下的拷贝应该遵循如下准则:(1)将成员的拷贝函数和生成新对象的函数分为两个方法,这样便于复用(2)为了保证父类和子类的成员均可以完成拷贝,应该在父类中定义具体的拷贝方法,并将其定义为虚方法,并且在子类中拷贝时遵循只拷贝该类的域的成员的原则,父类的成员拷贝应由父类的拷贝方法来完成。(3)在实现拷贝函数的时候需要注意句柄类型的转化。

class basic_test;

  int fin;

  int def = 100;

  function new(); 

  endfunction 

  virtual function void copy_data(basic_test t);

    t.def = def;

       t.fin = fin;

  endfunction

  virtual function basic_test copy();

    basic_test t = new();

       copy_data(t);

       return t;

  endfunction

endclass

class test_wr extends basic_test;

  int def = 200;

  function new();

    super.new();

  endfunction

  function void copy_data(basic_test t);

    test_wr h;//创建子类对象

       super.copy_data(t);

       $cast(h,t);//父类句柄通过$cast转化为子类句柄,一定要用父类句柄的原因在于父类句柄是有对象的,在后面存在test_wr t=new();句柄和对象要都存在。在父类句柄(比如指向子类A)通过$cast函数转化为子类句柄的时候,无论子类句柄之前是否以及指向另一个子类对象B,在转化成功后,子类句柄将与父类句柄都指向子类对象A 。一个句柄只有存在指向对象时,才可以访问其中的成员变量。

       h.def = def;//子类de赋值

  endfunction

  function basic_test copy();

    test_wr t = new();//创建子类对象

       copy_data(t);//子类句柄默认转化成父类句柄

       return t;//返回父类句柄

  endfunction

endclass

module tb;

test_wr wr,h;

initial begin

  wr = new();

  $cast(h,wr.copy());

  $display("wr.def=%0d",wr.def);

  $display("h.def=%0d",h.def);

  h.def = 300;

  $display("wr.def=%0d",wr.def);

  $display("h.def=%0d",h.def);

end

endmodule

2.句柄的声明,传递,使用

声明:先声明句柄,再创建对象,transation tr ;tr=new();transation tr表示句柄的声明。

传递:句柄可以作为一个参数来传递,比如task transmit(transation t)中t的传递实际上传递的是对象的句柄,而不是对象

使用:(1)句柄可以在方法内部完成修改,再由外部完成使用,但是需要注意的是在方法内部进行修改时一定注意句柄的传递方向,也就是限定inout或者ref,如果不给方向,那么默认的时input,也就是在内部完成修改后,方法并不会将句柄传出(2)可以通过句柄调用成员变量或成员方法,"."

句柄与对象:(1)一个对象只可以有一个句柄,但是可以把一个句柄赋给多个对象。(2)在程序执行的时候,可以在任何时刻为句柄创建新的对象,并将新的指针赋给句柄。因此在动态修改句柄的过程中,一定要注意创建对象的个数,如果你只创建了一个对象,那么在后续对句柄的多次操作过程中,实际上新的句柄会覆盖旧的句柄。这种错误尤其是在循环语句中将创建对象的语句放在循环语句之外会出现。(3)另外需要注意的是,句柄与句柄之间是相互独立的:比如创建了a句柄及其对象(x),再将a句柄赋给b句柄,此时a,b均指向同一个对象(x),再对a创建一个新的对象(y),那边b句柄任然指向旧的对象(x)不受a的影响。

transmit和句柄数数组:transmit(t)发送指针t,可以通过transmit结合数组实现对句柄数组的创建。transmit tarry[10];在通过foreach实现对数组的每一个元素的创建,tarry[i]=new();最后再将对象的每一个指针发送transmit(tarry[i]);实际上不结合transmit也可以实现句柄数组的创建,也就是创建一个数组,在对其中每个元素采用xx=new()的方式创建对象

3.变量和方法

变量:类中默认是动态变量,可以通过static使其变为静态变量,类中的静态变量一旦声明后,无论例化了多少个对象,都只能共享一个同名的静态变量.类中声明了静态变量可以通过两种方式进行索引该变量:(1)class a;定义某静态变量 static s  endclass;再在initial中通过new函数例化,t1=new(),则可以通过t1.s或a::s来索引。

方法:class中默认的是动态方法,同样可以采用static让其变为静态方法。静态方法中可以声明并使用动态变量,但是不可以使用类的动态成员变量。这是由于调用静态方法时,可能没有创建具体的对象,因此也没有为类中的动态成员变量开辟空间。使用静态方法时可以使用类的静态变量(其内存在编译阶段就分配好了)。调用静态方法使用"::"符号。

方法可以在类之外定义,但是需要添加关键词extern和"::",p115。

4.封装:创建完类以后,可以再类中创建成员,成员默认时public的(变量和方法也可以),可以通过限定改变这一特性,这一过程也就是类的封装。

local:只有该类可以访问成员,子类也不可以;protect:仅该类或者子类可以访问成员。

5.继承(继承的关键词extends)

super和this,this表示调用的成员是当前的成员,而super表示的是调用的成员是父类的成员。

继承过程中句柄的复制:t(父类)=wr (子类),此时,将子类赋给父类 ,使得父类较小的空间变得和子类一样大,但是,子类取值访问的时候,他任然是优先访问不同于父类的那一步;而对于父类取值访问时,他只能访问自己的较小的那部分,也就是父类的那部分。在前面的t=wr的基础上,如果使用$cast(wr,t)操作后结果是1,因为此时父类和子类的空间是一样的,csat将父类赋给子类的时候,编译器会检查这两者,发现两者均是一样空间,会返回1。但是,如果上述条件从t=wr变为t=new()和wr=new(),也就是父类是父类,子类是子类 (父类仅仅是小三角形的部分,子类是大三角形的部分),再次采用$cast(wr,t)时会返回0,因为两者指代的空间不一致。仅仅子类赋给父类的操作t=wr能实现,编译器不支持父类赋给子类的操作wr=t;父类赋给子类的操作仅仅在$cast操作允许(或者使用虚方法),并且要满足两者空间是一致时才会返回1,$cast转换后,限制的区域也可以被访问了。

父类的变量和方法子类都会继承,但是子类中和父类的“同名同参同返回值”的方法,调用的时候如果不加super,会覆盖掉它所继承的父类的“同名同参同返回值”的方法,这里说的方法的继承应该是方法内部内容上的继承,也就是说,子类的“同名同参同返回值”的方法里面的内容并不会继承父类的“同名同参同返回值”的方法的内容,需要在定义子类方法的时候,在第一行调用super“同名同参同返回值”的父类的方法,才会继承它的内容。子类继承父类的时候会把父类的所有成员变量和方法都继承,但是继承来的部分都放在小三角形里那部分属于父类的。如果要访问父类里的成员变量和方法需要使用super. (前提是父类定义的时候声明的不是local)。如果子类里又声明了一个和父类同名的变量crc,那么子类声明的变量放置在属于子类部分里,不使用super则访问的是子类的crc。

子类与父类的对变量与方法的调用顺序准则: 在继承过程,变量名尽量不要重名,方法可以重名;如果要子类继承父类, 无论是new函数还是其他函数,建议统一加上super;new函数默认会继承父类,但是其他函数不会默认先调用父类,仅仅在有super时才会调用父类,并且对于new函数来说,当父类中new函数中存在变量时,子类如果不采用super的话,程序会报错;即使子类和父类的new函数不一样,比如父类是fuction new(),子类是fuction new(int val)在继承过程中也会默认先调用父类,在经过子类。

类的重写,子类继承父类后,可以在父类的基础上进行修改。

this和super的区别:this所查找的范围是当前类以及所继承的父类(及以上层次)即该类声明或继承的所有成员,并且按照变量就近索引的原则(当指向的成员在当前类中不存在的时候,就会自当前子类往上逐级查找),可以在非静态的方法/约束/内嵌约束/类中定义的covergroup中使用;super 查找范围限定于该子类的父类(及以上层次),适用于访问某些被子类覆盖的成员(变量或方法)。这两种关 键词的查找方位均限定于该类的继承层次,与外部、引入变量或方法无关。

6.多态(多态的核心是虚方法,虚方法可以达到不进行句柄转化的情况下实现父类句柄访问子类对象)

虚方法使用的背景:(1)子类转换为父类是可行的,但父类转换为子类需要条件,一方面要求父类的对象指向之类的对象,另一方面要求在完成上面要求的前提下使用$cast函数 (2)子类的句柄指向子类的对象时,调用子类的句柄将会使用子类的方法,但是,即使将子类的对象给到父类,通过父类句柄调用方法时,同样不会使用子类的对象,也就是只能调用到父类的方法。在上面两个条件的基础上就会出现如何使在将子类的对象赋给父类的对象的基础上如何用父类的句柄调用到子类的方法,也就是采用虚方法virtual

虚方法的使用:(1)虚方法的使用准则是在父类中的方法或者函数前加入virtual,并且建议在底层的父类前加入,父类加入后子类不需要加入 (2)虚方法的执行逻辑是,在将子类的对象给到父类的前提下,调用父类中的任务时候,如果出现父类前面有virtual时,他会执行子类中的任务,仅仅当子类中不存在该任务时才会调用父类的;假设子类的任务前加上了一个super,那么此时实际需要先调用父类的方法再调用子类的方法?(3)虚方法的一个应用:A是父类,B继承A,C继承B,如果A中没有加virtual,那么再调用A的方法时,实际上就是调用A 的方法;如果A中加了virtual,那么调用A方法时执行的是B的方法还是C的方法?;如果B中加了virtual,那么再调用B的方法时实际上执行的时C的方法,并不会影响到A。

使用多态需要注意:(1)子类成员变量可声明同名的变量或方法(类型可不相同)去覆盖父类成员变量,此时,父类和子类的成员方法不使用多态进行动态绑定。但是,如果添加了virtual符号,子类成员方法与父类成员方法需要“三者合一”(方法名、参数类型和数目、 有否返回值)。(2)另外,需要注意的一点是,虚方法解决的问题是方法,是无法解决变量问题的,也就是如果子类继承父类,父类与子类中有同名的变量,又想通过父类调用子类的变量,这种操作是无法实现的,并且virtual只能针对方法或者函数,而不能针对变量,此时唯一的办法是父类的句柄转换为子类的句柄($cast函数)(3)除了new不可以添加virtual,父类的其余方法均可以添加;成员变量同样不可以添加。

7.关于类的一些其他点

虚类:虚类主要是作为基类的模板,不可以实例化,但是可以作为父类被继承,虚类的构造函数只能在子类中的构造函数的调用过程中使用,虚类中内部可以定义虚方法(纯虚方法),这种虚方法不需要具体实现,需要用pure virtual修饰;在扩展过程,必须对纯虚方法进行具体实现的重写,重写后才可以在普通类中进行使用,如果其中的任何纯虚方法没有具体的实现,那么子类也必须是虚类

参数化类:(1)像模块或者接口一样例化,在参数化的类中直接传入值;(2)在参数化的类中传入类型:对任意参数类型进行实例化:class mailbox #(type T=int)***mailbox #(real) mb,首先,在第一个mailbox中可以定义多个参数或者不同类型的参数,参数化变量在声明时应当指定参数类型,如果第二个mailbox中没有指定则默认int,第一个mailbox没有指定时,也就是没有默认类型,那么第二个mailbox就必须指定;(3)参数化类的扩展:A参数化类可以扩展与B参数化类,B如果写具体的类型,那么A将会扩展为该类;(4)通用类和真实参数值的一个组合被称为一个特例,为了在多个特例间共享静态成员变量,这些变量必须放置在一个非参数化的基类中(???)(5)typedef stack#(Vfour) Stack4;Stack4 s1 s2;避免重复

8.包

packege:: monitor mon=new();在顶层的module top中使用import pkg_name::可以表示引用的域如果不在当前module top域中,会自动的索引pkg_name库中的类,但是需要注意的是如果有多个import时,要保证各个import输入的库中的类没有重名,否则在索引时会导致冲突;包是将软件封装到不同的命名空间中,以此实现隔离,而库既可以容纳硬件空间也可以容纳软件空间。包中编译的类型可以导入其他的域,这些域包括module/interface/program/class/package,也就是软件域和硬件域。package种不可以定义硬件相关的内容,也就是module/interface/program。

9.类的一些比较

与结构体的联系和差别:两者都可以定义数据成员,类在创建时需要通过new函数或者构造函数创建对象(开辟空间),而结构体在变量声明时就以及创建空间;类中可以声明方法,结构体中不能;结构体从本质上来说任然是一种数据结构,但是类既包含了数据成员也包含了处理这些数据的方法;new()和new[];

与模块的联系和差别:模块一开始就要确定是否例化,而类可以根据需求随时开辟空间创建对象;模块中的变量方法默认是静态的,而类中的变量和方法默认是动态的;模块中的变量和成员是对外开放的,而类中的则可以根据需求来封装,使其不在对外开发;模块没有继承性(仅仅可以复制和再次基础上修改而不能在原有基础上扩展),而类由继承性

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值