类型转换
- 类型转换可以分为静态转换和动态转换。
- 静态转换即需要在转换的表达式前加上单引号即可(int'(4.0)),该方式并不会对转换值做检查。如果发生转换失败,我们也无从得知。
- 动态转换即需要使用系统函数$cast(tgt, src)做转换。
- 静态转换和动态转换均需要操作符号或者系统函数介入,统称为显式转换。
- 不需要进行转换的一些操作,我们称之为隐式转换。例如赋值语句右侧是4位的矢量,而左侧是5位的矢量,隐式转换会先做位宽扩展((隐式),然后再做赋值。
动态转换
- 当我们使用类的时候,类句柄的向下转换,即从父类句柄转换为子类句柄时,需要使用$cast()函数进行转换,否则会出现编译错误,这一步也是编译器的保护措施,防止用户出现错误的赋值。
- 如果将子类句柄赋值给父类句柄时,编译器则认为赋值是合法的,但分别利用子类句柄和父类句柄调相同对象的成员时,将可能有不同的表现。
- 将一个父类句柄赋值给一个子类句柄并不总是非法的。但是SV编译器对这种直接赋值的做法是禁止的,也就是说无论父类句柄是否真正指向了一个子类对象,赋值给子类句柄时,编译((静态)都将出现错误。
- 因此需要$cast(tgt, src)来实现句柄类型的动态转换。
- $cast(tgt, src)会检查句柄所指向的对象类型,而不仅仅检查句柄本身。(比如父类句柄指向子类对象时,转换可以成功,注意tgt是子类句柄,此时可以访问子类)
- —旦源对象跟目的句柄是同一类型,或者是目的句柄的扩展类,$cast()函数执行即会成功,返回1,否则返回0。
虚方法
- 类的继承是从继承成员变量和成员方法两个方面。从例码中可以看到test_wr类和test_rd类分别继承了basic_test类的成员变量以及成员方法。(即使子类和父类有同名的变量不同值,但还是继承了,通过super可以查找父类的变量)(子类覆盖override的方法不会继承父类同名方法,只有通过super.method()的方式显式执行才会达到继承的效果)
- 正是由于类的多态性,使得用户在设计和实现类时,不需要担心句柄指向的对象类型是父类还是子类,只要通过虚方法,就可以实现动态绑定(dynamic binding) ,或者在SV中称之为动态方法查找(dynamic method lookup) 。
- 我们将已经在编译阶段就可以确定下来调用方法所处作用域的方式称之为静态绑定(static binding) ,而与之相对的是动态绑定。动态绑定指的是,在调用方法时,会在运行时来确定句柄指向对象的类型,再动态指向应该调用的方法。(看句柄指向的对象来调用方法)
- 为了实现动态绑定,我们将basic_test::test定义为虚方法。
- 由于声明了basic_test::test为虚方法,系统在执行t.test时,会检查t所指向对象的类型为test_wr类,进而调用test_wr: :test。于是,输出结果与调用wr.test—致。
- 我们就可以通过虚方法的使用来实现类成员方法调用时的动态查找,用户无需担心使用的是父类句柄还是子类句柄,因为最终都会实现动态方法查找,执行正确的方法。
- 在为父类定义方法时,如果该方法日后可能会被覆盖或者继承那么应该声明为虚方法。
- 虚方法如果要定义,应该尽量定义在底层父类中。这是因为如果virtual是声明在类继承关系的中间层类中,那么只有从该中间类到其子类的调用链中会遵循动态查找,而最底层类到该中间类的方法调用仍然会遵循静态查找。即使子类方法没有super继承,一样调用同名方法?
- 虚方法通过virtual声明,只需要声明一次即可。当然再次声明来表明该方法的特性也是可以的。
- 虚方法的继承也需要遵循相同的参数和返回类型,否则,子类定义的方法须归为同名不同参的其它方法。
- 没有办法可以使父类句柄索引到子类对象里面的变量。(除了将其转为子类句柄)
//这里使用virtual,主要是希望通过句柄指向的对象来调用相应的函数
class basic_test;
function new();
……
endfunction
task test(); //virtual task test();
$display("basic_test::test");
endtask
endclass
class test_wr extends basic_test; //子类会继承父类的方法变量,即使同名不同值,通过super可获得
function new();
……
endfunction
task test();
super.test();
$display("test_wr:test");
endtask
endclass
basic_test t;
test_wr wr;
initial begin
wr=new();
t=wr;
wr.test(); //basic_test::test //basic_test::test
//test_wr::test //test_wr::test
t.test(); //basic_test::test //basic_test::test
end //test_wr::test
对象拷贝
- 声明变量和创建对象是两个过程,也可以一步完成。
Packet p1;
p1 = new ; //new可以有参数,不能有返回值,不能带 virtual
- 如果将p1赋值给另外一个变量p2,那么依然只有一个对象,只是指向这个对象的句柄有p1和p2。
- 以下这种方式表示p1和p2代表两个不同的对象。在创建p2对象时,将从p1拷贝其成员变量例如integer、string和句柄等,该种拷贝方式称为浅拷贝(shallow copy)。
Packet p1;
Packet p2;
p1 = new; //p1=new();
p2 = new pl; //p2=new()p1; //p1、p2指向各自对象,p1=p2则只有一个对象
//新创建一个对象,把原有对象里的变量拷贝到新对象
//句柄拷贝,p1和p2指向同一对象
- 对于拷贝(copy) ,对象的拷贝要比其它SV的变量类型都让人"当心”。因为就SV普通的变量拷贝而言,只需要通过赋值操作符"="就足够了。
- 而对象的拷贝则无法通过"="来实现,因为这一操作是句柄的赋值,而不是对象的拷贝。
- 那么如果要拷贝对象,指的是首先创建一个新的对象(开辟新的空间),再将目标对象的成员变量值拷贝给新对象的成员,这就使得新对象与目标对象的成员变量数值保持一致,即完成了对象的拷贝(成员变量的拷贝)。
区别句柄拷贝与对象拷贝的区别
class basic_test;
……
virtual function void copy_data(basic_test t);
t.def=def;
t.fin=fin;
endfunction
virtual function basic_test copy();
basic_test t=new(0);
copy_data(t);
return t;
endfunction
endclass
class test_wr extends basic_test;
……
function void copy_data(basic_test t);
test_wr h;
super.copy_data(t); //super,所以会调用父类方法,直接执行 父类句柄到父类的copy_data
$cast(h,t); //父类句柄指向子类对象,要添加这个是因为让h也指向子类对象吗?
h.def=def; //实现子类句柄只拷贝子类成员
endfunction
function basic_test copy(); //函数返回类型为basic_test
test_wr t=new();
copy_data(t); //调用该函数会隐式地将子类句柄转为父类句柄,但依然指向子类对象
return t;
endfunction
endclass
module tb;
……
test_wr wr;
test_wr h;
initial begin
wr=new();
$cast(h,wr.copy()); //h和wr指向不同对象
h.def=300;
end
endmodule
- 将成员拷贝函数copy_data()和新对象生成函数copy()分为两个方法,这样使得子类继承和方法复用较为容易。
- 为了保证父类和子类的成员均可以完成拷贝,将拷贝方法声明为虚方法,且遵循只拷贝该类的域成员的原则,父类的成员拷贝应由父类的拷贝方法完成。
- 在实现copy_data()过程中应该注意句柄的类型转换,保证转换后的句柄可以访问类成员变量。
回调函数
- 理想的验证环境是在被移植做水平复用或者垂直复用时,应当尽可能少地修改模块验证环境本身,只在外部做少量的配置,或者定制化修改就可以嵌入到新的环境中。
- 要做到这一点,一方面我们可以通过顶层环境的配置对象自顶向下进行配置参数传递,另外一方面我们可以在测试程序不修改原始类的情况下注入新的代码。
- 例如,当我们需要修改stimulator的行为时,有两种选择,一个是修改父类,但针对父类的会传播到其它子类;另外一个选择是,在父类定义方法时,预留回调函数入口,使得在继承的子类中填充回调函数,就可以完成对父类方法的修改。
virtual class Driver_cbs; //Driver回调虚类 不能例化,可以继承,虚类是提供模板
virtual task pre_tx(ref Transaction tr,ref bit drop);//两个ref,怎么第一个就是输入第二输出
……
endtask
virtual task post_tx(ref Transaction tr);
……
endtask
endclass
class Driver;
Driver_cbs cbs[$]; //队列,里面放着句柄
task run();
endclass
//回调函数主要有三步
//第一预留入口
task Driver::run;
forever begin
……
<pre_callback>
transmit(tr);
<post_callback>
……
end
endtask
//第二步,定义回调类
class Driver_cbs_drop extends Driver_cbs; //继承自虚类
virtual task pre_tx(ref Trsansaction tr,ref bit drop);
drop=($urandom(0,99)==0);
endtask
endclass
//第三步,例化以及添加回调类的实例
program automatic test;
……
begin
Driver_cbs_drop dcd=new();
env.drv.cbs.push_back(dcd);
end
env.run;
……
endprogram
参数化的类
- 参数化的使用是为了提高代码的复用率。
- 无论是设计还是验证,如果代码会被更多的人使用或者被更多的项目所采用,那么就需要考虑使用参数来提高复用率。
- 参数的使用越合理,后期维护的成本就会相应降低。
- 在硬件设计中,参数往往是整形,例如端口数目或者位宽。在验证环境中,参数的使用更加灵活,可以使用各种类型来做类定义时的参数。
- 在SV中,可以为类增加若干个数据类型参数,并在声明类句柄的时候指定类型。
- SV的类参数化近似于C++中的模板。
//该maibox只能用于操作整数类 类定义时添加参数
class mailbox; //class maibox #(type T=int); //参数名为T,值为int
local int queue[$]; // local T queue[$];
task put(input int i); //task put(input T i);
queue.push_back(i);
endtask
task get(ref int o); //task get(ref T o);
wait(queue.size()>0);
o=queue.pop_front();
endtask
task peek(ref int o); //task peek(ref T o);
wait(queue.size()>0);
o=queue[0];
endtask
endclass
//maibox #(real) mb; //创建一个存储real类型的maibox
//mb=new();
- 同一个参数化类,两个指定不同类型T的句柄可以使用$cast完成句柄的类型转换
- 一个参数类在定义时可以指定多个参数