面向对象编程(一)
一、前言
面向对象编程使用户能够创建复杂的数据类型,并且将它们跟使用这些数据类型的程序紧密地结合在一起。用户可以在更加抽象的层次建立测试平台和系统级模型,通过调用函数来执行一个动作而不是改变信号的电平。当使用事务来代替信号翻转的时候,你就会变得更加高效。这样做的附加好处是,测试平台跟设计细节分开了,它们变得更加可靠,更加易于维护,在将来的项目中可以重复使用。
测试平台的目标是给一个设计施加激励,然后检查其结果是否正确。如果把流入和流出设计的数据组合到一个事务里,那么围绕事务及其操作实施测试平台就是最好的办法。在OOP中,事务就是测试平台的焦点。传统的测试平台强调的是要做的操作:创建一个事务、发送、接收、检查结果、然后产生报告。而在OOP中,你需要重新考虑测试平台的结构,以及每部分的功能。发生器(generator)创建事务并且将它们传给下一级,驱动器(driver)和设计进行会话,设计返回的事务将被监视器(monitor)捕获,记分板(scoreboard)会将捕获的结构跟预期的结果进行比对。因此,测试平台应该分成若干个块(block),然后定义它们相互之间如何通信。
在SV中,你可以把类定义在program、module、package中,或者在这些块之外的任何地方。当你创建一个项目的时候,可能需要将每个类保存在独立的文件中。当文件的数目变得太大的时候,可以使用SV的包将一组相关的类和类型定义捆绑在一起。
二、OOP术语
- 类(class):包含变量和子程序的基本构建块。Verilog中与之对应的是模块(module);
- 对象(object):类的一个实例。在Verilog中,你需要实例化一个模块才能使用它;
- 句柄(handle):指向对象的指针。在Verilog中,你通过实例名在模块外部引用信号和方法。一个OOP句柄就像一个对象的地址,但是它保存在一个只能指向单一数据类型的指针中;
- 属性(property):存储数据的变量。在Verilog中,就是寄存器reg或者线网wire类型的信号。
- 方法(method):任务或者函数中操作变量的程序性代码。
- 原型(property):程序的头,包括程序名,返回类型和参数列表。程序体则包含了执行代码。
三、对象
1.创建新对象
Transaction tr;//声明一个句柄
Tr=new();
在声明句柄Tr的时候,它被初始化为特殊值null。接下来,你调用new()函数来创建Transaction对象。new函数为Transaction分配空间,将变量初始化为默认值(二值变量为0,四值变量为X),并返回保存对象的地址。
new函数也被称为"构造函数",但是new函数不能有返回值,因为构造函数总是返回一个指向类对象的句柄,其类型就是类本身。
可以使用具有默认值的函数参数来创建更加灵活的构造函数。
class Transaction
logic[31:0]addr,crc,data[8];
function new(logic[31:0] a=3,d=5);
addr=a;
foreach(data[i])
data[i]=d;
endfunction
endclass
应该避免在声明一个句柄的时候调用构造函数,即new函数。虽然这样在语法上是合法的,但是这会引起顺序问题,因为在这时构造函数在第一条过程语句前就被调用了。你可能希望按照一定的顺序初始化对象,但是如果在声明的时候调用了new函数,你就不能控制这个顺序了。
2.回收对象
一旦你得知事务已经成功完成,并且也得到了统计结果,你就不需要再保留这些对象了。这时候你需要回收内存。否则,长时间的仿真将会将内存耗尽,或者运行得越来越慢。
垃圾回收是一种自动释放不再被引用得对象得过程。SV分辨对象不再被引用得办法就是记住指向它得句柄的数量,当最后一个句柄不再引用某个对象了,SV就释放该对象的空间。
Transaction t;
t=new();
t=new();
t=null;
3.使用对象
可以通过对对象使用" . "符号来引用变量和子程序,例如:
Transaction t;
t=new();
t.addr=32'h42;
四、静态变量和全局变量
每个对象都有自己的局部变量,这些变量不和任何其他对象共享。如果有两个Transaction对象,则每个对象都有自己的addr、crc和data变量。但有时候你需要一个某种类型的变量,被所有的对象所共享。例如,可能需要一个变量来保存已创建事务的数目,如果没有OOP,可能需要创建一个全局变量。然后你就有了一个只被一小段代码所使用,但是整个测试平台都可以访问的全局变量。
在SV中,可以在类中创建一个静态变量。该变量将被这个类的所有实例所共享,并且它的使用范围仅限于这个类。
class Transaction;
static int count=0;\
int id;
function new();
id=count++;
endfunction
endclass
Transaction t1,t2;
initial begin
t1=new();
t2=new();
end
可以通过类名加上::来引用静态变量
静态变量通常在声明时初始化。你不能简单地在类的构造函数中初始化静态变量,因为每一个新的对象都会调用构造函数。你可能需要另一个静态变量来作为标志,以标识原始变量是否已被初始化。
SV不允许静态方法读写非静态变量。
关注作者
- 自述
作者是一位中科大数字设计专业的研究生,水平有限,如有错误,请大家指正,想要与大家一同进步。 - 经历
曾获得国家奖学金,“高教社杯”数学建模国家二等奖等 - 陆续更新:
1.与UVM验证相关的system verilog后续内容;
2.与verilog数字设计相关的一些基础模块设计,例如FIFO,UART,I2C等的书写。
3.保研与竞赛经历等 - 微信公众号
欢迎大家关注公众号“数字IC小白的日常修炼”,期待与大家一同仗剑遨游数字IC世界。