继承允许从一个现存的类得到一个新的类并共享其变量和子程序;原始类被称为基类或者超类,而新类因为扩展了基类的功能,被称为扩展类。(继承通过增加新的特性提供了可重用性,并且不需要修改基类)
例如:一个发生器创建了一个事务,随机化其值,然后将其发送到驱动器
该事务基类含有变量和子程序,变量包括源地址、目的地址、八个数据字和校验错误CRC变量,子程序包括用于显示内容和计算CRC的子程序。其中函数被标注为virtual,这样就可以在需要的时候重新定义(这一点适用于所有的任务和函数,除了new函数,因为new函数在对象创建时调用,所以无法扩展)。
Transaction类的扩展
class BadTr extends Transaction;//BadTr类可以直接访问原始类和本身所有的变量
rand bit bad_crc;
virtual function void calc_crc;
super.calc_crc();//扩展类的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();//但是注意不允许super.super.new的方式进行多级调用
endfunction
endclass:BadTr
如果你的基类构造函数有参数,那么扩展类必须有一个构造函数而且必须在其构造函数的第一行调用基类的构造函数
class Base1;
int var;
function new(input int var);//带有参数的构造函数
this.var=var;
endfunction
endclass
class Extended extends Base1;
function new(input int var);//需要参数
super.new(var);必须是new函数的第一行
endfunction
endclass
驱动类:从发生器接受事务信息,然后将他们输送至DUT
class Driver;
mailbox gen2drv;//线程间使用信箱传递信息
function new(input mailbox gen2drv);
this.gen2drv=gen2drv;
endfunction
task main;
Transaction tr;//定义句柄
forever begin
gen2drv.get(tr);//从发生器获得transaction
tr.calc_crc();//处理transaction,sv会查看存储在tr的对象类型
@ifc.cb.src=tr.src;//发送transaction
...
end
endtask
endclass
蓝图模式:如果改变蓝图对象,发生器就会创建一个不同类型的对象,在使用时需要创建一个复制方法来复制蓝图以便传送。
执行的三个状态:创建(build)、运行(run)、收尾(wrap-up)
environment类例化测试平台所有元素,并且执行者三个阶段
class Environment;
Generator gen;
Driver drv;
mailbox gen2drv;
function void build();
gen2drv=new();
gen=new(gen2drv);
drv=new(gen2drv);
endfunction
task run();
fork
gen.run();
drv.run();
join_none
endtask
task wrap_up();
endtask
endclass
使用environment的简单测试程序
program automatic test;
Environment env;
initial
begin
env=new();
env.build();
env.run();
env.wrap_up();
end
endprogram
将蓝图对象从Transaction对象变为BadTr对象,必须在环境的创建和运行阶段完成这个操作,这样顶层测试平台将运行环境的每个阶段并且改变蓝图。
如果对数据做进一步的约束(如果在扩展类中定义了一个约束,并且扩展后的约束名和基类中的约束名相同,那么扩展类的约束会替代基类的约束。
类型向下转换(类型转换):一个指向基类的指针转换成一个指向派生类的指针
将一个扩展类的句柄拷贝成基类句柄
Transaction tr;
BadTr bad;
bad=new();//构建badTr扩展对象
tr=bad;//基类句柄指向扩展对象
$display(tr.src);//显示基类对象的变量成员
tr.display;//调用BadTr::display
当一个类被扩展时,所有的基类变量和方法都将被继承,所以整数变量src存在于扩展对象中(但是如果做相反的操作就会失败,即将一个基类对象拷贝到一个扩展类的句柄中;当基类句柄确实指向一个派生类对象是允许的,$cast子程序会检查句柄所指向的对象类型,一旦源对象和目的对象都是同一类型或者目的类的扩展类)
将 $cast作为一个任务来使用时,sv会检查源对象类型,并输出错误报告;将 $cast作为函数使用时,sv仍做类型检查,但不输出错误报告(类型不兼容,返回0,类型兼容返回非0值)
当需要决定调用哪个虚方法时,sv会根据对象的类型,而非句柄的类型来决定调用什么方法(但是如果函数不是使用virtual修饰符,sv就会根据句柄的类型,而非对象的类型)
下例使用合成创建一个以太帧
1、增加了一个层次,导致层次名的增长
2、当new函数被调用的时候不知道是否创建一个vlan对象
创建不分层的以太帧类
对象的复制
带copy虚函数的事务基类
class Transaction;
rand bit[31:0] src,dst,data[8];
bit [31:0]crc;
virtual function Transaction copy();
copy=new();
copy.src=src;
copy.dst=dst;
copy.data=data;
copy.crc=crc;
endfunction
endclass
当你扩展Transaction类来创建BadTr类的时候,copy函数仍需要返回一个Transaction对象。
另一种优化方式将copy函数拆分成两个,创建一个独立的函数copy_data;这样这个类只负责拷贝其局部变量
当你想重用一个现有对象而不是分配一个新对象的时候
含有copy函数的扩展事务类
sv中,允许两种构造方法创建一个可以共享的基类;第一种是抽象类,可以被扩展但不能被直接实例化的类,使用“virtual”关键词进行定义;第二种纯虚(pure virtual)方法,一个没有实体的方法原型。
可以声明BaseTr类型的句柄,但是不能创建该类型的对象(需要首先扩展该类并对所有的纯虚方法提供具体实现)
记分板的设计取决于DUT(Design under test),对于一个处理原子事务的DUT(处理包信息),其计分板需要包含一个将输入的事务转换成期望值的传输函数、用来保存这些值的内存空间以及一个进行比较的子程序(对期望值和实际值进行比较)
下例中:第一个方法用来保存一个期望的事务,第二个方法找出与测试平台接受到的实际事务相匹配的期望事务。
参数化的类(用于处理多种数据类型)
class IntStack;
local int stack[100];
local int top;
function void push(input int i);//从顶端压栈
stack[++top]=i;
endfunction:push
function int pop();
return stack[top--];
endfunction
endclass:IntStack
但是这个堆栈只能用于操作整数类型(如果为实数类型做一个堆栈,需要复制该类,然后将数据类型由整型int转换为实型real),可以为类增加一个数据类型参数,并在声明类句柄的时候指定类型。
class stack #(type T=int);
local T stack[100];
local int top;
function void push(input T i);
stack[++top]=i;
endfunction:push
function T pop();
return stack[top--];
endfunction
endclass:stack
使用参数化的类创建一个实数型的堆栈并读写数据
initial
begin
stack #(real) rstack;
rstack=new();
for(int i=0;i<5;i++)
rstack.push(i*2.0);
for(int i=0;i<5;i++)
$display("%f",rstack.pop());
end