复习整理下。安利下 路科验证。
类和对象的概述
类和对象
- 对象编程语言更符合人对自然的理解(属性property和功能function)
- 无数的类(class)和对象(object)构成代码世界。类是将相同的个体抽象出来的描述方式,对象是实体,其具备有独立行为的能力,一个对象是万千世界中的“一粒沙”。
- 具有相同属性和功能的对象属于同一类,而不同的类之间可能有联系(继承关系)或者没有联系。
- 在C语言中,编程基于过程方法(function);在Verilog中,提供了笨拙的类对象编程可能性,即在module中定义方法,而后调用module实例中的方法。
- Verilog的module+method的方式与SV的class定义有本质上的差别,即面向对象编程的三要素 :封装(Encapsulation)继承(Inheritance)和多态(Polymorphism)。
- 类的定义核心即是属性声明(property declaration)和方法定义(method definition),所以类是数据和方法的自洽体(self-compatible),即可以保存数据和处理数据。这是与struct结构体在数据保存方面的重要区别,因为结构体只是单纯的数据集合,而类则可以对数据做出符合需要的处理。
为什么要OOP(面向对象编程)?
- 激励生成器(stimulus generator):生成激励内容
- 驱动器(driver):将激励以时序形式送至DUT
- 监测器(monitor):监测信号并记录数据
- 比较器(checker):比较数据
-
验证环境的不同组件其功能和所需要处理的数据内容是不相同的
-
同环境的同一类型的组件其所具备的功能和数据内容是相似的
-
基于以上两点,验证世界的各个组件角色明确、功能分立,使用面向对象编程与验证世界的构建原则十分符合。
-
示例激励数据类:一个transaction事务类
class Transaction;
bit[31:0]addr,crc,data[8];
function void display;
$display("Transaction:8h",addr);
endfunction:display
function void calc crc;
crc = addr ^ data.xor;
endfunction:calc_crc
endclass:Transaction
OOP的概念要素
官方OOP三要素;封装、继承、多态
- Class类: 基本模块包含成员变量和方法。在Verilog中module也可以包含变量和方法,只不过它是硬件盒子’,而class是软件盒子。
- Ojbect对象: 类的实例。Verilog中module也可以例化,这是硬件"例化,在SV中可以使用class来例化,这是软件"例化。
- Handle句柄(指针): 用来指向对象的指针。在Verilog中,可以通过层次化的索引来找到结构中的设计实例,而在SV的对象索引时,需要通过句柄来索引对象的变量和方法。
- Property属性(变量): 在类中声明的存储数据的变量。在Verilog中,它可以是wire或者reg类型。
- Method方法:类中可以使用task或者function来定义方法以便处理自身或者外部传入的数据。在Verilog中可以在module中定义task/function,也可以使用initial/always处理数据。
创建对象
- Verilog的例化和SV class例化的差别:
- 两者的共同点在于使用相同的模板来创建内存实例
- 不同点在于Veriloa的例化是静态的,即在编译链接时完成,而SV class的例化是动态的,可以在住意时间点发生,这也使得类的例化方式更加灵活和节省空间。
- Verilog中没有句柄的概念,即只能通过层次化的索引方式A.B.sigX,而SV class通过句柄可以将对象的指针赋予其它句柄,使得操作更加灵活。
- 在创建对象时,需要注意什么是**“声明”,什么是“创建”(即例化)**
Transaction tr;//声明句柄
tr =new();//创建对象
- 思考题: 下面有关类的说法哪些是错误的呢?
- A、类可以定义变量也可以定义方法
- B、一个类只能够用来生成一个对象(可声明很多个)
- C、指向对象的句柄有且只能有一个(错,可以有很多个,多个句柄可指向同一对象)
- D、类中定义的变量类型可以是wire或者reg(错,SV类偏向软件编程的思想,和硬件有关的内容都不要出现)
- 所谓创建对象即开辟新的内存间,用来存放新的成变量和方法
- 创建对象时,可以通过自定义构建函数(constructor)来完成变量的初始化和其它初始操作。
- 示例
class Transaction;
logic[31:0]addr,crc,data[8];
function new();
addr = 3;
foreach(data[i])
data[i]=5;
endfunction
endclass
- 构建函数new()是系统预定义函数,不需要指定返回值,函数会隐式地返回例化后的对象指针。
- 构建函数也可以定义多个参数作为初始化时外部传入数值的手段。
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);//data uses default of 5
end
上述代码中完成初始化后,tr.addr=10。new(10),将10传给了a。赋值在初始化之后,初始化的值被改变了,所以tr.addr不再是初值的’h10,而是10。
句柄的传递
- 在区分了类(抽象)和对象(具体)之后,还需要区分对象(存储空间)和句柄(空间指针)。也就是说,在创建了对象之后,该对象的空间位置不会更改,而指向该空间的句柄可以有多个。
Transaction t1,t2;//声明句柄t1,t2
t1 =new();//例化对象,将其句柄赋予t1
t2=t1;//将t1的值赋予t2,即t1和t2指向同一个对象
t1 = new();//例化第二个对象,并将其句柄赋予t1
对象的销毁
- 软件编程的灵活在于可以动态地开辟使用空间,在资源闲置或者不再需要时,可以回收空间,这样使得内存空间保持在一个合理的区间。
- C++语言中的类除了有构建函数,还有析构函数。析构函数的作用即在于手动释放空间,但这对编程人员的细心和经验提出了要求;Java和Python等后续面向对象语言则不再需要手动定义析构函数并且释放空间,这意味着空间的回收利用也是自动的。
- 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?")<