system verilog(五)面向对象编程

传统verilog代码的缺点:(由于verilog没有结构体,如果需要存储一个总线事务的信息,需要多个数组:地址,数据,指令等等)用于创建、发送和接受事务的代码位于模块中,但是这个模块可能连接在总线上,也可能没有链接到总线上;而且这些数组都是静态的

面向对象编程:能够创建复杂的数据类型,并且将它们和使用这些数据类型的程序紧密结合在一起,通过调用函数来执行一个动作而不是改变信号的电平。

编写第一个类(计算循环冗余校验码CRC)

class Transaction
	bit [31:0] addr,crc,data[8];//bit[31:0] data[7:0]

function void display;
	$display("Transcation:%h",addr);
endfunction :display

function void calc_cc;
	crc=addr^data.xor;
endfunction:calc_crc
endclass:Transaction

在system verilog中,可以将类定义在program、module、package中,或者这些块之外的任何地方,可以在程序和模块中使用。

OOP术语以及对应verilog关系:
1、类(class):包含变量和子程序的基本构建块(module)
2、对象(object):类的一个实例(实例化一个module)
3、句柄(handle):指向对象的指针(通过实例名在模块外部引用信号和方法)
4、属性(property):存储数据的变量(reg/wire)
5、方法(method):任务或者函数中操作变量的程序性代码(initial、always,function、task)
6、原型(prototype):程序的头,包括程序名,返回类型和参数列表
在verilog中,通过创建模块并且逐层梨花,就可以得到设计;在OOP中创建类并且创建对象,可以得到相似的结构。

创建对象:在systemverilog中,激励对象不断地被创建并且用来驱动DUT,检查结果,最后这些对象所占用的内存可以被释放以供新的对象使用。

声明和使用一个句柄

Transaction tr;//声明和使用一个句柄
Tr = new();//为一个transaction对象分配空间

在声明句柄时,被初始化为null;接下来调用new()函数来创建Transaction对象,new函数(构造函数,但是new函数不能有返回值,因为构造函数总是返回一个指向类对象的句柄,其类型就是类本身)为Transaction分配空间,将变量初始化为默认值(二值为0,四值为x),并返回保存对象的地址。(类似于c语言中的malloc函数)。

下例是一个带有参数的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

initial
begin
Transaction tr;
tr=new (10);//分配40个字节的空间
end

system verilog调用哪个new()函数,取决于赋值操作左边的句柄类型;即是什么类

new()与new[ ](设置动态数组大小)的区别,在于调用new()函数仅创建一个对象,而new[ ]操作则建立一个含有多个元素的数组,new()可以使用参数设置对象的数值,而new[]只需使用一个数值来设置数组的大小。

通过声明句柄来创建一个对象,在一次仿真中,一个句柄可以指向很多对象。
下例中,t1首先指向一个对象,然后指向另一个对象
在这里插入图片描述
对象的解除分配(deallocation)
sysemverilog 分辨对象不再被引用的办法就是记住它的句柄的数量,当最后一个句柄不再引用某个对象,systemverilog就释放对象空间

Transaction t;//创建一个句柄
t=new();//分配一个新的Transaction
t=new();//分配第二个,并且释放第一个
t=null;//解除分配第二个

注意:systemverilog不能回收一个被句柄引用的对象,除非手动设置所有句柄为null。

使用对象,可以对对象使用“."符号来引用变量和子程序

Transaction t;//声明一个Transaction句柄
t=new();//创建一个Transaction对象
t.addr=32'h42;
t.display();

只能通过对象的公有方法访问对象的变量,例如get()和put(),因为直接访问变量会限制以后对代码的修改。

静态变量与全局变量
1、简单的静态变量
在system verilog中,可以在类中创建一个静态变量,该变量将这个类的所有实例所共享,并且使用范围仅限于这个类。

class Transaction;
	static int count=0;
	int id;
	function new();
		id=count++;
	endfunction
endclass

Transaction t1,t2;
initial
begin
t1=new();//第一个对象,id=0,count=1;
t2=new();//第二个对象,id=1;count=2;
$display("%d,%d",t2.id,t2,count);
end

不管创建多少个Transaction对象,静态变量count只有一个,可以认为count保存在类中而非对象中,id不是静态的,每一个对象都有自己的id变量
在这里插入图片描述
system verilog不能输出对象的地址,但是可以创建ID来区别对象,但你打算创建一个全局变量的时候,首先考虑创建一个类的静态变量。(还可以通过类型访问静态变量,使用类型加上::,即类型作用域操作符)
例如:

initial
begin
$display("%d ”,Transaction :: count);//引用静态句柄
end

静态变量的初始化:(不能简单地在类的构造函数中初始化静态变量,因为每一个新的对象都会调用构造函数),在类的每一个实例都需要从同一个对象获取信息的时候,例如transaction类需要从配置对象获取模式位,可以使用一个简单的静态函数来显示静态变量的值,不允许静态方法读写非静态变量

class Transaction;
	static Comfig cfg;
	static int count=0;
	int id;
//显示静态变量的静态方法
statc function void display_statics();
	$display("Transaction cfg.mode=%s,count=%0d",cfg.mode.name(),count);
	endfunction
endclass

config cfg;
intiial
begin
cfg=new(MODE_ON);
Transaction::cfg=cfg;
Transaction::display_statics();//调用静态方法
end

类中的程序也称为方法,也就是在类的作用域内定义的内部task或者function
(应限制代码段长度在一页范围内以保证其可读性,可以将方法的原型定义(方法名和参数)放在类的内部,而方法的程序体(过程代码)放在类的后面定义。

例如:复制方法的第一行包括方法名和参数,然后再开始处添加关键词extern ,
然后将整个方法在类定义的后面,并在方法名前面加上类名和两个冒号(::作用域操作符)

class Transaction
	bit[31:0] addr,crc,data[8];
	extern function void display();
endclass

function void Transaction :: display();
$display("@%0t:Transaction addr=%h,crc=%h",$time,addr,crc);
foreach(data[i])  $write(data[i]);
$display();
endfunction

注意:方法的原型定义与块外的方法定义一致(除了多一个类名和作用域操作符之外)
在类的外部声明方法需要写类名,否则作用范围高了一级(在整个程序或者包中)

作用域规则:一个代码块,例如模块,程序,任务,函数,类或者begin-end块,for和foreach循环自动创建一个块,所以下标变量可以作为该循环作用域的局部变量来声明和创建。
(system verilog中新增可以在一个没有名字的begin-end块中声明变量,例如for循环中定义了索引变量)注意:如果在一个未命名的块内定义变量,那么最终在各个工具中的层次结构名字就可能完全不同。
名字相对于当前作用域,也可以用绝对作用域表示,例如$root 开始,对于一个相对的名字,systemverilog查找作用域内的名字清单,直到找到匹配的名字。

int limit //$root.limit

program automatic p;
int limit;//$root.p.limit

class Foo;
	int limit,array[];//$root.p.Foo.limit
	function void print(int limit);//$root.p.Foo.print.limit
	for(int i=0;i<limit;i++)
	$display("%m:array[%0d]=%0d",i,array[i]);
	endfunction
endclass

initial
begin
int limit=$root.limit;
Foo bar;
bar=new;
bar.array=new[limit];
bar.print(limit);
end
endprogram

类应当在program或者module外的package中定义;如果一个块中使用一个未声明的变量,而在程序块中有一个同名的变量,那么类就会使用程序块中的变量。所以仅在一个initial块中,需要声明变量。(导致使用程序级的变量)

如果将类移入package中,那么类就看不到程序一级的变量

在这里插入图片描述
当你使用一个变量名时,sv会在当前作用域寻找,接着向上级作用域寻找,直到找到变量为止。(但是如果是很深的底层作用域,却想引用类一级的对象,this表示将局部变量赋值给类一级的变量)
在这里插入图片描述
在一个类内使用另一个类
(通过使用指向对象的句柄,一个类内可以包含另一个类的实例)
在这里插入图片描述
最外层的类Transaction可以通过分层调用语法来调用statistics类中的成员。(一定要实例化对象,否则句柄ststs是null,调用start会失败)

有时需要编译一个类,该类中包含一个尚未定义的类。声明这个被包含类的句柄会引起错误,因为编译器不认识这个数据类型。需要使用typedef语句声明一个类名。

动态对象:
1、将对象传递给方法
调用方法时,传递的是对象的句柄而非对象本身
在这里插入图片描述
任务generator调用了transmit。两个句柄generator.t和transmit.t都指向同一个对象

当你调用一个带有标量变量(不是数组,也不是对象)的方法并且使用ref关键词的时候,sv传递该标量的地址,所以方法也可以修改标量变量的值;如果不使用ref关键词,sv将标量复制到参数变量中,不会影响原变量的值。
注意:一个常见的错误是当你修改参数的值时,忘记在方法的参数前加ref关键词,尤其是句柄
在这里插入图片描述
由于tr没有被声明为ref,所以在方法内部对tr的修改不会被调用该方法的代码看到;而tr默认为input。

function void create(ref Transaction tr);

endfunction

另一个常见的错误是忘记为每个事务创建一个新的对象(在begin end块中为每一个事务创建一个新对象)
在这里插入图片描述
句柄数组:写测试平台的时候,可能需要保存并且引用许多对象,可以创建句柄数组,数组的每一个元素指向一个对象

task generator
	transmit tarray[10];
	foreach (tarray[i])
		begin
		tarray[i]=new();//创建每一个对象
		transmit(tarray[i]);
		end
endtask

对象的复制
1、使用new操作符复制一个对象

class Transaction;
	bit[31:0]addr,crc,data[8];
endclass

Transaction src,dst;//声明句柄
initial
begin
src=new();// 创建第一个对象
dst=new src//使用new操作符进行复制
end

但是有一个问题,如果类中包含一个指向另一个类的句柄,那么,只有最高一级的对象被new操作符复制,下层的对象都不会被复制。已经定义的new()函数都不会被调用。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由于没有调用new操作符创建新的对象,会导致对dst修改会同样修改src

class Transaction;
	bit [31:0] addr,crc,data[8];
function Transaction copy;
	copy=new();//创建目标对象
	copy.addr=addr;
	copy.crc=crc;
	copy.data=data;
endfunction
endclass

Transaction src,dst;
initial
begin
src=new();
dst=src.copy;
end

在这里插入图片描述
在这里插入图片描述
给statistics类和层次结构中的每一个类增加了一个copy方法

最后使用时:
在这里插入图片描述
使用流操作符从数组到打包对象,或者从打包对象到数组 或者使用pack和unpack方法
在这里插入图片描述
公有与私有:核心概念把数据和相关方法封装成一个类。在一个类中,数据默认被定义为私有,这样防止了其他类对内部数据成员的随意访问。类会提供一系列的方法访问和修改数据。

建立一个测试平台
在这里插入图片描述
图中的事务是对象;generator、agent、monitor、checker和scoreboard 都是类,在environment类内部被例化。Test处于最高层,即处在例化environment类的程序中。功能覆盖(function coverage)的定义可以放在environment类的内部和外部。

发布了64 篇原创文章 · 获赞 5 · 访问量 3201

猜你喜欢

转载自blog.csdn.net/buzhiquxiang/article/details/104192393