文章目录
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
图解
借用一个比较火的图总体说明一下