目录
2. 创建对象时,可以通过自定义构造函数(constructor), 来完成变量的初始化和其他初始操作
3. 构造函数new() 是系统预定义函数,不需要指定返回值(void), 函数会隐式地返回例化后的对象指针。
4. 构造函数也可以定义多个参数,作为初始化时外部传入数值的手段。
类的三大特性:
封装(Encapsulation),继承(Inheritance),多态(Polymorphism)
类的定义:
类的定义核心即是属性声明(property declaration)和方法定义(method definition),所以类是数据和方法的自洽体,即可以保存数据也可以处理数据。这是与struct结构体在数据保存方面的重要区别,因为结构体只是单纯的数据集合,而类则可以对数据做出符合需要的处理。
验证为什么需要OOP:
1.验证的组件角色:激励生成器(stimulus generator,生成激励内容),驱动器(driver, 将激励以时序形式发送至DUT),监测器(monitor, 监测信号并记录数据),比较器(checker, 比较内容),
2. 验证世界的各个组件角色明确,功能分立,使用面向对象编程与验证世界的构建原则十分符合。
2.1 验证环境的不同组件其功能和所需处理的数据是不相同的,
2.2 不同环境的同一类型的组件其所具备的功能和数据内容是相似的
Verilog例化与SV class 例化的 异同:
同:使用相同的 “模板” 来创建内存实例
异:1. Verilog的例化是静态的,即在编译链接是完成
SV class 的例化是动态的,可以在任意时间点发生,这也使得类的例化方式更加灵活和节 省空间
2. Verilog中没有句柄的概念,即只能通过层次化的索引方式 A.B.sigX
SV class 通过句柄可以将对象的指针赋予其他句柄,使得操作更加灵活
创建对象:
1. 注意什么是'声明',什么是‘创建’(即例化)
声明句柄: Transcation tr;
创建对象: tr = new();
也可以合成一句: Transcation tr = new();
2. 创建对象时,可以通过自定义构造函数(constructor), 来完成变量的初始化和其他初始操作
class Transaction;
logic [31:0] addr,crc,data[8];
function new();
addr = 3;
foreach(data[i])
data[i] = 5;
endfunction
endclass
3. 构造函数new() 是系统预定义函数,不需要指定返回值(void), 函数会隐式地返回例化后的对象指针。
4. 构造函数也可以定义多个参数,作为初始化时外部传入数值的手段。
class Transaction;
logic [31:0] addr = 'h10;
logic [31:0] crc, data[8];
function new(logic [31:0] a = 3, d = 5);
addr = a;
foreach(data[i])
data[i] = d;
endfunction
endclass
initial begin
Transaction tr;
tr = new(10);
end
代码流程如下:
1) = 'h10 (16) , 开辟空间并初始化
2)new(10), 10传递给 a, 此时 a = 10; d = 5; addr = a = 10;
所以,tr.addr最后的结果是10
其中,a = 3 是默认参数,即表示当new没有传递参数时,a=3, 那么当new有传递参数时,a就为传递参数的值。
句柄的传递
在区分了类和对象之后,还要区分对象(存储空间)和句柄(空间指针)。也就是说,在创建了对象之后,该对象的空间位置不会更改,而指向该空间的句柄可以有多个。
Transaction t1,t2; //声明句柄t1,t2
t1 = new(); //例化对象,将其句柄赋予t1
t2 = t1; // 将 t1 的值赋予t2, 即t1和t2指向同一个对象
t1 = new(); // 例化第二个对象,并将其句柄赋予t1
句柄的使用
1)可以用来创建多个对象,也可以前后指向不同对象
Transaction t1,t2; // 声明句柄
t1 = new(); // 创建对象并将其指针赋予t1
t2 = new(); // 创建对象并将其指针赋予t2
t1 = t2; // 将t2 的值赋予 t1, t1和t2指向同一个对象,t1 之前指向的对象被释放
t2 = null; // 将t2 赋值为 ‘空’ 既不指向任何对象,此时指针‘悬空’ 悬空的指针很 “危险”
2)可以通过句柄来使用对象中的成员变量或者成员方法
Transaction t; // 声明句柄
t = new(); // 例化对象
t.addr = 32'h42; // 对象的成员变量赋值
t.display(); // 调用对象的成员方法
对象的销毁
1)概述: 软件的编程灵活性在于可以动态地开辟使用空间,在资源闲置或者不再需要时,可以回收空间,这样使得内存空间始终保持在一个合理的区间
2)各语言中对象的销毁:1,C++语言中的类除了有构造函数,还有析构函数,析构函数的作用即在于手动释放空间,这对编程人员的细心和经验要求比较高,2,Java和Python等后续的面向对象语言,则不再需要手动定义析构函数,就可以释放空间,即自动回收空间。
3)SV采用了自动回收空间的处理方式,自动回收空间的基本原理是,当一个对象,在整个程序中没有任何一个地方再 “需要” 它时,便会被 “销毁”,即回收其空间。这里 “需要”的意思即指的是有句柄指向该对象。
例:
class word
byte nb[];
function new(int n);
nb = new[n];
endfunction
endclass
initial begin: initial_1
word wd;
for(int i = 1; i <= 4; i++) wd = new(i);
end
initial begin: initial_2
#1ps
$display("How many Bytes are allocated for word instances?");
end
问: 假设 wd = new(1) 所需要开辟1B的空间, 那么在initial_2的display语句处,需要为对象例化开辟多少空间?
答: 循环4次,创建4个对象(1B 2B 3B 4B),执行完,wd句柄指向了第4个对象(initial中是静态)故,答案是4B
问:假设 wd = new(1) 所需要开辟1B的空间,那么在 initial_2 的display语句处,需要为对象例化开辟多少空间?
答:automatic 动态销毁,答案是 0B
静态变量:
1)在class中声明的变量其默认类型为动态变量,即其生命周期在仿真开始后的某时间点开始到某时间点结束。即生命周期始于对象创建,终于对象销毁。
2)如果使用关键字 static 来声明class内的变量时,则其为静态变量,静态变量的生命开始于编译阶段,贯穿于整个仿真阶段。
3)如果在类中声明了静态变量,那么可以直接引用该变量class::var, 或者通过例化对象引用object.var。
4)类中的静态变量声明以后,无论例化多少个对象(0...N), 只可以共享一个同名的静态变量,因此类的静态变量在使用时需要注意共享资源的保护。
举例:
class Transaction;
static int count = 0;
int id;
function new();
id = count++;
endfunction
endclass
Transaction t1,t2;
initial begin
t1 = new(); // 1st instance , id = 0, count = 1
t2 = new(); // 2nd instance , id = 1, count = 2
$display("Second id=%d, count=%d", t2.id, t2.count);
(因为count是静态变量,故count = 0, id = count++, t1实例化后,count = 1,id = 0, t2实例化后 count = 2, id = 1)
静态方法:
1) 在class 中定义的方法默认类型是动态方法,可以通过关键词static修改其类型为静态方法。
2)静态方法内:可以声明并使用动态变量,但是不能使用类的动态成员变量
原因:因为在调用静态方法时,可能并没有创建具体的对象(例如:Transaction::var),也因此没有动态成员变量开辟空间。
在静态方法中,使用类的动态成员变量时禁止的,可能会造成内存泄漏
在静态方法中,可以使用类的静态变量,因为静态方法同静态变量一样在编译阶段就已经为其分配好了内存空间。