systemverilog之OOP

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

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)总是提供相同的参数。

class son_packet extends packet(5);

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 class packet;

虚方法
虚方法是基本的多态构造,通过关键字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()。子类的指针不可以指向父类,父类指针可以指向子类。
在这里插入图片描述
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
  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值