天天看点

systemverilog之OOPOOP的语法介绍

---------感谢移知的SV课程-----------
           

OOP

  • OOP的语法介绍
    • 继承
    • 多态

OOP的语法介绍

OOP的全称为Object Oriented Programming, 即面向对象编程。SystemVerilog引入了一个面向对象的数据类型,对象是类的实例,类是对象的模板。比如人是一个类,是一个模板,里面有吃饭睡觉说话等特性,然后男人女人老人小孩就是类的实例化对象。在SV中,这样描述

P 	a;//P是定义的一个类,属于一个数据类型,a属于P的一个实例
int	b;//int是一个数据类型,b属于int的一个实例
           

类是一种数据类型, 它包含了数据以及对数据进行操作的子程序(函数和任务)。类的数据被称为类属性,它的子程序被称为方法,无论是类属性还是方法都是类的成员。类属性和方法结合在一起,定义了某种类型的对象的内容和能力。

可以通过类来创建类的实例,类的实例称为对象。对象可以动态地产生、删除(回收)、 赋值、以及通过对象句柄访问。对象句柄为语言提供了一种安全的、类似于指针的机制。类提供了继承和抽象建模的能力,OPP有三大特性:封装、继承、多态

下例为一个类的定义,类中包含一些属性和方法,类可以创建不同的实例,调用他们所需要的方法,其中有两个类的方法。

class packet;//packet为类的名字
	//数据或类属性
	bit [3:0] command;
	bit [4:0] address;
	integer status;
	//初始化
	function new();//new函数是sv本身有的,也叫构造函数 也可
	//以带参数,那么创建对象的时候可以用,没有就默认变量
		command = IDLE;//赋默认初始值
		address = 4'b0;
	endfunction
	
	//类的方法
	//公共访问入口
	task clean();
		command = 0;
		address = 0;
	endtask
	
	function integer current_status();
		current_status = status;
	endfunction
endclass
           

在上例子中,在使用一个类的时候,需要先声明一个类型为该类的变量(为这个变量保存一个对象句柄),然后使用new函数产生该类的一个对象。

Packet p;// 声明一个为packet数据类型的变量,相当于即将要创建对象的指针
p = new;//,调用类里面的构造函数进行初始化。将变量初始化成packet新分配的一个对象,开辟一个空间
           

变量p保存了一个对象句柄(handle/类似于指针),该句柄指向了Packet类的一 个对象。缺省情况下,未初始化的对象句柄被设置成一个特殊值null。比如c语言中int a;没有赋值情况下就是null。 我们可以通过将一个对象的句柄与null进行比较来确定对象是否已被初始化。可以通过p.* 访问对象里面的类属性或者方法。

packet p = new;
p.command = 0;//对类属性的访问
p.address = $random;
status = p.current_status(); //对方法的访问
           

其中p为对象的handle,对对象的引用通过“.”的方式访问对象的属性与方法,p相当于指针。

下例为对多个对象分配地址

packet t1, t2;
t1 = new;//首先开辟一个空间,初始化了对象,t1指针指向这个对象
t2 = t1;//t2指针也指向和t1一样的对象,同种类型才可以这样
t1 = new;//又开辟新的空间,t1指向新开空间的对象
           
packet t1;
t1 = new();//首先开辟一个空间,t1指向对象	
t1 = new();//指向新的对象,之前的就没有指针指向,会被回收,释放内存
           

静态变量

对于同一个类,例化不同对象,里面的值的变化各不相关。有时我们需要共享所有对象里面的一个类属性或者方法。就要使用关键字static,称为静态变量。

class packet;
	static integer a = $fopen("data","r");
endclass

packet p1,p2;
p1 = new;
p2 = new;
p2.a = 2;//等价于p1.a = 2
           

对静态变量的访问

1.通过handle来访问,2通过类名来访问,即使该类没有实例也可通过操作符 ::

class packet;
	static int count = 0;
	...
endclass

initial begin
$display("%d packet is apple", packet::count);
end
           

静态方法

方法也可以被声明成静态的。它就像一个常规的子例程一样可以在类的外部被调用,即使没有该类的实例。

一个静态方法不能访问非静态的成员(类属性和方法),**但它可以直接访问静态类属性或调用同一个类的静态方法。**在一个静态方法体内部访问非静态成员或访问特殊的this句柄是非法的,并且会导致一个编译错误。

静态方法不能是虚拟的。

class packet;
	static config,cfg;
	static int count = 0;
	int id;
	static function void display_statics();
		$display("packet cfg.mode = %s, count = %d",
					cfg.mode,name(),count);
	endfunction
config cfg;//类的实例 另外定义
initial begin
	cfg = new(MODE_ON);
	packet::cfg = cfg;//创建的对象赋给静态的packet里面的cfg
	packet::display_statics()
end
           

在类外面定义方法

Systemverilog中,可以将类的方法的原型定义(方法名和参数)放在类的内部,而方法的程序体放在类的外部。通过关键字extern可以实现方法的声明,然后通过作用域操作符::来表名这个函数作用于哪个类。

class packet;
	extern function void display();
endclass

function void packet::display();
...
endfunction
           

this关键字

如果在类的深处,想要引用类一级的变量, 可以通过this关键字很方便的做到。this关键字被用来明确地弓|用当前实例的类属性或类方法。

this关键字应使用在非静态的类方法中,否则会发布一个错误。

class packet;
	string name;
	function new(string name);
		this.name = name;//这个指的就是上一级的name
	endfunction
endclass

packet p1 = new(2);//等于那个string name等于2
           

在一个类中调用另外一个类:可以在一个类的内部,通过先定义另一个类的变量然后再function对另一个类进行new,然后还可以在这个类中通过handle访问另外的类,用“."访问。

对象的复制:下图产生了一个变量p1,它保存了一个 类型为Packet的类对象的句柄, 但p1的初始值为null。在Packet类型的一个实例被产生之前,对象并不存在,并且p1没有包含一个真实的句柄。

通过new操作符,产生了一个对象,并且将p1赋值给p2。那么,此时仍然只有一个对象,它可以使用p1或p2引用。

注意: new仅仅执行一次,所以只产生了一个对象。

packet p1;
p1 = new;
packet p2;
p2 = p1;
           

通过new操作符拷贝对象

packet p1,p2;
p1 = new;
p2 = new p1;
           

最后一条语句使得new第二次执行,因此产生了一个新的对象p2,它的类属性拷贝自p1。

这种方式的拷贝被称作浅拷贝(shallow copy) 。所有的变量都被拷贝:整数、字符串、实例句柄等。然而其中包含的对象没有被拷贝,拷贝的是它的句柄;与前面的例子一样,产生了相同对象的两个名字。(p1中包含的类不会拷贝,只是拷贝了句柄)

类似于静态变量,下图后后面的对象的数值在两个句柄里面是一样的

继承

SystemVerilog支持单继承,也就是说,每一个子类都由一 个单的父类继承而来。继承是面向对象编程的重要概念。

使用继承可以定义子类型,在子类型中增加新的方法和数据。被继承的类一般称为基类或者父类 ,得到的新类一般称为子类。

Systemverilog中使用extends来实现继承。

class packet;
	int addr, data;
	function new();

	endfunction
endclass

class ppp extends packet;//ppp继承packet
	string s;
	function ppp get_next();

	endfunction
endclass
           

例如:子类除了具有父类的属性和方法外,还可以扩展自己的属性和方法,例如本例中,子类ppp不仅具有父类的addr,data等属性,还扩展了自己的方法get_ next。

super关键字在一个继承内部使用,可以用来引用父类的成员变量。当父类和子类的变量命名不冲突时,在子类对父类的变量进行操作可以不用super关键字,如果冲突则需要。

class packet;
	int a,b;
	function new();
	.
	.
	endfunction
endclass
////////////////////////////
class link_packet extends packet;
	int c,d;
	function new();
		super.new();
		//也可以调用方法,还是相同操作
	endfunction
endclass
           

子类在实例化的时候会调用类方法new()。在函数中定义的任何代码执行之前,new( )执行的一个动作需要调用其父类的new()方法,并且会沿着继承树按这种方式一直向上调用。因此,所有的构造函数都会按正确的顺序调用,它们都是起始于根基类并结束于当前的类。如果父类的初始化方法需要参数,那么会有两种选择:

1)总是提供相同的参数。

2)使用super关键字

function new();//更加通用
	super.new(5);//必须是函数的第一条语句
endfunction
           

继承的一些特性

1)数据隐藏

2)在SystemVerilog中,未被限定的类属性和方法是public的,它们对访问对象名字的任何人都是有效的。即所有的类属性和方法都毫无限制地在类外可见。

3)然而,有时我们希望通过隐藏类属性和类方法来限制在类的外部访问他们,而且防止仅对类内部有效的类属性被偶然地修改。因此需要对这些属性和方法做隐藏。

4)Systemverilog通过local和protected关键字来实现数据的隐藏。

protected关键字

-一个被标识成protected的类属性或方法具有local成员的所有特性,但是它可以被继承以及对子类可见。

class p;
	protected int a;//可以被继承,对子类可见
endclass
           

local关键字

一个被标识成local的成员仅对类内的方法有效。而且这些本地成员在子类内是不可见得。以local来声明某些member,这些member只能被这个class使用,继承的子类无法使用。当然,访问本地类属性或方法的非本地方法可以被继承,并且作为子类的方法它可以正确地工作。local类似C+ +的private。

class ppp;
	local int i;
	function int compare(ppp other);
		compare = (this.i == other.i);//当子类继承该父类,
		//并且采用compare函数时候,可以通过this关键字对i进行访问
	endfunction
endclass
           

继承总结

继承后的类可以实现以下功能:

子类继承了父类的属性和方法,并可以修改。可以添加新的属性和方法。

在实现以上功能的同时需要满足一定的规则:

子类可以重写父类中的属性和方法,如果一个方法被重写,其必须保持和父类的原有定义一致的参数

子类可以通过super操作符来引用父类中的方法和成员

被声明为local的数据成员和方法只能对自己可见,对外部和子类都不可见,对声明为protected的数据成员和方法,对外部不可见,对自身和子类可见

多态

抽象类

我们可以产生一组类,它们可以看作是来源于一个共用的父类。此父类只定义公共的模型,并不会实例化,这样的类我们定义成抽象的类,virtual class,抽象类不能实例化。

虚方法

虚方法是基本的多态构造,通过关键字virtual来实现。子类中的虚拟方法重写应具有与父类匹配的参数类型,相同的参数名称,相同的限定符以及与原型相同的方向。

OOP多态

先看一个例子

class packet;
	int	i;
	function int get();
	get = i;
	endfunction
endclass

class ppp extends packet;
	int i = 2;
	function int get();
	get = -i;
	endfunction
endclass

ppp lp = new;
packet p = lp;
j=p.i;//j=1
j=p.get();//j = 1
           

创建ppp类的对象称为lp,将lp赋值给packet的handle p,通过p调用get()。子类的指针不可以指向父类,父类指针可以指向子类。

systemverilog之OOPOOP的语法介绍

Virtual

Systemverilog中多态通过virtual关键字来实现,多态性使得父类中的一个变量 (变量就是lp或者p)能够保存子类对象,并且能够直接从父类变量中引用这些子类的方法。

多态性如下:

如果方法不是virtual方法,则调用此方法时,由handle的类型来决定调用哪个方法。(上面例子不是virtual方法,handle p的类型是父类)

如果方法是virtual方法,则调用方法时,由object的类型来决定调用哪个方法。

class packet;
	int	i;
	virtual function int get();
	get = i;
	endfunction
endclass

class ppp extends packet;
	int i = 2;
	virtual function int get();
	get = -i;
	endfunction
endclass

ppp lp = new;
packet p = lp;//父类句柄指向子类对象
j=p.i;//j=2
j=p.get();//j = -2
           

实现多态1)方法的重写,2)有无virtual关键字 3)句柄的复制

补充知识

上图的p和lp是关于对象的一个引用,p和lp是一个handle(句柄)/指针。那些对象指的是object。

强制类型转换

将一个子类变量赋值给层次树中较高的父类变量是合法的。将一个父类变量直接赋值给一 个子变量则是非法的。(上面例子)

如果父类句柄引用了指定子类的句柄(即父类已经指向了子类),那么将一个父类句柄赋值给一个子类变量则是合法的。就是虽然这个句柄的类型是父类。但是指向的对象类型是子类

为了检查赋值是否合法,需要使用动态强制类型转换函数$cast,语法如下:

$cast(dest_handle,source_handle);

常量类属性

类属性可以通过const限定符声明为只读的。由于类属性是动态的对象,类属性允许有两种形式的只读变量:全局常量和实例常量。

全局常量类属性是那些在声明中包含了初始值得常量类属性。它们与其它的const变量类似,也就是它们不能在除声明之外的其它地方赋值。

实例常量在声明中不包含初始值,仅包含const限定符。这种类型的常量可以在运行时赋一个值,但赋值只能在对应的类构造器(new函数)中完成一次。

典型情况下,全局常量还能被声明成static,因为它们对于类的所有实例是共享的。然而,一个实例常量不能声明成static,因为它们在构造器不接受所有的赋值。

参数化的类

如果定义一个基本类,它的对象在实例化的时候可以具有不同的数组尺寸或数据类型,这种功能通常很有用,可以使用参数化的类来实现。普通的Verilog参数机制可以用来参数化一个类, 例如:

class vector #(int size  = 1);
	bit [size-1:0] a;
endclass
vector #(10) vten;
vector #(.size(2)) vtwo;
typedef vector#(4) vfour;
           

参数话一个类,也可以将数据类型作为参数,如下例定义了一个基本的stack类,它可以使用任意的数据类型实例化:

任何类型都可以作为参数,包括用户定义的类型

class stack #(type T = int);//初始为int型
local T item[];

endclass

stack is;
stack #(bit[1:10]) bs;
stack #(real) rs;
           

typedef类

有时一个类变量需要在类本身被声明之前声明。

例如,如果两个类中的每一个类都需要另外一 个类的句柄。 在编译器处理第一个类的声明期间,编译器遇到了第二个类的引用,而这个弓|用是未定义的,因此编译器会将它标记为一个错误。

通过typedef可以解决此问题,为第二个类提供提前声明,例如:

typedef class C2;//C2声明为class类型
class C1;
	C2 c;
endclass

class C2;
	C1 c;
endclass
           

继续阅读