八、类和对象

本文详细介绍了SystemVerilog中的类(class)、继承(inheritance)、句柄与对象、构造函数、类型化构造函数、多态性以及参数化类的使用。涵盖了类声明、继承关系、构造函数调用、类与对象的区别、以及如何处理浅拷贝与深拷贝。同时探讨了抽象类和纯虚方法在设计模式中的应用。
摘要由CSDN通过智能技术生成

class

什么是class

class是一种用户定义的数据类型,一种OOP构造,可用于封装数据(属性)和对数据进行操作的任务/函数(方法)。这是一个例子:

class myPacket;
	bit [2:0] header;
	bit		 encode;
	bit [2:0] mode;
	bit [7:0] data;
	bit 	stop;
	function new (bit [2:0] header = 3'h1, bit [2:0] mode = 5);
		this.header = header;
		this.encode = 9;
		this.mode = mode;
		this.stop = 1;
	endfunction
	
	function display ();
		$display ("Header = 0x%eh, Encode = %b, Mode = 9x%h, Stop = %0b", this header,
					 this. encode, this. mode, this. stop);
	endfunction
endclass

在上面的例子中有几个关键点需要注意:
function new ()被称为构造函数,并在对象创建时自动调用。
this关键字用于引用当前类。通常在一个类中使用来引用它自己的属性/方法。
display()是一个函数,这是正确的,因为显示值不会消耗模拟时间。
function new ()参数有默认值

什么是继承

继承是OOP中的一个概念,它允许我们扩展一个类以创建另一个类,并可以从新类对象的句柄访问原始父类的所有属性,和方法。该方案背后的想法是允许开发人员将新属性和方法添加到新类中,同时仍保持对原始类成员的访问。这使我们可以在完全不触及基类的情况下进行修改。

ExtPacket是扩展的,因此是Packet的子类。作为一个子类,它从其父类继承属性和方法。如果在父类和子类中都存在同,名的函数,那么它的调用将取决于用于调用该函数的对象句柄的类型。在下面的示例中, Packet和ExtPacket都有一个名为的函数display() 。当这个函数被子类句柄调用时,子类display()函数将被执行。如果这个函数被父类句柄调用,那么父类display()函数将被执行。

class Packet;
	int addr;
	function new (int addr);
		this.addr = addr;
	endfunction
	
	function display ();
		$display ("[Base] addr=0x%0h", addr);
	endfunction 
endclass
// A subclass called 'ExtPacket' is derived from the base class 'Packet' using 
// 'extends' keyword which makes 'EthPacket' a child of the parent class 'Packet' 
// The child class inherits all variables and methods from the parent class 
class ExtPacket extends Packet;
	
	// This is a new variable only available in child class 
	int data;
	function new (int addr, data);
		super.new (addr); // Calls 'new' method of parent class 
		this.data = data;
	endfunction
	
	function display ();
		$display ("[Child] addr=0x%0h data=0x%0h", addr, data);
	endfunction
endclass

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	initial begin
		bc = new (32'hface_cafe);
		bc.display ();

		sc = new (32 'hfeed_feed, 32'h1234_5678);
		sc.display ();
	end 
endmodule

类句柄与对象

什么是类句柄

诸如下面的pkt之类的类变量只是知道该对象的名称。它可以持有Packet类对象的句柄,但在分配某些东西之前它总是如此null 。此时,类对象还不存在。

// Create a new class with a single member called 
// count that stores integer values 
class Packet;
	int count;
endclass

module tb;
	// Create a "handle" for the class Packet that can point to an 
	// object of the class type Packet
	// Note: This "handle" now points to NULL
	Packet pkt;
	initial begin
		if (pkt == null)
			$display ("Packet handle 'pkt' is null");
			// Display the class member using the "handle"
			// Expect a run-time error because pkt is not an object 
			// yet, and is still pointing to NULL. So pkt is not 
			// aware that it should hold a member
		$display ("count = %d", pkt. count);
	end 
endmodule

什么是类对象

new()只有在调用它的函数时才会创建该类的实例。要再次引用该特定对象,我们需要将其句柄分配给Packet类型的变是

// Create a new class with a single member called 
// count that stores integer values 
class Packet;
	int count;
endclass

module tb;
	// Create a "handle" for the class Packet that can point to an 
	// object of the class type Packet
	// Note: This "handle" now points to NULL
	Packet pkt;
	initial begin
		if (pkt == null)
			$display ("Packet handle 'pkt' is null");

		// call the new() function of this class
		pkt = new();
			
		if(pkt == null)
			$display("What's wrong,pkt is still null");
		else
			$display("Packet handle 'pkt' is now pointing to an boject, and not null");
		
		// Display the class member using the "handle"
		$display ("count = %d", pkt. count);
	end 
endmodule

当两个句柄都指向同一个对象时会发生什么?

如果我们将pkt分配给一个名为pkt2的新变量,那么新变量也将指向pkt中的内容。

// Create a new class with a single member called 
// count that stores integer values
class Packet;
	int count;
endclass
module tb;
	// Create two "handles" for the class Packet 
	// Note: These "handles" now point to NULL
	Packet pkt, pkt2;
	
	initial begin
	// Call the new() function of this class and 
	// assign the member some value
	pkt = new();
	pkt.count = 16'habcd;

	// Display the class member using the "pkt" handle
	$display ("[pkt] count = 0x%eh", pkt.count);
	
	// Make pkt2 handle to point to pkt and print member variablep
	kt2 = pkt;
	$display ("[pkt2] count = 9x%h", pkt2. count);
	end 
endmodule

现在我们有两个句柄pkt和pkt2指向同一个类类型Packet的实例。这是因为我们没有为pkt2创建一个新实例,而是只为pkt指向的实例分配了一个句柄。

构造函数

构造函数只是一种创建特定类数据类型的新对象的方法。 C/C++需要复杂的内存分配技术,不正确的取消分配可能导致内存泄漏和其他行为问题。SystemVerilog虽然不是一种编程语言,但能够简单地构造对象和自动垃圾收集(类似于Java)。

构造函数可以声明为local or protected,但不能声明为static or virtual

隐式调用类构造函数

如果该类没有显式编码的函数,则会自动提供new()一个隐式的新方法。在这种情况下, addr被初始化为零,因为它是bit默认值为零的类型。

class Packet;
	bit [31:0] addr;
endclass

module tb;
	initial begin
		Packet pkt = new;
		$display("addr = 0x%0h", pkt.addr);
	end
endmodule

明确定义类构造函数时

class Packet;
	bit [31:0] addr;
	
	function new();
		addr = 32'hfade_cafe;
	endfunction
endclass

module tb;
	initial begin
		Packet pkt = new;
		$display("addr = 0x%0h", pkt.addr);
	end
endmodule

在上面的例子中,变量声明创建了一个Packet类的对象,并会自动调用该类中的new()函数。该new()函数称为类构造函数,是一种用一些值初始化类变量的方法。请注意,它没有返回类型并且是非阻塞的。

继承类的行为

派生类的新super.new()方法将首先使用,基类构造函数完成后,派生类中定义的每个属性都将初始化为默认值,之后将执行新方法中的其余代码。

// Define a simple class and initialize the class member "data" in new() function 
class baseclass;
	bit [15:0] data;
	function new ();d
		data = 16'hface;
	endfunction 
endclass

// Define a child class extended from the above class with more members
// The constructor new() function accepts a value as argument, and by default is 2 

class subclass extends baseclass;
	bit [3:0] id;
	bit [2:9] mode= 3;
	
	function new (int val = 2);/
	// The new() function in child class calls the new function in 
	// the base class using the "super" keyword
	super. new ();
	
	// Assign the value obtained through the argument to the class member 
	id= val;
	endfunction 
endclass
module tb;
	initial begin
	// Create two handles for the child class 
	subclass sc1, sc2;
	// Instantiate the child class and display member variable values 
	sc1 = new ();
	$display ("data=0x%0h id=%0d mode=%0d", sc1.data, sc1.id, sc1.mode);

	// Pass a value as argument to the new function in this case and print 
	sc2 = new (4);
	$display ("data=ex%0h id=%0d mode=%0d", sc2. data, sc2.id, sc2. mode);
	end
endmodule

类型化构造函数

这里的区别在于您可以调用new()子类的函数,但在单个语句中将其分配给基类的句柄。这是通过new()使用范围运算符引用子类的函数来完成的::,如下所示。

class C
endclass

class D extends C;
endclass

module tb;
	initial begin
		C c = D::new;
	end
endmodule

基类C的变量c现在引用了一个新构造的D类型的对象,这实现了与下面给出的代码相同的效果。

...
module tb;
	initial begin
		D d = new;
		C c = d;
	end
endmodule

this关键字

this关键字用于引用当前实例的类属性、参数和方法。它只能在非静态方法、约束和覆盖组中使用。this基本上是一个预定义的对象句柄,它引用用于调用所使用的方法的对象

class Packrt;
	bit [31:0] addr;

	function new(bit [31:0] addr);
		this.addr = addr;
	endfunciton
endclass

除非赋值有歧义,否则this通常不需要使用关键字来指定对方法中类成员的访问。

super关键字

在super子类中使用关键字来引用基类的属性和方法。如果属性和方法已被子类覆盖,则必须使用关键字来访问它们。

class Packet;
	int addr;
	function display();
		$display("[Base] addr=0x%0h", addr);
	endfunciton
endclass

class extPacket extends Packet;

	function display();
		super.display();
		$display("[Child] addr=0x%0h", addr);
	endfunction
	
	function new();
		super();
	endfunction
	
endclass

module tb;
	Packet p;
	extPacket ep;
	initial begin
		ep = new();
		p = new();
		ep.display();
	end
endmodule

typedef类

有时,由于在声明类本身之前使用了类变量,编译器会出错。例如,如果两个类之间需要一个句柄,就会弹出先有鸡还是先有蛋的经典难题。这是因为编译器处理第一个类,它发现对第二个类的引用是尚未声明的类。

class ABC;
	DEF def;	//error:DEF has not been declared yet
endclass

clsss DEF;
	ABC abc;
endclass

用法

在这种情况下,必须使用关键字为第二类提供前向声明。typedef当编译器看到一个typedef类时,它将知道稍后会在同一个文件中找到该类的定义。

typedef class DEF;
class ABC;
	DEF def;	//error:DEF has not been declared yet
endclass

clsss DEF;
	ABC abc;
endclass

通过使用typedef,DEF被声明为类型class,后来被证明是相同的,其实不必在typedef语句中指定DEF是class类型

typedef DEF;
class ABC;
	DEF def;
endclass

typedef与参数化类使用

typedef也可以用于具有参数化端口列表的类

typedef XYZ;

module top;
	XYZ #(8'h3f, real)			  xyz0; // positional parameter override
	XYZ #(.ADDR(8'h60), .T(real)) xyz1; // named parameter override 
endmodule
class XYZ #(parameter ADDR = 8 'hee, type T = int);
endclass

多态

多态性允许使用基类类型的变量来保存子类对象并直接从超类变量引用这些子类的方法。virtual如果父类方法在本质上,它还允许了类方法具有与其父类不同的定义。

类句柄只是一个容器,用于保存父类或子类对象。了解父类和子类的相互处理关系在SystemVerilog中是很重要的。

将子类分配给基类

```cpp
class Packet;
	int addr;
	function new (int addr);
		this.addr = addr;
	endfunction
	
	function display ();
		$display ("[Base] addr=0x%0h", addr);
	endfunction 
endclass
// A subclass called 'ExtPacket' is derived from the base class 'Packet' using 
// 'extends' keyword which makes 'EthPacket' a child of the parent class 'Packet' 
// The child class inherits all variables and methods from the parent class 
class ExtPacket extends Packet;
	
	// This is a new variable only available in child class 
	int data;
	function new (int addr, data);
		super.new (addr); // Calls 'new' method of parent class 
		this.data = data;
	endfunction
	
	function display ();
		$display ("[Child] addr=0x%0h data=0x%0h", addr, data);
	endfunction
endclass

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	initial begin
		sc = new (32 'hfeed_feed, 32'h1234_5678);

		//assign subclass to basecalss handle
		bc = sc;
		
		bc.display();
		sc.display();
	end 
endmodule

simulation log

[Basel addr=0xfeedfeed
[Child] addr=0xfeedfeed data=0x12345678

即使bc指向子类实例,当从bc调用函数时,它仍然调用基类中的函数。这是因为函数是根据句柄的类型而不是句柄指向的对象的类型来调用的。现在让我们尝试通过一个基类句柄来引用一个子类成员,你会得到一个编译错误。

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	initial begin
		sc = new (32 'hfeed_feed, 32'h1234_5678);

		//assign subclass to basecalss handle
		bc = sc;
		$display("data = 0x%0h", bc.data);		
	end 
endmodule

simulation log

$display("data = 0x%0h", bc.data);
ncvlog: *E, NOTCLM (inheritance.sv, 49|36) : data is not a class item.

将基类分配给子类

将基类类型的变量直接分配给其子类类型之一的变量是非法的,因此会出现编译错误。

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	initial begin
		bc = new (32'hfeed_feed);

		//assign baseclass object to subcalss handle
		sc = bc;
		bc.display();		
	end 
endmodule

simulation log

 sc = bc;
 ncvlog: *E, TYCMPAT (inheritance. sv, 56|12) : assignnent operator type check failed (expecting datatype compatibl e with 'class

virtual

父类中的方法可以声明为virtual允许所有子类使用不同的定义覆盖该方法,但包含返回类型和参数的原型应保持不变。

class Base;
	rand bit [7:0] addr;
	rand bit [7:0] data;
	// Parent class has a method called 'display' declared as virtual virtual 
	function void display(string tag="Thread1");
		$display ("[Base] %s: addr=0x%0h data=0x%0h", tag, addr, data);
	endfunction 
endclass
class Child extends Base;
	rand bit en;

	// Child class redefines the method to also print 'en' variable 
	function void display(string tag="Thread1");
	$display ("[Child] %s: addr=0x%0h data=0x%0h en=%0d", tag, addr, data, en);
	endfunction
endclass

我们看到由指向子类实例的基类句柄调用的方法最终会执行基类方法而不是子类中的方法。如果基类中的该函数被声明为virtual,那么只有子类方法才会被执行。

class Packet;
	int addr;
	function new (int addr);
		this.addr = addr;
	endfunction
	
	virtual function display ();
		$display ("[Base] addr=0x%0h", addr);
	endfunction 
endclass

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	initial begin
		sc = new (32 'hfeed_feed, 32'h1234_5678);
		bc = sc;
		bc.display();
	end 
endmodule

simulatiom log

 [Child] addr=0xfeedfeed data=0x12345678

$cast

可以将超类句柄分配给子类类型的变量

module tb;
	Packet bc; // bc stands for Baseclass
	ExtPacket sc; // sc stands for Subclass
	ExtPacket sc2; // sc2 stands for Subclass
	initial begin
		sc = new (32 'hfeed_feed, 32'h1234_5678);
		bc = sc;
		$cast(sc2, bc);
		sc2.display();
		$display("data=0x%0h", sc2.data);		
	end 
endmodule

simulation log

[Base] addr=0xfeedfeed data=0x12345678
[data]=0x12345678

静态变量和函数

静态变量

当类中的变量声明为static时,该变量将是所有类实例中的唯一副本。

class Packet;
	bit [15:0] addr;
	bit [7:0]  data;
	static int static_ctr = 0;
	 	   int ctr = 0;
	function new(bit [15:0] ad, bit [7:0] d);
		addr = ad;
		data = d;
		static_ctr++;
		ctr++;
		$display("static_ctr=%0d  ctr=%0d addr=0x%0h data=0x%0h", static_ctr, ctr, addr, data);
	endfunction
endclass

module tb;
	initial begin
		Packet p1, p2, p3;
		p1 = new(16'hdead, 8'h12);
		p2 = new(16'hface, 8'hab);
		p3 = new(16'hcafe, 8'hfc);
	end
endmodule
 static_ctr=1 ctr=1 addr=0xdead  data=0x12 
 static_ctr=2 ctr=1 addr=0xface  data=0xab
 static_ctr=3 ctr=1 addr=0xcafe  data=0xfc

会看到计数器在所有类对象p1,p2和p3 static之间共享,因此在创建三个数据包时将增加到3。另一方面,普通的计数器变量ctr没有声明为,因此每个类对象都有自己的副本。这就是为什么在创建所有三个对象之后ctr仍然为1的原因

静态函数

静态方法遵循所有类范围和访问规则,但唯一的区别是即使没有类实例化,它也可以在类外部调用。方法static无法访问非静态成员,但可以直接访问静态类属性或调用同一类的静态方法。静态方法也不virtual。使用类名的静态函数调用需要通过作用域运算符进行::

class Packet;
	static int ctr = 0;
	function new();
		ctr++;
	endfunction
	static function get_pkg_ctr();
		$display("ctr=%0d", ctr);
	endfunction
endclass

module tb;
	Packet pkt[6];
	initial begin
		for(int i = 0; i < $size(pkt), i++) begin
			pkt[i] = new;
		end
		Packet::get_pkt_ctr();
		pkt[5].get_pkt_ctr();
	end
endmodule

simulation log

ctr=6 
ctr=6

让我们添加一个名为mode的非静态成员,并尝试从我们的静态函数中调用它。

class Packet;
	static int ctr = 0;
	bit [1:0] mode
	function new();
		ctr++;
	endfunction
	static function get_pkg_ctr();
		$display("ctr=%0d mode=%0d", ctr, mode);
	endfunction
endclass

这是不允许的,会导致编译错误

 $display ("ctr=%Od mode=%od", ctr, mode) ;
 ncvlog: *E, CLSNSU  (static-function. sv, 10|40) : A static class method cannot access non static class members.

浅拷贝与深拷贝

浅拷贝

当pkt与新对象的构造函数 起使用时, pkt中的内容将被复制到pkt2中。

Packet pkt, pkt2;

pkt = new;
pkt2 = new pkt;

这种方法称为浅拷贝,因为所有变量都跨整数、字符串、实例句柄等进行复制,但嵌套对象并未完全复制。只有它们的句柄将分配给新对象,因此两个数据包都将指向同一个嵌套对象实例。为了说明这一点,让我们看一个例子。

class Header;
	int id;
	function new (int id);
		this.id = id;
    endfunction

	function showId();
		 $display ("id=0x%0d", id);
    endfunction
endclass

class Packet;
	int addr; 
	int data;
	Header hdr;
	function new (int addr, int data, int id);
		hdr = new (id);
		this.addr = addr;
		this.data = data;
	endfunction
	
	function display(string name);
		$display ("[%s] addr=0x%0h data=0x%0h id=%0d", name, addr, data, hdr.id);
    endfunction
endclass

module tb;
	Packet pl, p2;
	initial begin
		// Create a new pkt object called pl
		pl = new (32'hface_cafe, 32'h1234_5678, 26); 
		pl.display ("p1");
		
		// Shallow copy pl into p2: p2 is a new object with contents in pl 
		p2 = new pl;
		p2.display ("p2");
		
		// Now let's change the addr and id in pl
		pl.addr = 32'habed_ef12; 
		pl.data = 32'h5a5a_5a5a;
		pl.hdr.id = 17;
		pl.display("p1");

		// Print p2 and see that hdr. id points to the hdr in pl, while 
		// addr and data remain unchanged.
		p2.display ("p2");
	end 
endmodule
 [p1] addr=0xfacecafe   data=0x12345678 id=26
 [p2] addr=0xfacecafe   data=0x12345678 id=26

 [p1] addr=0xabcdef12   data=0x5a5a5a5a id=17
 [p2] addr=0xfacecafe   data=0x12345678 id=17

类Packet包含一个名为Header的嵌套类。首先,我们创建了一个名为p1的数据包并为其分配了一些值。然后使用浅拷贝方法将p2创建为p1的副本。为了证明只复制句柄而不是整个对象,修改了p1数据包的成员,包括嵌套类中的成员。当打印p2中的内容时,我们可以看到嵌套类中的id成员保持不变。

深拷贝

深拷贝是复制所有内容(包括嵌套对象)的地方,通常需要自定义代码来实现此目的。 systemverilog没有C++运算符重载的机制,但是我们可以封装一个函数达到此目的。

Packet p1 = new;
Packet p2 = new;
p2.copy(p1);

让我们在上面给出的实例中添加一个在Packet类中调用的自定义函数。

class Packet;
	...
	function copy (Packet p);
		this.addr = p.addr; 
		this.data = p.data; 
		this.hdr.id = p.hdr.id;
	endfunction
	...
endclass

module tb; 
	Packet pl, p2;
	initial begin
		pl = new (32' hface_cafe, 32' h1234_5678, 32' hla);
		pl.display ("p1");
		p2 = new (1, 2, 3);  // give some values 
		p2.copy(p1);
		p2.display("p2");

		// Now let's change the addr and id in pl
		pl.addr = 32'habcd_ef12;
		pl.data = 32'h5a5a_5a5a;
		pl.hdr.id = 32'h11;
		pl.display("pl");

		// Now let's print p2 - you'11 see the changes made to hdr id 
		// but not addr
		p2.display("p2");
	end
endmodule

我们在copy()这里调用了自定义函数而不是浅拷贝方法,因此Header对象的内容也应该被复制在p2中

[pl] addr=0xfacecafe 	data=0x12345678 id=26
[p2] addr=0xfacecafe	data=0x12345678 id=26
[pl] addr=0xabedef12	data=0x5a5a5a5a id=17
[p2] addr=0xfacecafe	data=0x12345678 id=26

参数化类

为什么我们需要对类进行参数化?

有时,编写一个可以以多种方式实例化以实现不同数组大小或数据类型的泛型类会容易得多。这避免了为特定功能(如大小或类型)重新编写代码的需要,而是允许将单个规范用于不同的对象。这是通过将SystemVerilog参数机制扩展到类来实现的。参数类似于指定类的本地常量。类可以为每个可以在类实例化期间覆盖的参数具有默认值。

语法

// Declare parameterized class
class <name_of_class> #(<parameters>);
class Trans #(addr = 32);

// Override class parameter
<name_of_class> #(<parameters>) <name_of_inst>;
Trans #(.addr(16)) obj;

例子

// A class is parameterized by #()
// Here, we define a parameter called "size" and gives it 
// a default value of 8.  The "size" parameter is used to 
// define the size of the "out" variable
class something #(int size = 8);
	bit [size-1:0] out;
endclass

module tb;

	// Override default value of 8 with the given values in #() 
	something #(16) sth1;			 // pass 16 as "size" to this class object
	Something #(.size (8)) sth2;	 // pass 8 as "size" to this class object
	typedef something #(4) td_nibble;// create an alias for a class with "size"=4 as "nibble" 
	td_nibble nibble;
	
	initial begin
		// 1.  Instantiate class objects 
		sth1 = new;
		sth2 = new;
		nibble = new;

		// 2.  Print size of "out" variable.  $bits() system task will return 
		// the number of bits in a given variable
		$display ("sth1.out = %0d bits", $bits(sth1.out));
		$display ("sth2.out = %0d bits", $bits(sth2.out));
		$display ("nibble.out = %0d bits", $bits(nibble.out));
	end 
endmodule
 sthl.out = 16 bits 
 sth2.out = 8 bits 
 nibble.out = 4 bits

将数据类型作为参数传递

在这种情况下,数据类型是参数化的,并且可以在实例化期间被覆盖。在前面的案例中,我们将参数定义为具有特定值。类似于C++的模板技术

// "T" is parrameter that is set to have a default value of "int"
// Hence "items" will be "int" by default
class stack #(type T = int)
	T item;
	
	function T add_a(T a);
		return item + a;
	endfunction
endclasss

module tb;
	stack 	    	   st;	//st.item is by default of int type
	stack  #(bit[3:0]) bs;	//bs.item will become a 4-bit vector
	stack  #(real)     rs;	//rs.item will become a real number

	intiial begin	
		st = new;
		bs = new;
		rs = new;

		// assign different values, and add 10 these values
		// Then print the result - note different values printed
		// that are affected by change in data type

		st.item = -456;
		$display("st.item = %0d", st.add_a(10));

		bs.item = 8'ha1;
		$display("bs.item = %0d", bs.add_a(10));

		rs.item = 3.14;
		$display("rs.item = %0.2f", rs.add_a(10));
	end
endmodule	
 st.item = -446
 bs.item = 11
 rs.item = 13.14

任何类型都可以作为参数提供,包括用户定义的类型,例如class和struct。

extern 关键字

class类定义可能会变得很长,在和之间有很多行endclass 。这使得很难理解类中存在的所有函数和变量,因为每个函数和任务都占用了很多行。
在方法声明中使用extern限定符表示实现是在该类的主体之外完成的。

class ABC
	...
	extern function void display();
	...
endclass

function void ABC::display()'
	$display("hello world");
endfunction

module tb;
	initial begin
		ABC abc = new();
		abc.display();
	end
endmodule

local

声明为local的成员仅可用于用一类的方法,并且不能被子类访问,但是,访问local成员的非本地方法可以被子类继承和覆盖。

从类外访问

class ABC
	byte public_var;
	local byte local_var;

	function void display();
		$display("public_var=0x%0h, local_var=0x%0h", public_var, local_var);
	endfunction
endclasss

module  tb;
	initial begin
		ABC abc = new();
		abc.display();

		$display("public_Var = 0x%0h", abc.public_var);
		$display("local_Var = 0x%0h", abc.local_var);
	end
endmodule
 $display ("local_var = 0x%0h", abc. local_var) ;
 ncvlog: *E, CLSNLO (testbench. sv, 24|47): Access to local member 'local_var' in class 'ABC' is not allowed here.

删除导致编译错误的行并看到了正确的输出。访问该local成员变量的唯一方法就是display()函数

module  tb;
	initial begin
		ABC abc = new();
		abc.display();

		$display("public_Var = 0x%0h", abc.public_var);
	end
endmodule

当被子类访问时

在此示例中,让我们尝试local从子类中访问成员,我们希望看到一个错误,因为local对子类也不可见。

class ABC
	local byte local_var;
endclasss

class DEF extends ABC;
	function show();
		$display("local_var=0x%0h", local_var);
	endfunction
endclass

module  tb;
	initial begin
		DEF def = new();
		def.display();
	end
endmodule
$display ("local_var = 0x%0h", local_var) ;
ncvlog: *E, CLSNLO (testbench. sv, 10|43) : Access to local member 'local_var' in class ' ABC' is not allowed here.

抽象类和纯虚方法

抽象类

抽象类通过virtual关键字定义

语法:

virtual class <class_name>
	// class definition
endclass

然而抽象类不能被直接实例化,这个类可以扩展形成其他可以被实例化的子类。在验证工作中,我们期望搭建的验证环境能高度重用,并且易于扩展。抽象类和纯虚方法就使我们可以搭建一个通用的验证环境模板,并基于该模板扩展为期望的验证环境。

virtual class BaseClass;
	int data;
	function new();
		data = 32'hc0de_c0de;
	endfunction
endclass;

module tb;
	BaseClass  base;
	initial begin
		base = new();
		$display("data = 0x%0h", base.data);
	end
endmodule
 base = new () ;
 ncvlog: *E, CNIABC (testbench.sv, 12|5) : An abstract (virtual) class cannot be instantiated.

抽象类可以像任何其他systemverilog类一样使用extends关键字进行扩展

virtual class BaseClass;
	int data;
	function new();
		data = 32'hc0de_c0de;
	endfunction
endclass;

class ChildClass extends BaseClass;
	function new();
		data = 32'hfade_fade;
	endfunction
endclass

module tb;
	ChildClass  child;
	initial begin
		child = new();
		$display("data = 0x%0h", child.data);
	end
endmodule

纯虚方法

纯虚方法:这是一种没有实体的方法原型。这样的方法只需要在抽象类中指定一个原型,并且在子类中定义实现。

1、一个由抽象类扩展而得来的类只有在所有虚方法都有实体的时候才能被例化。
2、关键词“pure”表明一个方法声明是原型定义,而不仅仅是空的虚方法。
3、 纯虚方法只能在抽象类中定义,但是抽象类中也可以定义非纯方法。

virtual class BaseClass;
	int data;
	pure virtual function int getData();
endclass;

class ChildClass extends BaseClass;
	virtual function getData();
		data = 32'hfade_fade;
		return data;
	endfunction
endclass

module tb;
	ChildClass  child;
	initial begin
		child = new();
		$display("data = 0x%0h", child.getData());
	end
endmodule

图解

借用一个比较火的图总体说明一下
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值