数字IC验证23823--类

OOP的概念要素

  • Class类:基本模块包含成员变量和方法。在Verilog中module也可以包含变量和方法,只不过它是硬件盒子,而class是软件盒子。
  • Ojbect对象:类的实例。Verilog中module也可以例化,这是’硬件′例化,在SV中可以使用class来例化,这是`软件′例化
  • Handle句柄(指针)∶用来指向对象的指针。在Verilog中,可以通过层次化的索引来找到结构中的设计实例,而在SV的对象索引时,需要通过句柄来索引对象的变量和方法。
  • Property属性(变量):在类中声明的存储数据的变量。在Verilog中,它可以是wire或者reg类型。
  • Method方法:类中可以使用task或者function来定义方法以便处理自身或者外部传入的数据。在Verilog中可以在module中定义task/function,也可以使用initial/always处理数据

句柄的传递

  • 在区分了类(抽象)和对象(具体)之后,初学者还需要区分对象(存储空间)和只似关备1’而指尚该空间的句柄可以有之后,该对象的空间位置不会更改,而指向该空间的句柄可以有多个。
Transaction t1, t2 ; //声明句柄t1,t2
t1 = new () ; //例化对象,将其句柄赋予t1
t2 = t1; //将t1的值赋予t2,即t1和t2指向同一个对象
t1 = new () ; //例化第二个对象,并将其句柄赋予t1

在这里插入图片描述

静态变量

  • 与硬件域例如module, interface不同的是,在class中声明的变量其默认类型为动态变量,即其生命周期在仿真开始后的某时间点开始到某时间点结束。具体来讲,其声明周期始于对象创建,终于对象销毁。
  • 那么如果使用关键字static来声明class内的变量时,则其为静态变量。根据之前课程对变量声明周期的描述,静态变量的生命开始于编译阶段,贯穿于整个仿真阶段。
  • 如果在类中声明了静态变量,那么可以直接引用该变量class::var,或者通过例化对象引用object.var。
  • 类中的静态变量声明以后,无论例化多少个对象,只可以共享一个同名静态变量,因此类的静态变量在使用时需要注意共享资源的保护。

静态方法

  • 类似于静态变量,在class中定义的方法默认类型是动态方法,而我们也可以通过关键词static修改其类型为静态方法。
  • 静态方法内可以声明并使用动态变量,但是不能使用类的动态成员变量。原因是因为在调用静态方法时,可能并没有创建具体对象,也因此没有为动态成员变量开辟空间,因此在静态方法中使用类的动态成员变量是禁止的,可能会造成内存泄漏,但是静态方法可以使用类的静态变量,因为静态方法同静态变量一样在编译阶段就已经为其分配好了内存空间。

类的成员

  • 类是成员变量和成员方法的载体,之所以称之为自洽体,是因为其变量和方法应符合聚拢原则,即一个类的功能应该尽可能简单,不应当承担过多的职责,更不应该承担不符合它的职责,这在设计模式中称之为单一职责原则。
  • 类作为载体,也具备了天生的闭合属性,即将其属性和方法封装在内部,不会直接将成员变量暴露给外部,通过protected和local关键词来设置成员变量和方法的外部访问权限。所以封装属性在设计模式中称之为开放封闭原则。
  • 如果没有指明访问类型,那么成员的默认类型是public,子类和外部均可以访问成员。
  • 如果指明了访问类型是protected,那么只有该类或者子类可以访问成员,而外部无法访问。
  • 如果指明了访问类型是local,那么只有该类可以访问成员,子类和外部均无法访问。
  • 访问类型的设定是为了更好地封装类,尤其是要发布供他人使用的软件包(商业软件),但对于初学者以及应用范围较窄的验证环境,可以使用默认的访问类型,以便于在类的内部或者外部更方便地修改成员变量或者调用成员方法。

this的使用

  • 当你在一个类中的底层作用域想引用类一级的变量,用this指向当前类的一级的变量
class test;
     int count = 0;
    static function new(int count);
      this.count = count;  //类变量 count = 局部变量 count
    endfunction
endclass

类的继承

class black_cat extends cat;//extends表示继承,黑猫继承了白猫
	function new();
		this.color=BLACK;//黑猫自己没有color,于是去副类,也就是继承了的cat中,继承了cat的color等所有成员变量方法
	endfunction
endclass
 
class white_cat extends cat;//白猫继承了cat
	function new();
		this.color=WHITE;
	endfunction
endclass
 
black_cat bk;//黑猫句柄
white_cat wt;//白猫句柄
initial begin
	bk=new();//创建一只黑猫
	wt=new();//创建一只白猫
	bk.set_good(1);//黑猫句柄调用set_good函数给定1,1赋给is_good
	wt.set_good(1);
end

如果要将数据发送到DUT,那么需要有以下的基本元素和数据理方法,我们将它们封装到Transaction类中。

class Transaction ;
rand bit [31:0] src, dst, data[8] ;//随机成员变量bit [31:0]crc; ll二次处理后的成员数据
virtual function void calc_crc () ;
	crc = src ^ dst ^ data. xor;
endfunction
virtual function void display (input string prefix="");
	$display ( "%sTr: src=%h, dst=h, crc=%h",prefix, src, dst, crc) ;
endfunction
endclass

如果我们为了测试DUT的稳定性,需要加入一些错误的数据来测试DUT的反馈,但我们又想尽量复用原有的验证环境(也包括已经定义好的类),那么我们就可以考虑使用继承的方式来新创建一个类BadTr。

class BadTr extends Transaction ;
rand bit bad_crc;
virtual function void calc_crc();
	super.calc_crc() ; 
	if (bad_crc) crc = ~crc; 
endfunction
virtual function void display (input string prefix="") ;
	$write ( "%sBadTr : bad_crc=%b, ", prefix, bad_crc) ;
	super.display ();
endfunction
endclass : BadTr
  • BadTr是Transaction的子类,Transaction是BadTr的父类.
  • BadTr重新定义了函数calc_crc()和display(),而在其内部过super来索引父类的同名函数。
    在这里插入图片描述
  • 就继承来看,类的继承包括了继承父类的成员变量和成员方法。
  • 子类在定义new丞数时,应该首先调用父类的new函数即super.new()。如果父类的new函数没有参数,子类也可以省略该调用,而系统会在编译时自动添加super.new()。
  • 从对象创建时初始化的顺序来看,用户应该注意有如下的规则:
    • 子类的实例对象在初始化时首先会调用父类的构造函数。
    • 当父类构造函数完成时,会将子类实例对象中各个成员变量按照它们定义时显式的默认值初始化,如果没有默认值则不被初始化。
    • 在成员变量默认值赋予后(声明的同时即赋值),才会最后进入用户定义的new函数中执行剩余的初始化代码。

成员覆盖

  • 在父类和子类里,可以定义相同名称的成员变量和方法(形式参数和返回类型也应该相同),而在引用时,也将按照句柄类型来确定作用域。
  • 如果没有this或super来指定作用域
    • 首先看变量是否是函数内部定义的局部变量
    • 其次看变量是否是当前类定义的成员变量
    • 最后再看变量是否是父类或者更底层类的变
class basic_test;
	int def = 100;
	int fin;
	
	task test(stm_ini ini);
		$display("basic_test::test");
	endtask
	
	function new(int val);
		...
	endfunction

endclass

class test_wr extends basic_test;
	int def = 200;
	function new();
		super.new(def);
		$display("test_wr::new");
		$display("test_wr::super.def = %0d", super.def);	// 父类 def = 100
		$display("test_wr::this.def = %0d", this.def);		// 子类 def = 200
	endfunction

endclass

下面代码的wr.def和t.def分别是多少?

module tb;
	...
	basic_test t;
	test_wr wr;
	
	initial	begin
		wr = new();
		t = wr;
		$display("wr.def = %0d", wr.def);
		$display("t.def = %0d", t.def);
	end
endmodule

  • 子类句柄赋值给父类之后,被赋值的父类句柄只能指向整个子类中属于父类的部分,这样对访问来说是安全,相当于缩小了访问范围,所以t.def = 100,wr.def = 200;
  • 子类句柄可以赋值给父类,反之父类的句柄不能赋值给子类;
  • 若wr = t,编译报错,相当于扩大了访问范围;
  • 如果想将父类句柄(该父类句柄要指向子类的对象)赋值给子类,用Scast();【没遇到过这样的代码,以后补充
    【遇到了】
  • 若子类中没有同名的def变量,w.def优先在子类成员中找def,找不到,扩大范围,找到父类中的def,wr.def = 100;

类和结构体的异同

  • 都可以定义数据成员
  • 类的变量在声明后需要构造才会构建实体对象object,new();如果没有new,SV在编译时会帮我们创建空的new函数
    类可以声明方法function和task。结构体不可以
  • 结构体是一种数据结构,类包含了数据成员以及针对这些成员操作的办法

类和module的异同

  • 都可以作为封闭的容器来定义和存储
  • 从例化看,module必须在仿真一开始就确定是否应该被例化,不需要运行任何时间,整个结构就会被确定下来;类在仿真的任何时刻都可以开辟内存创建新的对象。(软件世界和硬件世界)
  • 从封装看,module内的变量方法对外部开放,类可以选择要不要开放(上面三种限定词)
  • 从继承看,module没有任何继承性,无法在原有module的基础上进行新的module功能的扩展。类可以一直嵌套
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值