此笔记是根据路科sv练习2-类的继承的讲解视频整理而成,是对以下代码及其详解,并通过几个常见的疑问来加深对类的继承的理解。
module class_inheritance;
bit b_member_override = 1;
class trans;
bit[31:0] data[];
int pkt_id;
int data_nidles;
int pkt_nidles;
bit rsp;
function trans clone(trans t = null);
if(t == null) t = new();//若clone没有传递进来句柄,就创建一个对象;若有,就直接用该句柄
t.data = data;
t.pkt_id = pkt_id;
t.data_nidles = data_nidles;
t.pkt_nidles = pkt_nidles;
t.rsp = rsp;
return t;//若clone有传递进来句柄,那么最后return的t和原来的t是同一个句柄
endfunction
endclass
class chnl_trans extends trans;
int ch_id; // new member in child class
// member function override with
// same function name, arguments, and return type
// TODO-1 seperately enable the clone function-1 and function-2, and check
// if both of them works, and compare which is better, and why?
// clone function-1
function trans clone(trans t = null);
chnl_trans ct;
if(t == null)
ct = new();
else
void'($cast(ct, t));
ct.data = data;
ct.pkt_id = pkt_id;
ct.data_nidles = data_nidles;
ct.pkt_nidles = pkt_nidles;
ct.rsp = rsp;
ct.ch_id = ch_id; // new member
return ct;
endfunction
// clone function-2
// function trans clone(trans t = null);
// chnl_trans ct;
// if(t == null)
// ct = new();
// else
// void'($cast(ct, t));
// void'(super.clone(ct));
// ct.ch_id = ch_id; // new member
// return ct;
// endfunction
endclass
initial begin: member_override
trans t1, t2;
chnl_trans ct1, ct2;
wait(b_member_override == 1); $display("b_member_override process block started");
ct1 = new();
ct1.pkt_id = 200;
ct1.ch_id = 2;
// t1 pointed to ct1's trans class data base
t1 = ct1;
// t2 copied ct1's trans class data base
t2 = ct1.clone();//克隆的返回值都是父类句柄,所以给父类句柄t2
void'($cast(ct2, t2));
// TODO-2 why could not clone t1(->ct1) to t2?
// t2 = t1.clone(); // ERROR clone call:
// void'($cast(ct2, t2));
$display("ct1.pkt_id = %0d, ct1.ch_id = %0d", ct1.pkt_id, ct1.ch_id);
// TODO-3 uncomment the statement below and consider
// why t1 could not point to ct1.ch_id?
// $display("ct1.pkt_id = %0d, ct1.ch_id = %0d", t1.pkt_id, t1.ch_id);
$display("ct2.pkt_id = %0d, ct2.ch_id = %0d", ct2.pkt_id, ct2.ch_id);
end
endmodule
首先解释一下父类的克隆函数:
接着我们来看子类函数给出的第一种写法:
解析:
这种写法中,如果传递进来的句柄是null,那就会创建一个子类的句柄ct;如果传递进来的句柄非null,由于参数类型是trans,所以传递进来的是父类句柄,但是我们是要对子类对象进行克隆。所以通过cast将父类句柄转换成子类句柄,去访问子类变量。
注意:由于函数定义的返回类型是父类trans,所以此处存在一个隐性的类型转化,也就是把子类句柄转化为父类句柄。最后return的子类句柄ct会被转换成父类句柄。
问题1:可以只用父类句柄完成对子类成员变量的克隆吗?也就是改成下面这种形式。
NO!
1、39-43可以成功,因为父类句柄可以访问到子类对象中属于父类的成员变量;
但是44行无法成功,因为父类的句柄无法访问到只属于子类对象的成员变量ch_id。
因此,为了能够访问到子类里的变量,我们需要将指向子类对象的父类句柄转换为子类句柄。
void'($cast(ct,t));
这样可以保证同时访问到父类和子类的变量。
问题2:将返回类型和参数类型设置为子类chnl_trans不是更省事吗?
有人可能会觉得疑惑,为什么要让传进来的句柄是父类的(参数类型是父类),然后将其转换为子类句柄方便对子类的变量进行复制,最后返回了一个子类句柄还要再隐形转换为父类句柄(返回类型是父类)?
直接将返回类型和参数类型设置为子类,不是可以省了很多转化的步骤吗?像下面这样:
注意,如果只是单纯的一个类,不存在继承关系,那么没问题,可以这么写。
而如果你想要对同名函数进行覆盖和继承,那么就会出现问题,因为函数的返回类型和参数类型就不一致了。
另外,我们知道,父类定义方法时,如果该方法日后可能被覆盖或者继承,那么应该声明为虚方法 virtual function。
而虚方法的继承要遵循三个一致——函数名、返回类型、参数类型一致。如果有哪一个不一致,那么就不叫继承了,虚函数对应的功能也就无法出现。
所以,为了让覆盖和继承按照我们的意愿进行,在定义子类函数的时候,要和父类的函数保持函数名、返回类型、参数类型都一致。
对上面子类函数clone代码的总结:
由于传递进来的父类句柄指向的是子类对象,所以将此父类句柄通过动态转换为子类句柄,利用子类句柄去访问父类和子类的成员变量,然后返回子类的句柄ct。最后再把子类句柄隐性转成父类句柄。
上面的子类克隆函数还有更佳的写法,如下:
55行相当于替代了39-43行,而且复用性更好,它将子类的句柄ct放入父类的克隆函数中,完成了对父类变量的拷贝。
再通过56行将子类的变量也拷贝一下,完成对整个子类对象成员变量的拷贝。
问题3:可否把55行里的ct换成t?
会有点问题。
1、如果传进来的句柄t非空,那么就不会产生子类句柄ct,那么数据就会直接通过t句柄传递给t指向的对象,完成数据的复制,没问题;
2、如果没有传进来句柄(也就是调用函数时没有参数,直接写clone()),那么函数内部会创建子类句柄ct。如果写成了void’(super.clone(t)),那么数据就拷贝给了t,而不是我们想给的ct。因此最后的结果只是把ch_id的值复制了而已。
注意:void’(super.clone(t))里的t不是clone函数里的参数t,因为你会发现经过clone函数后,形式参数t还是null,并没有值的改变。
再来解决initial块的两个问题。
TODO-2为什么不能写成下面这种形式?
t2 = t1.clone(); // ERROR clone call:
void'($cast(ct2, t2));
因为我们的目的(69行)是为了复制子类的对象,如果写成上面这样,由于t1是一个父类句柄,所以通过t1.clone()只能克隆得到父类的对象,和我们的期望不一样。
而且将指向父类对象的父类句柄给到t2,那么接下来的动态转换就会失败。不过不会显示,因为void’($cast(ct2, t2))由于void的存在是不返回什么的,可以把void去掉,那么可以看到转换失败的打印信息:
并且最后85行也会报错,因为ct2.ch_id无效
TODO-3 既然t1也是指向了子类对象,那可以把79行换成83行吗?
不行,因为我们无法通过父类的句柄去访问子类的成员变量,一定要通过动态转换才行。
Questasim仿真
步骤:
1、仿真命令:
vsim -voptargs=+acc -classdebug work.class_inheritance
命令解释:
(1)、vopt命令可以加快仿真的速度
-voptargs=“参数”
如果参数为+acc,也就是-voptargs=+acc,意思就是优化部分参数,用于保留某些类别的对象的可见性,否则这些对象可能会被优化掉。被优化掉的对象会使您的调试和分析工作更加困难
如果想全部优化参数,就用-vopt
不想优化参数,那么就用-novopt
ps.关于什么时候需要优化,可见下面链接,这里做部分摘抄
https://blog.csdn.net/u010170039/article/details/79159563
设计优化包括:
①不优化:完全不进行设计优化,所有信号可见,但仿真速度较慢(小工程也无所谓了)
②优化,信号不可见:进行设计优化,但信号都不可见,速度最快。调试完全依赖于testbench的打印输出。
③优化,具有完全可见性:进行设计优化,但又保证所有信号可见,速度较快,调试必备。
④优化,自定义可见性:进行设计优化,自定义哪些模块及信号可见。
(2)、-classdebug
添加上可以看到更多和类相关的调试内容
2、仿真后设置断点,来了解程序的运行
数值默认为16进制,可以通过右击信号,选择Global Signal Radix来设置为十进制。