SV学习(6)——类的继承、句柄的使用
1. 类的成员访问类型
- public:如果没有指明访问类型,默认是public,子类和外部均可以访问成员
- protected:只有该类或者子类可以访问成员,外部不可访问
- local:只有该类可以访问成员,子类和外部均无法访问
2. 类的继承 extends
class cat;
protected color_t color; // 类和子类可以访问
local bit is_good; // 类可以访问
function set_good(bit s);
this.is_good = s;
endfunction
endclass
class black_cat extends cat;
function new();
this.color = BLACK;
endfunction
endclass
class white_cat extends cat;
function new();
this.color = WHITE;
endfunction
endclass
black_cat bk;
white_cat wt;
initial begin
bk = new();
wt = new();
bk.set_good(1);
wt.set_good(1);
end
【Q】
下面对于黑猫白猫类的说法那些是正确 的?
A:可以通过外部修改黑猫的颜色使其变为白猫
B:黑猫可以自己在初始化时使用this.is_good = 1,夸自己是好猫
C:外部可以通过访问黑猫的is_good属性得知它是不是好猫
D:至于白猫是不是一只“大脸猫”,无从得知
【An】
A:color是protected类型,类或者子类可以访问,外部不可访问
B:is_good是local类型,类可以访问,子类和外部均无法访问
C:外部不可访问local
3. 子类索引父类的同名函数 super
class basic_test;
int def = 100;
int fin;
task test(stm_ini ini);
$display("basic_test::test");
endtask
function new(int val);
...
endfunction
endclass
class test_wr extends basic_test;
function new();
super.new(def);
$display("test_wr::new");
endfunction
task test(stm_ini ini);
super.test(ini);
$display("test_wr::test");
endtask
endclass
class test_rd extends basic_test;
function new();
super.new(def);
$display("test_rd::new");
endfunction
task test(stm_ini ini);
super.test(ini);
$display("test_rd::test");
endtask
endclass
- 子类可以继承父类的成员变量和方法,也可以定义新的成员变量和方法
- 子类中的方法和父类本没有任何关联,是通过super才继承的
- 如果子类中没有调用super.new(),那就不会执行父类new函数初始化部分【因为上述父类的new函数有参数】
new():1. 系统看到new会开辟空间;2. 进入new完成变量的初始化;3. 返回句柄;
用一个代码例子体会一下,
class basic_test;
int def = 100;
int fin;
task test(int ini);
$display("basic_test::test");
endtask
function new();
$display("basic_test::new");
endfunction
endclass
class test_wr extends basic_test;
int def = 200;
function new();
super.new();
$display("test_wr::new");
endfunction
task test(int ini);
$display("test_wr::test");
endtask
endclass
class test_rd extends basic_test;
int def = 200;
function new();
$display("test_rd::new");
// super.new(); // err
endfunction
task test(int ini);
super.test(ini);
$display("test_rd::test");
super.test(ini);
endtask
endclass
module test;
initial begin
test_wr wr1;
test_rd rd1;
wr1 = new();
wr1.test(1);
rd1 = new();
rd1.test(1);
end
endmodule
蓝色标记说明:子类在定义new函数时,首先应该调用父类的new函数即super.new()。如果父类的new函数没有参数,子类也可以省略调用,而系统会在编译时自动添加super.new()。
总结:
- new函数必须要完成继承,若父类new没有参数,就默认调用;若父类new有参数,就必须显式调用super.new(参数列表);
- 除了new函数,其他函数需要显式调用才能执行;
- 父类的函数可以在子类的不同名函数中调用,
4. 成员覆盖
在父类和子类里,可以定义相同名称的成员变量和方法(形式参数和返回类型也应该相同),而在引用时,也将按照句柄类型来确定作用域。
class basic_test;
int def = 100;
int fin;
task test(stm_ini ini);
$display("basic_test::test");
endtask
function new(int val);
...
endfunction
endclass
class test_wr extends basic_test;
int def = 200;
function new();
super.new(def);
$display("test_wr::new");
$display("test_wr::super.def = %0d", super.def); // 父类 def = 100
$display("test_wr::this.def = %0d", this.def); // 子类 def = 200
endfunction
endclass
【Q】
下面代码的wr.def和t.def分别是多少?
module tb;
...
basic_test t;
test_wr wr;
initial begin
wr = new();
t = wr;
$display("wr.def = %0d", wr.def);
$display("t.def = %0d", t.def);
end
endmodule
【An】
子类句柄赋值给父类之后,被赋值的父类句柄只能指向整个子类中属于父类的部分,这样对访问来说是安全,相当于缩小了访问范围,所以t.def = 100,wr.def = 200;
-
子类句柄可以赋值给父类,反之父类的句柄不能赋值给子类;
-
若wr = t,编译报错,相当于扩大了访问范围;
-
如果想将父类句柄(该父类句柄要指向子类的对象)赋值给子类,用$cast();【没遇到过这样的代码,以后补充】【遇到了】
-
若子类中没有同名的def变量,wr.def优先在子类成员中找def,找不到,扩大范围,找到父类中的def,wr.def = 100;
补充一个有趣的代码
class packet;
integer i = 1;
integer m = 2;
function new(int val);
i = val + 1;
endfunction
function shift();
i = i << 1;
endfunction
function test(int x);
$display("haha");
endfunction
endclass
class linkedpacket extends packet;
integer i = 3;
integer k = 5;
function new(int val);
super.new(val);
if(val >= 2)
i = val;
endfunction
function shift();
super.shift();
super.test(6);
// i = super.i << 2;
i = i << 2;
endfunction
endclass
module tb;
initial begin
packet p = new(3);
linkedpacket lp = new(1);
packet tmp;
tmp = lp;
$display("p.i = %0d", p.i);
$display("lp.i = %0d", lp.i); // child class i = 3, parent class i = 2
// $display("lp.super.i = %0d", lp.super.i); // err: unexpected "SystemVerilog keyword 'super'",super用在类内部
$display("tmp.m = %0d", tmp.m); // m = 2, index to parent scope
$display("tmp.i = %0d", tmp.i); // i = 2, index to parent scope
// $display("tmp.k = %0d", tmp.k); // err: not found k,父类只能索引父类域中的对象
//-- p.shift();
//-- $display("after shift, p.i = %0d", p.i);
//-- lp.shift();
//-- $display("after shift, lp.i = %0d", lp.i);
end
endmodule
5. 句柄的传递
-
句柄可以作为形式参数通过方法来完成对象指针的传递,从外部传入方法内部;
-
传递的是对象指针,句柄,不是对象;
task generator; Transaction t; t = new(); transmit(t); endtask task transmit (Transaction t); ... endtask
-
句柄可以在方法内部首先完成修改,而后再由外部完成使用;
function void create(Transaction tr); tr = new(); tr.addr = 100; ... endfunction Transaction t; initial begin create(t); t.addr = 10; $display(t.addr); end
【Q】
最后显示的t.addr是多少?
【An】
句柄创建后默认悬空null,create函数并没有返回值,函数执行完t还是悬空,t.addr会报错
修改:默认参数传递方向是input,改为inout或ref
6. 句柄的动态修改
-
程序在执行时,可以在任何时刻为句柄创建新对象,并将新的指针赋给句柄;
task generator_trans(); Transaction t; Transaction fifo[$]; // 队列 t = new(); for(int i = 0; i < 3; i++) begin t.addr = i << 2; fifo.push_back(t); end t = fifo.pop_front(); endtask
【Q】
t.addr的数值是多少?
A:0
B:4
C:8
D:12
【An】
选C。
声明句柄t后,总共只创建了一个对象,所以每次在队尾push_back入队的时候,都是这一个对象地址(句柄),循环中的t.addr索引就是这个对象的addr,相当于addr赋值了三次,分别是0、4、8,最后从队头pop_front取出的就是最后一次写入的值