我的 System Verilog 学习记录(9)

news2025/1/17 23:15:47


引言

本文简单介绍 SystemVerilog 的类。

前文链接:

我的 System Verilog 学习记录(1)

我的 System Verilog 学习记录(2)

我的 System Verilog 学习记录(3)

我的 System Verilog 学习记录(4)

我的 System Verilog 学习记录(5)

我的 System Verilog 学习记录(6)

我的 System Verilog 学习记录(7)

我的 System Verilog 学习记录(8)



啥叫类 ?

类是一种用户定义的数据类型,是一种面向对象的结构,可以用于数据(参数)、操作数据的任务/函数(方法)的封装。示例如下:

上例中有几个关键点:

  • function new() 称为构造函数,在创建对象时自动调用;
  • this 关键字用于指向当前类,通常在类的内部使用指向它自己的参数/方法;
  • display() 是一个立即执行的函数,不消耗仿真时间;
  • function new() 参数具有缺省值,因此第6行代码,(空白行不算)(下图)将创建一个值为[3‘h1,0,2’h5,1]的包对象

如何获取类内信号 ?

为此,您必须创建类的一个对象,该对象可用作其属性和方法的句柄。

 仿真:

如何创建类数组 ?

这和 int 类型数组方式类似。

由于每个myPacket对象在构造函数new()中没有参数,因此应用了缺省值。

啥是继承 ?

假设您希望拥有一个包含 myPacket 所有属性/方法的类,并且能够在不更改 myPacket 的情况下向其中添加更多内容,最好的方法是通过继承。在下面的示例中,networkPacket 使用 extend 关键字继承 myPacket 的属性/方法。要调用基类(myPacket)的函数,使用 super 关键字。

抽象/虚拟类是啥 ?

如果使用 virtual 关键字创建抽象类,则不能创建类的对象。如果您不希望其他人创建类的对象,而是强制用户保留抽象类作为基类并扩展它以创建子类以满足其目的,则这是很有用的。

类的句柄和对象

类的句柄是啥 ?

像下面的 pkt 这样的类变量只是知道该对象的名称。它可以保存类Packet的对象的句柄,但在分配到某个东西之前,它始终为空(null)。此时,类对象还不存在。

类句柄示例

类的对象是啥 ?

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

类对象示例

仿真log :

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

如果将 pkt 赋值给 pkt2 ,新变量也会指向与 pkt 相同的内容。

仿真log :

现在有两个句柄,pkt 和 pkt2 指向Packet 类的同一个实例。因为我们还没有为 pkt2 创建实例,只是指定了 pkt 的一个实例句柄。、

类的构造函数

构造函数可以为特定数据类型创建对象。

构造函数

当显式定义类构造函数时

C/C++需要复杂的内存分配技术,不适当的释放可能会导致内存泄漏和其他行为问题。SystemVerilog虽然不是一种编程语言,但能够简单地构造对象和自动垃圾回收。

 仿真log:

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

当显式调用类构造函数时

如果类内没有显示定义 new() 函数,将会自动提供一个 new方法供创建对象时使用。这种情况下,addr 被初始化为0,因为bit类型的数据初值为0。

继承类的行为

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

 仿真结果:

在上面的示例中,当创建一个子类对象时,它将首先调用子类的new()函数,当被super关键字调用时,它将从那里分支到baseClass的new()方法。接下来将初始化数据,控制返回给子类。new 方法将在id初始化为4之后完成。

当 new 函数被声明为 静态/虚拟 函数

构造函数可以声明为 local 或者 protected,但不可声明为 static 或 virtual。否则仿真器会报错,示例如下:

类型化的构造函数

这里的不同之处在于,您可以调用子类的new()函数,但在单个语句中将其分配给基类的句柄。这是通过使用范围操作符 :: 引用子类的new()函数来实现的,如下所示。

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


this 指针

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

示例

使用它的一种非常常见的方式是在初始化块中。

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


super 关键字

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

示例

super关键字只能在派生自基类的类范围内使用。下面显示的代码将有编译错误,因为 extPacket不是Packet的子级。请注意,new方法是为每个类定义隐式定义的,因此我们不需要在基类包中定义新的定义。

现在让我们看看当extPacket是类Packet的派生时的输出:

访问基类方法

在下面的示例中,基类的display方法是使用super关键字从子类的display方法中调用的。


typedef 类

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

编译错误

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

应用

通过使用typedef,DEF被声明为类 类型,后来被证明是相同的。没有必要在typedef语句中指定DEF是类 类型。

用 typedef 定义参数化的类

也可以在具有如下所示的参数化端口列表的类上使用typedef。


继承

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

示例

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

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();
	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end
endmodule

Questa Sim仿真结果:

当您试图将子类实例分配给基类句柄时,就会变得更加棘手。


多态性

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

父类子类赋值

类句柄其实就是一个存放子类或者父类对象的容器。了解父类如何处理保存子对象,子类如何处理保存父对象,这一点很重要。

子类赋值给基类

以上例中的示例,将子类的实例赋值给基类句柄。

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();
	
/*	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD  CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end*/

// 子类实例赋值给基类句柄测试
	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
		CLASS_CHILD_OBJ1.display1();
	end
endmodule

Questa Sim执行仿真:

尽管基类指针指向子类实例,但当从基类对象调用 display1() 函数时,它仍然调用基类中的display1()函数。这是因为该函数是基于句柄的类型而不是句柄所指向的对象类型来调用的。现在,让我们尝试通过基类句柄引用一个子类成员,这样会出现编译错误。

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();

/*	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD  CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end*/

// 子类实例赋值给基类句柄测试
/*	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
		CLASS_CHILD_OBJ1.display1();
	end*/
	
// 基类句柄引用子类成员
	CLASS_PARENT CLASS_PARENT_OBJ2;
	CLASS_CHILD  CLASS_CHILD_OBJ2;

	initial
	begin
		CLASS_CHILD_OBJ2 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ2 = CLASS_CHILD_OBJ2;

		$display("data = %0h",CLASS_PARENT_OBJ2.data);
	end
endmodule

编译时报错: 

基类赋值给子类

将一个超集类型变量赋值给其子类类型变量是不合法的,编译时会报错。

测试代码:

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();

/*	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD  CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end*/

// 子类实例赋值给基类句柄测试
/*	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
		CLASS_CHILD_OBJ1.display1();
	end*/

// 基类句柄引用子类成员
/*	CLASS_PARENT CLASS_PARENT_OBJ2;
	CLASS_CHILD  CLASS_CHILD_OBJ2;

	initial
	begin
		CLASS_CHILD_OBJ2 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ2 = CLASS_CHILD_OBJ2;

		$display("data = %0h",CLASS_PARENT_OBJ2.data);
	end*/

// 基类变量赋值给子类成员
	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		CLASS_CHILD_OBJ3 = CLASS_PARENT_OBJ3;

		CLASS_PARENT_OBJ3.display1();
	end
endmodule

但是,如果超类句柄引用的对象与子类变量的赋值兼容,则 $cast() 可用于将超类句柄分配给子类类型的变量。

示例如下:

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();

/*	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD  CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end*/

// 子类实例赋值给基类句柄测试
/*	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
		CLASS_CHILD_OBJ1.display1();
	end*/

// 基类句柄引用子类成员
/*	CLASS_PARENT CLASS_PARENT_OBJ2;
	CLASS_CHILD  CLASS_CHILD_OBJ2;

	initial
	begin
		CLASS_CHILD_OBJ2 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ2 = CLASS_CHILD_OBJ2;

		$display("data = %0h",CLASS_PARENT_OBJ2.data);
	end*/

/*// 基类变量赋值给子类成员
	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		CLASS_CHILD_OBJ3 = CLASS_PARENT_OBJ3;

		CLASS_PARENT_OBJ3.display1();
	end*/

// 基类变量赋值给子类成员 ( $cast() )
	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		$cast(CLASS_CHILD_OBJ3,CLASS_PARENT_OBJ3);

		CLASS_PARENT_OBJ3.display1();
	end
endmodule

这样使用 $cast() 后,可以通过编译,但是运行时会报错。这是因为基类指针没有指向与子类相兼容的对象。

下面我们尝试将基类指针指向另一个子类对象,然后做与上相同的事情。此时的基类指针就像一个承载器。

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_class ();

/*	CLASS_PARENT CLASS_PARENT_OBJ;
	CLASS_CHILD  CLASS_CHILD_OBJ;

	initial
	begin
		CLASS_PARENT_OBJ = new(32'h00112233);
		CLASS_PARENT_OBJ.display1();

		CLASS_CHILD_OBJ = new(32'h44556677,32'h8899AABB);
		CLASS_CHILD_OBJ.display1();
	end*/

// 子类实例赋值给基类句柄测试
/*	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
		CLASS_CHILD_OBJ1.display1();
	end*/

// 基类句柄引用子类成员
/*	CLASS_PARENT CLASS_PARENT_OBJ2;
	CLASS_CHILD  CLASS_CHILD_OBJ2;

	initial
	begin
		CLASS_CHILD_OBJ2 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ2 = CLASS_CHILD_OBJ2;

		$display("data = %0h",CLASS_PARENT_OBJ2.data);
	end*/

/*// 基类变量赋值给子类成员
	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		CLASS_CHILD_OBJ3 = CLASS_PARENT_OBJ3;

		CLASS_PARENT_OBJ3.display1();
	end*/

// 基类变量赋值给子类成员 ( $cast() )
/*	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		$cast(CLASS_CHILD_OBJ3,CLASS_PARENT_OBJ3);

		CLASS_PARENT_OBJ3.display1();
	end*/

// 基类变量赋值给子类成员 ( $cast() + 指针转移 )
	CLASS_PARENT CLASS_PARENT_OBJ3;
	CLASS_CHILD  CLASS_CHILD_OBJ3,CLASS_CHILD_OBJ4;

	initial
	begin
		CLASS_PARENT_OBJ3 = new(32'h44556677);
		CLASS_CHILD_OBJ3 = new(32'h44556677,32'h8899AABB);

		CLASS_PARENT_OBJ3 = CLASS_CHILD_OBJ3;

		$cast(CLASS_CHILD_OBJ4,CLASS_PARENT_OBJ3);

		CLASS_CHILD_OBJ4.display1();
		$display("data = %0h",CLASS_CHILD_OBJ4.data);
	end
endmodule

Questa Sim 仿真结果:

虚拟方法

父类中的方法可以声明为虚拟方法,这样支持其子类以不同的定义覆盖此虚拟方法。但是包含返回类型和表达式的原型应该保持相同。


虚拟方法

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

示例

没有virtual

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_virtual ();
// 子类实例赋值给基类句柄测试
	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
	end
endmodule

仿真结果: 

有virtual

// 基类定义
class CLASS_PARENT ;
	int addr;

	function new (int addr);
		this.addr = addr;
	endfunction 

	virtual function void display1();
		$display("[Base Class] addr = 0x%0h",addr);
	endfunction
endclass 
// 子类定义
class CLASS_CHILD extends  CLASS_PARENT;
	int data;

	function new (int addr,data);
		super.new(addr);
		this.data = data;
	endfunction

	function void display1();
		$display("[Sub Class] addr = 0x%0h , data = 0x%0h",addr,data);
	endfunction
endclass 
// TestBench
module test_virtual ();
// 子类实例赋值给基类句柄测试
	CLASS_PARENT CLASS_PARENT_OBJ1;
	CLASS_CHILD  CLASS_CHILD_OBJ1;

	initial
	begin
		CLASS_CHILD_OBJ1 = new(32'h44556677,32'h8899AABB);
		CLASS_PARENT_OBJ1 = CLASS_CHILD_OBJ1;
		CLASS_PARENT_OBJ1.display1();
	end
endmodule

仿真结果: 

 由上面的仿真对比可以看出,当基类的 display1() 函数声明为 virtual 时,子类的 display1() 函数被执行。

关键的要点是,您应该始终将基类方法声明为虚拟的,以便已经存在的基类句柄现在将引用子类中的函数覆盖。


静态变量/函数

每一个类的实例都会拷贝一份其内部变量。

静态变量

当类中的变量被声明为静态时,该变量将是所有类实例中唯一的副本。为了演示示例,我们将比较静态计数器和非静态计数器。静态计数器使用static关键字声明并命名为static_ctr,而普通计数器变量则命名为ctr.这两个计数器都将在new()函数中递增,以便每次创建对象时都会更新它们。

 仿真结果:

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

如果您想知道在特定时间之前生成的数据包总数,则将变量声明为静态变量非常有用。

静态函数

静态方法遵循所有类作用域和访问规则,但唯一的区别是它可以在类外调用,而不需要类实例化。静态方法(方法)不能访问非静态成员,但它可以直接访问静态类属性或调用同一类的静态方法。此外,静态方法不能是虚的。使用类名的静态函数调用需要通过作用域运算符 :: 进行

现加入一个非静态成员 mode ,并且从静态函数中调用它。会编译出错。

对象拷贝

浅拷贝

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

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

// ---- ---- 类定义
class Header;
	int id;

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

	function void show_id();
		$display("id = 0x%0h",id);
	endfunction
endclass 

class Packet;
	int addr;
	int data;
	Header Header_OBJ;//类内嵌套 Header 类

	function new(int addr,int data,int id);
		Header_OBJ = new(id);
		this.addr = addr;
		this.data = data;
	endfunction

	function void display(string name);
		$display("[%s] addr = 0x%0h data = 0x%0h id = %0d",name,addr,data,Header_OBJ.id);
	endfunction
endclass 

// ---- ---- TEST BENCH
module TEST_SHALLOW_COPY();
	Packet p1,p2;
	initial
	begin
		p1 = new(32'h11223344,32'h55667788,30);
		p1.display("p1");

		p2 = new p1;// 将 p1 浅拷贝至 p2
		p2.display("p2");

		// 修改 p1 的内容
		p1.addr = 32'hAABBCCDD;
		p1.data = 32'hEEFF0011;
		p1.Header_OBJ.id = 60;
		p1.display("p1");
		// 此时 p2 嵌套类的对象 Header_OBJ 已经指向 p1 的 Header_OBJ,但是 p2 的 addr 和 data 均未改变。
		p2.display("p2");
	end
endmodule

 Questa Sim编译/仿真结果:

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

深拷贝

深度复制是复制所有内容(包括嵌套对象)的地方,通常需要特定代码来实现此目的。

让我们在上面给出的示例中的数据包类中添加一个名为copy()的定制函数。

// ---- ---- 类定义
class Header;
	int id;

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

	function void show_id();
		$display("id = 0x%0h",id);
	endfunction
endclass 

class Packet;
	int addr;
	int data;
	Header Header_OBJ;//类内嵌套 Header 类

	function new(int addr,int data,int id);
		Header_OBJ = new(id);
		this.addr = addr;
		this.data = data;
	endfunction

	function void display(string name);
		$display("[%s] addr = 0x%0h data = 0x%0h id = %0d",name,addr,data,Header_OBJ.id);
	endfunction

	// 定制 copy 函数
	function void copy (Packet p);
		this.addr = p.addr;
		this.data = p.data;
		this.Header_OBJ.id = p.Header_OBJ.id;
	endfunction
endclass 

// ---- ---- TEST BENCH
module TEST_DEEP_COPY();
	Packet p1,p2;
	initial
	begin
		p1 = new(32'h11223344,32'h55667788,30);
		p1.display("p1");

		p2 = new(1,2,3);
		p2.copy(p1);// 将 p1 深拷贝至 p2
		p2.display("p2");

		// 修改 p1 的内容
		p1.addr = 32'hAABBCCDD;
		p1.data = 32'hEEFF0011;
		p1.Header_OBJ.id = 60;
		p1.display("p1");
		// 此时 p2 嵌套类的对象 Header_OBJ 已经指向 p1 的 Header_OBJ,但是 p2 的 addr 和 data 均未改变。
		p2.display("p2");
	end
endmodule

仿真结果: 

 注意,我们在这里调用了定制的Copy()函数,而不是浅层的复制方法,因此Header对象的内容也应该被复制到p2中。

请注意,即使更改了p1的id字段,对象p2的id仍然保持先前的值。

参数化的类

为啥需要参数化类?

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

语法

示例

参数化的类

下面给出的是一个参数化类,它的大小是可以在实例化期间更改的参数。

将数据类型作为参数传递

在这种情况下,数据类型是参数化的,并且可以在实例化期间被覆盖。在前一种情况中,我们定义了具有特定值的参数。

 请注意,任何类型都可以作为参数提供,包括用户定义的类型,如类或结构体。


extern

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

示例

local

声明为本地成员的成员仅对同一类的方法可用,并且不能由子类访问。但是,访问本地成员的非本地方法可以由子类继承和重写。

示例

在下面的示例中,我们将声明两个变量-一个是公共的,另一个是本地的。当从类外部的某个地方访问类的本地成员时,我们预计会看到一个错误。这是因为关键字 local 用于使成员仅在同一个类内可见。

不出所料,编译器会发出一个编译错误,指向从类外部访问本地成员的行。

在上面的示例中,我们可以删除导致编译错误的行,并看到我们得到了良好的输出。唯一访问本地成员的其他函数是display()函数。

当被子类访问时

在本例中,让我们尝试从子类中访问本地成员。我们预计会在这里看到错误,因为本地成员对子类也是不可见的。


抽象类

SystemVerilog禁止直接实例化声明为虚的类,它被称为抽象类。

语法

然而,这个类可以被扩展以形成其他子类,然后这些子类可以被实例化。这对于强制测试用例开发人员总是扩展一个基类来形成另一个类以满足他们的需要很有用。因此,基类通常被声明为虚的,尽管这不是强制的

正常类的示例

抽象类的示例

让我们将基类声明为虚拟类,使其成为一个抽象类,看看会发生什么。

仿真器报告编译错误,如下所示,因为不允许实例化抽象类。

扩展抽象类

抽象类可以像任何其他SystemVerilog类一样使用如下所示的扩展关键字进行扩展。

从下面的仿真输出可以看出,扩展抽象类以形成可以使用new()方法实例化的其他类是完全有效的。

纯虚拟方法/函数

抽象类中的虚方法可以用关键字 pure 声明,称为纯虚拟方法。这样的方法只需要在抽象类中指定一个原型,而实现则留给子类中的定义。

示例

 纯虚方法原型及其实现应该具有相同的参数和返回类型。


随机化

为啥需要随机化?

开发定向测试需要很长时间,因为您必须考虑所有可能的情况来验证不同的功能。您很有可能会遗漏某些角例。因此,我们希望能够生成落在有效范围内的随机值,并将这些随机值应用于我们感兴趣的信号。

为什么我们不能有任意随机值?

简单地运行随机化测试没有多大意义,因为会有许多无效的情况。我们创建有效配置的随机化测试的方法是使用约束。这种验证风格通常被称为约束随机验证(CRV)。

SV中随机化怎么做到?

要在变量上启用随机化,您必须将变量声明为 rand 或 randc 。这两者之间的区别在于,randc 本质上是循环的,因此在随机化之后,只有在应用了所有其他值之后才会再次选取相同的值。如果随机化成功,randomize()将返回1,否则返回0。

我们可以通过使用 assert() 函数确保随机化成功。这将避免运行模拟垃圾值,除非我们仔细查看,否则无法计算出这些值。

请注意,Mode 的随机化出现了重复值,而对于 Key ,这些值本质上是循环的(3,4,5,6是一个完整的集合)。

有哪些不同的约束风格?

您可以用多种方式编写约束。约束不应该相互冲突,否则随机化将在运行时失败。

如下给出简单示例:

 在下一篇博文,详细说明约束的写法。



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/398413.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript深度剖析:TypeScript 中命名空间与模块的理解?区别?

一、模块 TypeScript 与 ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块 相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的 例如我们在在一个 TypeScript 工程下建立一…

JavaScript 中的类型转换机制以及==和===的区别

目录一、概述二、显示转换Number()parseInt()String()Boolean()三、隐式转换自动转换成字符串自动转换成数值四、 和 区别1、等于操作符2、全等操作符3、区别小结一、概述 我们知道,JS中有六种简单数据类型:undefined、null、boolean、string、number、…

固定资产管理5大难题?一招破解,现学现用

随着国有企业改革的不断深化,国有固定资产管理越来越受到人们重视,然而国有固定资产管理的好坏在一定程度上影响着国有企业的经济效益,如果不处理好必定影响国有企业的发展。 固定资产存在的5大难题 01.资产流程和管理方式相对落后&#xff0…

分布式架构-流量治理-流量控制

系列目录 分布式架构-流量治理-服务容错 分布式架构-流量治理-流量控制 引子 任何一个系统的运算、存储、网络资源都不是无限的,当系统资源不足以支撑外部超过预期的突发流量时,便应该要有取舍,建立面对超额流量自我保护的机制,这…

企业新闻稿的格式和要求是什么?如何写好新闻稿?

新闻稿是企业自己撰写给媒体的新闻素材,媒体采纳你的稿件后就可以传播到更多的大众面前。 所以企业新闻稿的撰写一方面要让媒体认可,另外一方面是让用户认可你的品牌或是产品。 企业新闻稿的格式和要求是什么?如何写好新闻稿?今…

Unity之ASE实现影魔灵魂收集特效

前言 我们今天来实现一下Dota中的影魔死亡后,灵魂收集的特效。效果如下: 实现原理 1.先添加一张FlowMap图,这张图的UV是根据默认UV图,用PS按照我们希望的扭曲方向修改的如下图所示: 2.通过FlowMap图,我…

java - 数据结构,算法,排序

一、概念 1.1、排序 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 平时的上下文中,如果提到排序,通常指的是排升序(非降序)。 通常意义上的排序&#…

ESP32设备驱动-AM2301(DHT21)温度湿度传感器驱动

AM2301(DHT21)温度湿度传感器驱动 文章目录 AM2301(DHT21)温度湿度传感器驱动1、AM2301(DHT21)介绍2、硬件准备3、软件准备4、驱动实现1、AM2301(DHT21)介绍 AM2301 湿敏电容数字温湿度模块是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温…

腾讯安全SOC+与广州农商银行联合安全体系入选工信部试点示范项目

近日,工信部、央行、银保监、国家互联网信息办公室等十二部门联合发布“2022年网络安全技术应用试点示范项目名单”,由腾讯云和广州农村商业银行股份有限公司联合共建的“金融云平台安全运营体系”成功入选。该体系依托腾讯安全SOC产品,构建了…

java——了解反射

目录 什么是反射? 反射如何获取类信息? 小结: 什么是反射? 反射是用代码分析类信息的能力 类中有哪些信息:方法、对象、构造器、全局变量、父类、接口等.... 反射如何获取类信息? 三种方式 1.通过对象…

k8s client-go源码解析之informer 二

Informer(二) 注意:本文内容为学习笔记,内容为个人见解,不保证准确性,但欢迎大家讨论何指教。 本篇介绍cache.SharedIndexInforme中 Controller及其组件 informer大致工作流程如下: sharedI…

CVS Health 西维斯健康EDI需求

CVS Health西维斯健康在特拉华州成立,通过旗下的 CVS Pharmacy 和 Longs Drugs 零售店以及 CVS.com 电商提供处方药、美容产品、化妆品、电影和照片加工服务、季节性商品、贺卡和方便食品。CVS Health通过使高质量的护理变得更经济、更易获得、更简单、更无缝&#…

中国通信行业十大杰出女性,看看你认识几个?

注:排名不分先后█ 01 中国信息通信研究院副院长 王志勤王志勤王志勤在中国乃至全球通信行业具有极高的知名度。她1992年毕业于北京邮电大学无线通信专业,在通信领域辛勤耕耘了三十余年,长期从事标准研究和制定工作,参与了中国从2…

C 语言网络编程 — 高并发 TCP 网络服务器

目录 文章目录目录TCP Socket 编程示例服务端客户端测试高并发 TCP 网络服务器I/O 并发模型设计系统文件描述符数量限制完全断开连接导致的性能问题关注 TCP 连接的状态合理配置 TCP 连接内核参数使用 shutdown() 来确保 Connection 被正常关闭断开重连问题使用 Heartbeat 来判…

【强化学习】强化学习数学基础:时序差分方法

时序差分方法Temporal Difference Learning举个例子TD learning of state values算法描述TD learning of action values: SarsaTD learning of action values: Expected SarsaTD learning of action values: n-step SarsaTD learning of optimal action values: Q-learningA un…

【Redis】主从集群 实现读写分离(二)

目录 2.Redis主从 2.1.搭建主从架构 2.2.主从数据同步原理 2.2.1.全量同步 2.2.2.增量同步 2.2.3.repl_backlog原理 2.3.主从同步优化 2.4.小结 2.Redis主从 2.1.搭建主从架构 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,…

YOLOv7、YOLOv5改进之打印热力图可视化:适用于自定义模型,丰富实验数据

💡该教程为改进YOLO高阶指南,属于《芒果书》📚系列,包含大量的原创改进方式🚀 💡更多改进内容📚可以点击查看:YOLOv5改进、YOLOv7改进、YOLOv8改进、YOLOX改进原创目录 | 唐宇迪老师联袂推荐🏆 💡🚀🚀🚀内含改进源代码 按步骤操作运行改进后的代码即可�…

【毕业设计】Java局域网聊天室系统的设计与实现

点击免费下载源码 视频聊天系统作为一种新型的通信和交流工具,突破了地域的限制,可以提供更为便捷、灵活、全面的音、视频信息的传递和服务,具有极其广泛的发展前景。 介绍了采用JAVA编程开发视频聊天系统的一套比较常用的解决方案。文字聊…

Spring之实例化Bean _ @Resource和@Autowired实现原理(3)

目录 1. 搜集注解信息 applyMergedBeanDefinitionPostProcessor(*) 2. 将实例化的Bean放入3级缓存中 addSingletonFactory(***)为循环依赖做准备 3. 根…

RS232/RS485信号接口转12路模拟信号 隔离D/A转换器LED智能调光控制

特点:● RS-485/232接口,隔离转换成12路标准模拟信号输出● 可选型输出4-20mA或0-10V控制其他设备● 模拟信号输出精度优于 0.2%● 可以程控校准模块输出精度● 信号输出 / 通讯接口之间隔离耐压3000VDC ● 宽电源供电范围:10 ~ 30VDC● 可靠…