之前定义过的类Packet,可以进一步扩展构成一个它的子类LinkedPacket。类Packet的定义如下:
class Packet ;//class定义类 类名 packet
//类 packet的成员
//数据或类属性
bit [3:0] command; bit [40:0] address; bit [4:0] master_id;
integer time_requested ;
integer time_ issued;
integer status;
typedef enum { ERR_OVERFLOW= 10,ERR_UNDERFLOW = 1123} PCKT_TYPE;
const integer buffer_size = 100;
const integer header_size;
//类 packet的成员方法new ();clean() ;issue_request; current_status
//初始化
function new ();
command = 4' d0; address = 41'b0; master_id = 5'bx; header_size = 10;
endfunction
//方法
//公共通道入口
task clean() ;
command = 0; address = 0; master_id = 5'bx ;
endtask
task issue_request( int delay ) ;
//向总线发送请求
endtask
function integer current_status ( ;
current_status = status;
endfunction
endclass
通过extends,子类LinkedPacket继承于其父类Packet,包括继承其所有的成员(变量/方法)。
class LinkedPacket extends Packet;//父类名Packet,子类名LinkedPacket
//在子类继承父类的成员变量和方法基础上,定义新的成员next和成员函数get_next()
LinkedPacket next ;
function LinkedPacket get_next();
get_next = next;
endfunction
endclass
子类可以继承父类的成员,所以LinkedPacket对象中也包含Packet类的成员。由此,父类句柄可以指向子类的对象。
LinkedPacket lp = new ;
Packet p = lp;
注意: 父类句柄如果指向子类对象 ,那么将无法访问子类的成员变量(这是由于父类句柄可访问范围所限制的)
注意: 虽然在子类中可以新定义子类成员 ,但是如果子类中声明了与父类同名的成员(变量/方法),那么子类对其同名成员的访问都将指向子类,而父类的成员将被隐藏。
示例: 下面的代码经过简化,父类和子类只保留了各自同名成员变量i和成员变量get
//创建父类Packet
class Packet;
integer i = 1;
function integer get() ;
get = i ;
endfunction
endclass
//创建子类LinkedPacket
class LinkedPacket extends Packet;
integer i = 2;
function integer get() ;
get = -i ;
endfunction
endclass
tb.sv文件内容:
`include "class_test.sv"
module tb;
initial begin
LinkedPacket lp = new;
Packet p = lp; //将子类句柄lp赋值给父类句柄p
//虽然子类句柄和父类句柄此时都指向同一个子类,但是
//由于句柄类型,限制了其访问范围
//所以通过父类句柄p,可以访问的成员变量和成员方法都是父类范围的
integer i,j;
j = p.i; // j = 1,不是2
$display("j is %d\n",j);
i = p.get() ; //i = 1,不是-1或者-2
$display("i is %d\n",i);
end
endmodule
虽然子类句柄和父类句柄此时都指向同一个子类,但是由于句柄类型,限制了其访问范围
,所以通过父类句柄p,可以访问的成员变量和成员方法都是父类范围的 ,而不是子类范围。 如果使用子类句柄lp,去访问同名成员,访问的都是子类范围的。
由于继承的关系,如果子类成员方法想要继承父类成员方法,那么可以在子类代码中通过super关键词来访问当前对象的父类成员。
super是用来访问当前对象其父类的成员。尤其当子类的成员如果与父类的成员同名,那么需要使用super来指定访问其父类成员,而非默认的子类成员。
class Packet; //父类
integer value;
function integer delay () ;
delay = value * value;
endfunction
endclass
class LinkedPacket extends Packet; //子类integer value;
function integer delay () ;
delay = super.delay ()+ value * super.value;
//子类通过super,访问与父类同名的delay函数,和同名成员变量value
endfunction
endclass
super对于类的继承和对域作用范围的指定都有帮助 。
这个验证环境已经有了generator和driver的两个组件:
generator(激励产生器):是单纯产生激励数据,
driver(驱动器):是单纯使用激励数据来发送时序激励。
这种单一职责的划分,使得各个组件的任务十分明确,那么一个简单环境构建就有了。
在验证环境中,描述测试环境的部分由test来实现。按照test要求来生成激励,并且传递给driver组件由generator实现。在最后,通过激励的数据内容,将其按照时钟周期,将数据驱动到硬件接口上的是driver。最后激励数据通过信号的方式,抵达了DUT边界端口。
如果要将数据发送到DUT,那么需要有以下的基本元素和数据处理方法,我们将它们封装到Transaction类中。
从generator产生的激励中,包括source、dst、data和crc等变量,将它们和有关的处理函数calc_crc()和display()封装在 Transaction类中。 driver获取这个类之后,将从中提取变量值,再将其驱动到DUT端口上面。
class Transaction;
rand bit [31:0] src, dst, data[8] ;//随机成员变量
bit [31 :0] crc; //二次处理后的成员数据
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。
// BadTr是Transaction的子类,Transaction是BadTr的父类。
class BadTr extends Transaction;
rand bit bad_crc;
// BadTr重新定义了函数calc_crc()和display(),
//而在其内部通过super来索引父类的同名函数。
virtual function void calc_crc;
super.calc_crc() ; // 计算crc
if (bad_crc) crc = ~crc; //破坏crc
endfunction
virtual function void display(input string prefi x="");
$write("%sBadTr: bad_crc=%b, ", prefix, bad_crc);
super.display () ;
endfunction
endclass : BadTr
在定义了这两个激励类型以后,它们将可以被激励发生器产生,并送往驱动器。
BadTr是Transaction的子类,Transaction是BadTr的父类。
BadTr重新定义了函数calc_crc()和display(),而在其内部通过super来索引父类的同名函数。
如果创建一个子类对象,那么该对象的空间可以划分为父类成员的空间和子类成员的空间。即便父类和子类有同名的成员变量和成员方法,但是它们的空间都是独立的。
需要注意的是,如果是父类的句柄指向了子类的对象,那么它访问的空间将会受到父类句柄可访问范围的影响,只能访问父类成员