天天看點

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
           

繼續閱讀