OOP介绍
面向对象编程OOP使用户能创建复杂的数据类型,并且将它们跟使用这些数据类型的程序紧密地结合起来。在更加抽象的层次建立测试平台和系统级模块,通过调用函数来执行一个动作而不是改变信号的电平,使用事务来代替信号翻转。
在OOP中,发生器generator创建事务并传给下一级,驱动器driver与设计会话,设计返回的事务被监视器monitor捕获,计分板scoreboard或者说比较器checker将捕获结果跟预期结果进行对比。
OOP术语
类class,包含变量和子程序的基本构建块
对象object,类的一个实例instance
句柄handle,指向对象obj的指针即对象所在地址,可以指向很多对象,但一次只能指向一个
属性property,数据类型
方法method,task或function
原型prototype,程序名、返回类型、参数列表
类的定义
类class可以定义在program,module,package中或以外的任何地方(最好在package内定义),类中可以包含functon,task,当有许多类时需要将它们用package打包到一个sv文件中,使用时只需将package导入到testbench中。
类的使用
1.创建新对象new
类在使用前必须例化,例化格式:
Transaction tr; //声明句柄
Tr = new(); //创建对象
new为类中内建函数用来分配和初始化对象;在声明句柄tr时,它被初始化为空null,调用new()函数为Transaction创建对象分配空间,这时变量的初始值为0或X,然后返回保存对象的地址。
用户可以自定义new函数,用来初始化变量。
class Transaction ;
logic [31:0] addr,crc,data[8]; //两个32位的寄存器和一个包含8个32bit元素的数组
function new;
addr = 3;
foreach (data[i])
data[i] = 5;
endfunction
endclass
- 带参数的new函数
除了将变量设为固定值还可以使用具有默认值的函数参数来创建灵活的new函数
class Transaction ;
logic [31:0] addr,crc,data[8]; //两个32位的寄存器和一个包含8个32bit元素的数组
function new(logic [31:0] a=3,b=5); //参数类型与赋值左侧保持一致
addr = a;
foreach (data[i])
data[i] = b;
endfunction
endclass
- 将声明句柄与创建对象分开
- new()与new[ ]的区别
new()创建对象,可以使用参数设置对象的数值;new[ ]建立数组,参数设置数组大小
- 对象的解除分配
重新创建对象可以将之前的对象释放或者给句柄赋值null
- 使用对象
通过已经例化的句柄索引变量和子程序
2.静态变量和全局变量
可以在类中声明静态变量,这样该变量将被这个类的所有实例也就是说每个例化的对象所共享,范围仅限这个类。这样可以区分每次例化对象,因为你可以对这个静态变量每次例化时进行递增操作,使每个对象的静态变量都有唯一的值。可以避免使用全局变量,使变量仅在这个class内部使用,减少对外部变量的引用。
通过类名可以访问这个静态变量如:Transaction::static_value
3.方法的使用及定义
如同对象的使用,通过已经例化的句柄调用方法。
方法可以在类的内部定义,也可以在类的外部定义。
类的外部定义方法:
class Transaction;
bit [3:0] addr,crc,data[8];
extern function void display();
endclass
function void Transaction::display();
...
endfunction
4.作用域
作用域是一个代码块,如模块,程序,任务,函数,类或者begin-end快。for和foreach循环自动创建一个块。在不同作用域中使用相同名字的变量时要尤其注意,如果在一个块内使用了一个未声明的变量,碰巧在程序块中有一个同名的变量,那么类就会使用程序块中的变量。
使用this.指向类一级的作用域
5.类的嵌套
可以在类的内部例化其他类
理解动态对象
1.将对象传递给方法
调用方法时,传递的是对象的句柄而非对象。
task generator;
Transaction t;
t = new;
transmit (t);
endtask
task transmit (Transaction t);
...
endtask
generator.t和transmit.t 都指向同一对象。
2.在任务中修改句柄
如果你想在transmit中修改参数t的初始值,需要在参数前加ref关键词,这样就会对句柄指向的对象进行修改。
3.修改多次对象
如果你想多次修改对象的值,并将它们每次修改的值都保存下来,那么每次修改前都需要重新创建对象,否则会覆盖掉之前对象的值。最典型的就是使用循环结构多次创建对象并修改对象的值。
4.句柄数组
如何保存每次创建的对象的值,我们可以创建句柄数组,没错句柄也可以使用数组的形式;如:
Transaction t[10];这就是10个句柄,只需使用foreach循环遍历每个t[i]就可以用来创建10个对象。
对象的复制
在上面我们提到了想要修改句柄必须要声明ref类型 ,但如果我们不想修改它的值只是想借用呢?
-
浅复制
1.使用new操作符复制一个对象
Transaction src,dst;//例化两个句柄
initail begin
src = new; //创建一个对象
dst = new src;//复制对象
end
src和dst指向同一个对象
如果Trancaction中包含指向另一个类的句柄(类的嵌套),那么只有最高一级的对象new操作符复制,下层对象不会被复制。start是例化在Tansaction类中的一个句柄,当你修改dst.start的对象时,src.start也会发生改变。
2.编译一个copy函数进行复制一个对象
function Transaction copy;
copy.src =src;
copy.dst =dst;
copy.data =data;
endfunction
通过句柄调用copy函数即可实现对象的复制,前提是你的句柄要创建对象。
Transaction T1,T2;
initial begin
T1 = new();
T2 = T1.copy;
end
-
深层复制
1.定义嵌套类的深层复制函数
事务类:
function Transaction copy;
copy=new();//调用了事务类中定义的new函数,new函数对start创建对象,并赋予id
copy.src =src;
copy.dst =dst;
copy.data =data;
copy.stats =stats.copy();//Statstics类中也定义copy函数
endfunction