SystemVerilog验证 测试平台编写指南 第八章 OOP面向对象编程的高级技巧指南

8.1 继承
为总线事务创建一个可以注入错误并且带有可变延时的复杂类。
方法1:使用合成(composition),即在类中例化另一种类型的类。有时候很难将功能分成独立部分。如果使用合成,则需要为正确和错误事务分别创建不同的类,正确类的测试平台需要重写以处理错误类的对象。
方法2:使用扩展类,当需要增加事务,而对现有的测试代码修改越少越好,例如增加错误注入功能。
扩展类和类合成的区别:
扩展类解决,增加新事务;使用类合成时,大量修改代码麻烦。
如何使用:
扩展类共享基类的变量和子程序。
1)基本类中的方法,需标记为virtual,这样扩展类中才可以重新定义。扩展类中函数,和基类中函数名一样时,通过super.函数名,调用基类中的函数。System Verilog中不允许super.super.new方式进行多层调用。
2)如果基类构造函数new()有参数,那么扩展类,必须有一个构造函数,并在构造函数的第一行调用基类的构造函数。
例8.3 扩展类中带有参数的构造函数

class Basel;
	int var;
	function new(input int var);//带有参数的构造函数
		this.var=var;
	endfunction
endclass

class Extended extends Basel;
	function new(input int var); //需要参数
		super.new(var);//必须是new构造函数的第一行
		//构造函数的其它行为
	endfunction
endclass

3)OOP规则指出:基类的句柄,也可以指向扩展类的对象。

8.2 蓝图(Blueprint)模式
1)背景:一个简单的发生器,通过信箱将数据传递给驱动器。
例8.5 发生器类
//使用Transaction对象的发生器类
//第一个尝试…只有有限的功能

class Generator;
	mailbox gen2drv;
	Transaction tr;

	function new(input mailbox gen2drv);
		this.gen2drv=gen2drv;//this->类一级变量
	endfunction

	task run;
		forever begin
			tr=new();//创建事务
			assert(tr.randomize);//随机化
			gen2drv.put(tr);//送入驱动器
		end
	endtask

endclass

存在问题:该例在循环内部而非循环外部创建事务对象,避免常见的测试平台的错误。
New()放在循环外部,错误原因是,mailbox中放入的是句柄,而不能是对象,所有的句柄都指向同一个对象。(1)任务Run创建了一个事物并立即随机化,意味着事务使用了默认的所有约束。要修改,必须要修改transaction类。(2)无法使用扩展

解决办法:将tr的创建和初始化分开,使用蓝图模式。

另一个问题:如果简单的把创建和初始化分开,而放在循环外部,而避免测试平台错误(P200),如何解决?蓝图模式如何解决

2)蓝图模式概念
首先构建一个对象蓝图(金属模),然后修改它的约束,甚至可以用扩展对象替换它,随机化这个蓝图时,就得到想赋予的随机值;然后复制这个对象,将copy发给下游。
蓝图:是一个钩子(hook),允许你改变发生器类的行为而无需修改其类代码。蓝图对象在一个地方构建(new()),在另一个地方(任务run)使用。
例8.6 使用蓝图模式的发生器类
class Generator;
mailbox gen2drv;
Transaction blueprint;

	function new(input mailbox gen2drv);
		this.gen2drv=gen2drv;
		blueprint=new();
	endfunction

	task run;
		Transaction tr;
		forever begin
			assert(blueprint.randomize);//随机化
			tr=blueprint.copy();
			gen2drv.put(tr);//送入驱动器
		end
	endtask

endclass

3)P200与P221相对比分析:重要
蓝图模式,也就比new()在循环外地generator多了一个copy函数。
问题(1)蓝图模式,new()在循环外,也只有一个对象,而mailbox中放入的只能是句柄,如何解决常见的平台错误?
因为copy,是对象的复制,而不是句柄的复制。这样蓝图模式只有一个句柄,但是随机化后,copy,相当于再循环中创建了许多对象。而测试平台常见错误的本质是,只创建了一个对象。这样就避免了问题。
问题(2)蓝图模式下,因为只有一个ID号,那么任务run循环中,下发了许多数据,这些只有一个ID号了?
因为copy是对象的复制,所以在copy中ID号也会增加。下发的每个数据,都有各自的ID号。
4)使用扩展的transaction
为了注入错误,需要将蓝图对象transaction变成Badtransaction(改变蓝图)。必须在环境的创建和运行阶段之间完成这个操作。注意:所有的badTr引用都在这一个文件中,这样就不需要改变environment类或generator类。

Env.build();
Begin
Badtr bad = new();
Env.gen.blueprint = bad;
End
Env.run
目的是:将一个对象取代另一个对象。New()后都是对象了,将对象赋值给对象,这是什么写法?不是复制呀?复制本质是将一个句柄指向一个对象。
解释:上述是句柄的复制,将扩展类句柄bad赋值给基类句柄blueprint,这样基类句柄指向扩展类对象,后面的代码调用的时候,就直接指向扩展类bad了,改变了蓝图。
Env.new()和nev.build()区别
Env.new()仅仅new()函数
nev.build()是将各个模块new(),并传达一些参数,通过这些参数将环境的各个模块,连接起来。
8.3 类型向下转换(downcasting)和虚方法

8.3.1 $cast作类型向下转换
背景:基类句柄可以指向扩展类对象,不需要额外的代码;扩展类句柄指向基类对象,一般情况下会出错,但有时候是可以的,前提是基类句柄指向了它的一个扩展类对象。
作用:扩展类句柄指向基类对象时,使用 $cast()函数。在非法的情况下,不会编译报错,会返回了一个0。
$cast做任务使用时,systemverilog会在运行时,检查源对象类型和目的对象类型不匹配,会报错
$cast做函数使用时 ,仍做类型检查,在不匹配时,不会报错,函数返回0
1)基类句柄指向扩展类对象——出现情况:修改蓝图,不改过多代码,增加功能
例8.12 将一个扩展类的句柄拷贝成基类句柄

Transaction tr;   //基类句柄
BadTr bad;        //扩展类句柄
bad = new();     //构建BadTr 扩展对象
tr = bad;         //基类句柄指向扩展类对象
$display(tr.src);//显示基类对象的变量成员
tr.display;        //调用的是扩展类的方法

2)扩展类句柄指向基类对象——出现情况:基类virtual 方法copy函数,它的继承类中copy函数
将基类句柄赋值给扩展类句柄,使扩展类句柄指向基类对象,一般编译器会出错,不能运行,所以非常小心;只有基类句柄指向扩展类对象时,再将扩展类句柄指向基类对象时,不出错。为了检测基类句柄是否指向了扩展对象,并且不让编译器报错,可以使用$cast()函数检测。
当把扩展类句柄指向基类对象时,发生什么?
例8.13 将一个基类句柄拷贝到一个扩展类句柄

 tr= new();            //创建一个基类 对象
 bad = tr;               //扩展类句柄指向基类句柄 ERROR:这一行不会被编译
 $display(bad.bad_crc);//基类对象不存在bad_crc成员

上述会发生错误,编译不会被通过。因为有些属性在基类中不存在;但是扩展类句柄指向基类句柄不总是非法的(见下面代码,是可以的),当基类句柄指向一个扩展类对象时是允许的。
例8.14 使用$cast拷贝句柄

Transcation tr; //基类句柄
BadTr bad,bad2; //扩展类句柄
Bad= new();//构建BadTr扩展对象
Tr = bad;   //基类句柄指向扩展类对象
 
//检查对象类型并拷贝。如果类型失配则在仿真时报错
//如果成功,bad2就指向tr所引用的对象
$cast(bad2,tr);      //扩展类句柄指向基类对象

//检查类型失配,如果类型失配,在仿真时也不会输出错误信息
if(!$cast(bad2,tr);
	$display(“cannot assign tr to bad2”);
$display(bad2.bad_crc);//原始对象中存在bad_crc成员

句柄类型和对象类型差异(书中翻译的不准,type of handdle 和 object)
个人理解:
Transaction tr; 句柄tr类型是transaction
句柄类型:关键字
对象类型:类中成员的类型差异
8.3.2 虚方法和多态
多态:多个程序使用一个共同的名字的现象(polymorphism)。
多态解决问题:计算机建构面临的一个问题。让物理内存很小的情况下,让处理器能够对很大的地址空间寻址。针对这个问题引入了虚拟内存。
虚拟方法继承劣势:
基类使用了虚拟方法,扩展类也必须使用相同的“签名”,扩展类中虚拟子程序不能增加或删除参数,这意味着必须提前做好规划。
8.5 对象的复制
1)因为是virtual 函数,扩展类中copy方法也必须是transaction型的,与基类一致。但是要copy的是badtr类型的,所以要new一个bad
例8.20 带有copy 虚函数的事物基类

Class transaction ;
	Rand bit[31:0] src,dst,data[8]; //变量
	Bit[31:0] crc;

	Virtual function transaction copy ();
		Copy   = new();
		Copy.src = s rc;
		Copy.dst = dst;
		Copy.data = data;
		Copy.crc  = crc;
	Endfunction
Endclass

例8.21 带有copy虚函数的扩展事务类

Calss badtr extends transaction
	Rand bit bad_crc;
	Virtual function badtr copy(); //错误
	Virtual function transaction copy();
		Badtr bad;
		Bad = new();
		Bad.src = src; //复制数据域
		bad.dst = dst;
		bad.data = data;
		bad.crc = crc;
		Bad.bad_crc = bad_crc;
		Return bad;
	endfunction
endclass

2)优化途径一:创建一个独立的函数copy_data,这样每个类只负责copy其局部变量,即扩展类中的copy函数用super.copy_data(tr),代替了基类中变量的复制。代码的重用性提高。
例8.23 使用copy_datd函数的扩展事务类
$cast(bad,tr); //扩展类句柄指向基类句柄,基类句柄类型转换成扩展类使用的情况: 因为virtual 函数,在继承中,虚拟函数必须和基类中名称和参数也一致。这样扩展类中copy_data函数参数仍然是transaction类型的tr,这样出现了参数是基类句柄,但是copy_data函数内要作的确是扩展类的成员,就要将基类句柄参数赋值给扩展类句柄,要将扩展类badtr类型的数据返回,所以必须用 $cast(bad,tr)。
优化途径二:前面的copy子程序都会创建一个新对象,改进的一种方法就是指定复制对象的存放地址
例8.24 使用copy函数的事务基类

class Transaction;
	Virtual function transaction copy(transaction to =null);
		if(to == null)
			copy = new(); //创建新对象
		else
			copy = to;//或者使用现有对象
		copy_data(copy);
endfunction

8.6 抽象类和纯虚方法
背景:验证的目标之一是创建多个项目共享的代码。
目的:systemverilog 有两种方法创建共享的基类:抽象类和纯虚方法
Virtual class (抽象类):可以被扩展但是不能被直接例化。
Pure virtual function(纯虚方法):没有实体的方法原型,相当于一个声明。
由抽象类扩展而来的类,只有在所以的虚拟方法都有实体的时候才能被例化,纯虚方法只能在抽象类中定义。
抽象类中,纯虚方法是没实体的,非纯虚方法最好也不写实体。
8.7 回调
背景:测试平台目的:创建一个不做任何修改就能在所有测试中使用的验证环境。要做到这点的关键是测试平台使用钩子,(什么是钩子?)钩子作用,在不修改原始类的情况下注入新的代码。采用virtual 方法,也可以在扩展类中覆盖基类方法,但是需要重复原方法的所有代码,并且它的修改将传播到它的所有扩展类中。
作用:回调就是一个钩子,在不修改原始类的情况下注入新的代码。
实现:回调任务在顶层中创建,在最低级即驱动器中调用。这样驱动器不需要知道测试的任何信息,它只需要使用一个可以在测试中扩展的通用类。
1) 使用回调注入干扰
回调的一个常见用法就是注入干扰,例如引入一个错误或者延迟。下面测试平台使用回调对象,随机地丢弃数据包。
扩展类是如何作用的?在扩展的回调类中注入错误,如何在驱动器中作用的?
关键是数据队列的作用,驱动器中使用了,回调基类的数据队列
回调基类是抽象类,在扩展的回调类中加入错误注入,而drive驱动类中,是回调基类的数据队列,在环境中将扩展类句柄让入驱动器类,回调基类的数据队列中。
例8.13 使用回调进行错误注入的测试create error injection callback

begin
	Driver_cbs_drop dcd = new();//创建错误注入的回调任务
	env.drv.cbs.push_back(dcd); // Put into driver放入驱动器队列
end

与前面扩展类作用的差异?
前面代码,要使扩展类中增加代码,需要使基类句柄指向扩展类句柄。
驱动器类:
下面的代码如何解释
回调也可以想scoreboard 发送数据或收集功能覆盖率。
优点:你可能想过将scoreboard和功能覆盖数据组置于一个事物处理器中,通过邮箱连接到测试平台中,这是一种笨拙的方法,原因如下:测试平台组件几乎都是被动和异步的,组件只有在测试平台给他数据的时候才被唤醒,而且不会主动地向下游事物处理器传递信息。麻烦:1)这样一个需要同时监视多个邮箱的事物处理器复杂了;2)你可能在多个地方采集数据,但是事物处理器设计用来处理单个数据源回调

你可能感兴趣的:(SV)