至此,SV的学习结束,实验4和实验5等回头过来重做,一定要到完全理解为止。SV DAY30
类型转换
- 分类:静态转换,动态转换
- 静态转换:在需要转换的表达式前面加上单引号,不会对转换值检查,如果转换失败,我们也不知道
- 动态转换:$cast(tgt,src)。会返回0或者1,前者是子类,后者是父类句柄。函数会检查句柄所指向的对象类型
- 类的句柄向下转换时(从父类句柄到子类句柄),就要用cast进行转换。
- 子类句柄赋值给父类句柄,赋值合法,但是子类父类的句柄分别调用相同对象的成员时可能会有不一样的表现
- 异同:都需要操作符号或者系统函数介入,统称显示转换
- 那隐式转换呢?:不需要进行转换的一些操作。比如赋值语句右侧是4位矢量,左侧是5位矢量,先做位扩展再赋值
class transaction;
rand bit [31:0] src;
function void display (input string prefix="");
$display("%stransaction:",prefix, src);
endfunction
endclass
class badtr extends transaction;
bit bad_crc;
function void display (input string prefix="");
$display("%sbadtr: bad_crc=%b", prefix, bad_crc);
super.display(prefix);
endfunction
endclass
transaction tr;
badtr bad,bad2;
transaction tr;
badtr bad;
bad=new();//创建子类的对象
tr=bad;//子类句柄赋值给父类句柄
$display(tr.src);
tr.display();//调用的是父类的display transaction::display() badtr::display()
//如果创建的是父类的对象
tr=new();
bad=tr;//编译error
//如果做转换$cast(bad,tr);也不能成功,因为父类不能扩展成子类,bad是null空句柄
$display(bad.bad_crc);
bad=new();
tr=bad;//父类句柄指向子类对象
if(!$cast(bad2,tr))//
$display("cannot");
$display("bad2.bad_crc");
bad2.display();//调用的是子类的方法
虚方法
class basic_test;
int fin;
int def=100;
function new();
$display("basic_test::new");
endfunction
task test();
$display("basic_test::test");
endtask
endclass
class test_wr extends basic_test;
int def=200;//也继承了父类的100
function new();
super.new();//子类调用父类必须用super,否则就是子类覆盖了父类的方法
$display("test_wr::new");
endfunction
task test();
super.test();
$display("test_wr::test");
endtask
endclass
basic_test t;
test_wr wr;
initial begin
wr=new();
t=wr;//子类句柄例化后给父类句柄,对象的传递,都指向子类的对象
$display("wr test starts");
wr.test();//调用子类的test,先打印父类basic_test::test,再打印子类test_wr::test
$display("wr test ends");
$display("t test starts");
t.test();//只打印父类,basic_test::test
$display("t test ends");
end
作用:动态绑定,调用方法时,在运行时确定句柄指向对象的类型,再动态指向应该调用的方法。也就是在父类task前面加上virtual,在t.test();时就会执行子类test。因为查找到t所指向的对象是wr子类句柄
- 尽量定义在底层的父类
- 如果方法可能被覆盖或继承,就声明virtual,只需声明一次
- virtual task的继承需要遵循相同的参数和返回类型
对象拷贝
- 声明变量packet p1;和创建对象p1=new;是两个过程,可以写成一个语句packet p1=new;
- packet p2; 如果将p2=p1;这样依然只有一个对象(new),指向这个对象的句柄有两个
浅拷贝
packet p1;
packet p2;
p1=new;
p2=new p1;//两个对象,在创建p2对象时将从p1拷贝其成员变量
test_wr h;
initial begin
wr=new();//例化的是子类
h=wr;//子类句柄赋值给父类,同时指向子类对象
$display("wr.def=%0d", wr.def);//打印出来是同一个200
$display("h.def=%0d", h.def);
h.def=300;
$display("wr.def=%0d", wr.def);//同一个300
$display("h.def=%0d", h.def);
end
拷贝对象
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
basic_test t=new(0);//创建父类句柄指向的对象
copy_data(t);//变量拷贝给这个对象
return t;//返回创建对象的句柄t
endfunction
endclass
class test_wr extends basic_test;
......
function void copy_data(basic_test t);
test_wr h;
super.copy_data(t);//调用父类函数,拷贝def和fin,子类中的父类
$cast(h,t);//将父类句柄转换成子类句柄
h.def=def;//想把子类中的父类中的def给到子类的def
endfunction
function basic_test copy();
test_wr t=new();//t是子类句柄
copy_data(t);//t需要变成父类句柄,隐式转换了
return t;
endfunction
endclass
- 成员拷贝函数copy_data()和新对象生成函数copy()分成两个task,使子类继承和方法复用更容易
- 为了保证父类子类的成员都可以拷贝,要用virtual task,且遵循只拷贝该类的域成员
- 父类的成员拷贝应该用父类的拷贝task完成。super
- 父类句柄到子类,注意转换
回调函数
- 理想的验证环境是在被移植做水平复用或垂直复用时,尽可能少修改模块验证环境本身,只在外部做少量的配置,或定制化修改就可以嵌入新的环境
- 怎么做?通过顶层环境的配置对象自顶向下进行配置参数传递;不修改原始类并注入新的代码
- 顶层环境之一就是set_interface
- 比如实验4中在父类root_test预留的do_config,用virtual然后在具体test中进行详细随机和约束,进行子类继承
class driver;
driver_cbs cbs[$];//队列
task run();
bit drop;
transaction tr;
forever begin
drop=0;
agt2drv.get(tr);
foreach(cbs[i]) cbs[i].pre_tx(tr,drop);
if(drop) continue;//drop==1时才继续执行下面的语句
transmit(tr);
foreach(cbs[i]) cbs[i].post_tx(tr);
end
endtask
endclass
class driver_cbs_drop extends driver_cbs;
virtual task pre_tx (ref transaction tr, ref bit drop);//drop也可以是output
drop=($urandom_range(0,99)==0);//drop==1的概率是1%,==0概率99%
endtask
endclass
program automatic test;
environment env;//环境?
initial begin
env=new();//例化并执行一系列函数
env.gen_cfg();
env.build();
begin//重点是这个线程
driver_cbs_drop dcd=new();//例化创建回调对象(例化了class)
env.drv.cbs.push_back(dcd);//植入driver
end
env.run();
env.wrap_up();
end
endprogram
- 如何使用回调函数?
- 预留入口(foreach)
- 定义回调callback的类(class driver_cbs_drop extends driver_cbs;)
- 例化添加(driver_cbs_drop dcd=new();//例化创建回调对象(例化了class) env.drv.cbs.push_back(dcd);//植入driver)
参数化的类
- 参数化的目的:提高代码的复用率(类似分频器那里的parameter N。根据需要加上具体的数值即可实现多种分频)
- 合理的参数的使用可以降低后期维护的成本
- 验证环境中,类型也可以做类定义时的参数
- SV可以为class增加多个数据类型参数,并在声明类的句柄时指定类型
class mailbox;//只能操作整数类型int。
local int queue[$];
task put(input int i);
queue.push_back(i);
endtask
task get(ref int o);
wait(queue.size()>0);
o=queue.pop_front();
endtask
task peek(ref int o);
wait(queue.size()>0);
o=queue[0];
endtask
endclass
如果要存储real类型或句柄,就需要复制这个类然后将数据类型转换成real或某个class的类型。类大增,重复代码大增。
所以做以下的修改
class mailbox #(type T=int);//声明mailbox的类型时将int参数化,下面的int改为参数化
local T queue[$];
task put(input T i);
queue.push_back(i);
endtask
task get(ref T o);
wait(queue.size()>0);
o=queue.pop_front();
endtask
task peek(ref T o);
wait(queue.size()>0);
o=queue[0];
endtask
endclass
- 后期若需要修改参数类型只需要重新定义T,默认是int
initial begin
real o;//o声明为real类型
mailbox #(real) mb;
mb=new();//创建一个存放real类型的mailbox,进行存取
for (int i=0; i<5; i++)
mb.put(i*2.0);
for (int i=0; i<5; i++)
mb.get(o);
end
太久没打球,一个人去投了一下,长时间的久坐已经把身体废掉了,光健身还是不够的。