类是用户定义的数据类型。类包含一组**“属性”和“方法”**。属性和方法都被视为类的成员。
通常来说,
- 类属性是类内部的变量,
- 类方法是类内部的函数/任务。
类声明本身不占用任何内存。我们可以在SystemVerilog中在模块内部或外部定义一个类。
类声明语法:
class class_name;
// 声明类属性
// 定义类方法(任务和函数)
endclass
示例:
class home;
bit light;
int fan ;
string switch;
task open_electricity();
switch = "ON"
$display("switch is %s so electricity is open",switch);
endtask
endclass
在上述示例中,类 home 包含 light、fan 和 switch 作为类的属性,open_electricity 是类的方法。
类和面向对象编程 | 描述 |
---|---|
1.类 | 类是用户定义的数据类型,包括数据(类属性)、函数和任务,这些函数和任务操作数据 |
2.类句柄 | 对象句柄只不过是对象的指针 |
3.类构造函数 | 构造函数只是创建特定类数据类型的新对象的方法 |
4.类对象 | 类属性和方法只能在创建对象后访问 |
5.this关键字 | this 关键字用于引用当前实例的属性或方法 |
6.super关键字 | super 关键字在子类中使用,用于引用父类的属性和方法 |
7.extern关键字 | extern 关键字用于方法,提供在类体外定义类方法的功能 |
8.常量类属性 | 使用const限定符使类属性只读 |
9.复制对象 | 根据内存位置复制对象 |
10.参数化类 | 参数类似于局部常量 |
11.虚方法 | 虚方法功能在运行时设置,允许将扩展类句柄分配给基类句柄 |
12.静态属性和方法 | 使用static关键字的类成员称为静态类成员 |
13.覆盖类成员 | 在子类中使用与父类相同名称定义类属性和方法将覆盖类成员 |
14.继承 | 继承是一个OOP概念,允许用户创建基于现有类的类 |
15.多态性 | 相同代码根据处理的对象类型不同表现出不同的行为 |
16.封装 | 隐藏类内的数据,并仅通过方法使其可用的技术 |
17.抽象 | 使用virtual关键字声明的类称为抽象类 |
类实例(或)类句柄#
句柄是对类对象的间接引用,就像内存中地址的指针。
语法:
class_name class_handle;
在下面的代码中,home 是一个类,h1 是该类的句柄。它可以保存类 home 的对象的句柄,但在使用类构造函数(即 new 函数)进行赋值之前,它始终为 null。在这一点上,类对象尚不存在。
代码片段:
class home; //define class
//declare class properties
bit light;
int fan ;
string switch;
//define class method(task/function)
task open_electricity();
switch = "ON";
$display("switch is %s so electricity is open",switch);
endtask:open_electricity
endclass:home
module tb;
home h1;
initial begin:BEGIN_I
$display("Let's declare handle");
$display("Check wheather the object is created or not");
if (h1==null)
$display("object is empty");
else
$display("object is not empty");
end:BEGIN_I
endmodule:tb
输出:
- 在上述代码中,我们为类 home 创建了一个句柄 h1 并检查它是否为 null。
- 在下图 Fig-2 中,我们可以看到它显示对象为空。因此,我们可以说对象尚未创建。
类构造函数#
“new()” 函数被称为类构造函数。调用 “new()” 函数时,它分配内存并将地址返回给类句柄。构造一个对象会在内存中分配用于保存对象属性的空间。
语法:
class_handle_name = new();
类对象:#
对象是该类的一个实例。使用对象首先要声明该类类型的变量(保存对象句柄),然后创建该类的对象(使用 new() 函数)并将其赋值给该变量。
简单来说,如果我们创建一个对象,那么我们为该类的句柄分配了内存。
语法:
class_handle_name = new();
上述语句将创建一个对象并赋值给句柄 h1。我们可以用两步或一步创建一个对象。
创建对象有两种方式:
1. 两步过程:
第 1 步: home h1;
第 2 步: h1 = new();
2. 一步过程:
第 1 步: home h1 = new();
代码片段:
class home; //define class
//declare class properties
bit light;
int fan ;
string switch;
//define class method(task/function)
task open_electricity();
switch = "ON";
$display("switch is %s so electricity is open",switch);
endtask:open_electricity
endclass:home
module tb;
home h1; //creating handle
initial begin:BEGIN_I
$display("Using new() method we can create an object");
h1=new();
if (h1==null)
$display("object is empty");
else
$display("object is not empty");
end:BEGIN_I
endmodule:tb
输出:
在下图 fig-3 中,我们可以看到在创建对象后,类句柄的内存已创建,因此显示对象不为空。
访问类属性和方法
借助 ‘.’ 我们可以访问类属性和方法。类属性和方法可以通过使用对象名后跟属性或方法名来访问。
语法:
<class_handle>.<class_properties> = 1;
<class_handle>.<class_method>;
代码片段:
class home; //define class
//declare class properties
bit light;
int fan;
string switch;
//define class method(task/function)
task open_electricity();
switch = "ON";
$display("\t switch is %0s so electricity is open",switch);
endtask:open_electricity
endclass:home
module check_electricity;
initial begin:BEGIN_I
home h1; //creating handle
h1=new(); //Creating Object for the Handle
h1.light=1; // access the properties
h1.fan=1;
$display("");
h1.open_electricity; //access the method
$display("\t light is %0d(ON) and fan is also %0d(ON)",h1.light,h1.fan);
$display("");
end:BEGIN_I
endmodule:check_electricity
在上面的代码中,home 是一个类。它们有属性和方法。这里为类 home 创建了一个句柄 h1,然后使用 new() 函数创建了一个对象,通过 h1.light 和 h1.fan 访问类属性,并通过 h1.open_electricity 访问类方法。
输出:
在下图 fig-4 中,我们看到访问属性并将 light 的值设为 1 和 fan 的值设为 1,打印出 light 为 1 和 fan 也为 1。
This 关键字#
“this” 关键字解决了当类属性和传递给类方法的参数相同时编译器的歧义。简单来说,“this” 关键字用于在类方法内部调用类属性的值。
语法:
this.class_property;
代码片段:
class base_class;
string fan ="OFF" ;
string switch="OFF";
function void open_electricity();
string fan="ON";
string switch="ON";
this.fan=fan;
this.switch=switch;
$display("Inside class method :- switch is %0s that's why fan is %0s",switch,fan);
endfunction:open_electricity
endclass:base_class
module check_electricity;
base_class b1;
initial begin:BEGIN_I
b1=new();
b1.open_electricity;
$display("Outside class :- switch is %0s that's why fan is %s",b1.switch,b1.fan);
end:BEGIN_I
endmodule:check_electricity
通过上面的例子,我们可以看到,fan 和 switch 是类属性,函数的参数相同,但我们使用 this 关键字来区分它们。
输出:
在这段代码中,我们在 fan 和 switch 前面加上了 this 关键字,然后打印出了在类内部和类外部 fan 和 switch 的值都是 ON。
如果在上面的代码中不使用 “this” 关键字会发生什么?
如果在将 fan 分配给 fan 或将 switch 分配给 switch 之前没有使用 this 关键字,则在方法内部显示的是属性的被覆盖值,但在类外部显示的是属性的默认或给定值。
代码片段:
class base_class;
string fan ="OFF" ;
string switch="OFF";
function void open_electricity();
string fan="ON";
string switch="ON";
fan=fan;
switch=switch;
$display("Inside class method :- switch is %0s that's why fan is %0s",switch,fan);
endfunction:open_electricity
endclass:base_class
module check_electricity;
base_class b1;
initial begin:BEGIN_I
b1=new();
b1.open_electricity;
$display("Outside class :- switch is %0s that's why fan is %s",b1.switch,b1.fan);
end:BEGIN_I
endmodule:check_electricity
输出:
在下图中,我们可以看到如果我们没有提及 this 关键字,那么在方法内部显示 switch 为 ON,这就是为什么 fan 是 ON,但在类外部显示 switch 为 OFF,这就是为什么 fan 是 OFF。
super 关键字#
“super” 关键字用于子类或派生类中引用父类的类成员。如果在子类中覆盖了父类的方法,则可以使用 “super” 关键字从子类中访问父类的方法,如果两个类成员具有相同的名称用于类属性和方法。
语法:
super.class_method;
代码片段:
class base_class;
string fan,switch; //properties of class
function void display(); //method of class
switch="ON";
$display("Here using super keyword we can get both display() methods");
$write("switch is %s " ,switch);
endfunction:display
endclass:base_class
class sub_class extends base_class;
string fan="ON";
function void display();
super.display;
$write("that's why fan is %s \n" ,fan);
endfunction:display
endclass:sub_class
sub_class s1; //creating handle for class
module super_example;
initial begin:BEGIN_I
s1 =new(); //create an object
s1.display(); //access the sub_class method
end:BEGIN_I
endmodule:super_example
输出:
在下图中,我们可以看到通过使用子类句柄显示了父类方法和子类方法,因此它显示 switch 为 ON,这就是为什么 fan 也是 ON,并且它只显示了单行,因为这里使用了 $write。
作用域解析#
在类中,我们有一个作用域解析运算符,用于使用类名访问类属性和方法,后跟符号 ::,然后是类的属性和方法。
extern 关键字#
“extern” 关键字用于方法,它提供了一个在类体外定义方法的方法。
如果方法定义很长(方法内部有很多行代码),则 extern 方法提供了更好的可读性和类的更清晰的实现。
extern 关键字用于方法声明,使用作用域解析运算符的类名用于方法定义。
注意:
- 方法定义和声明应具有相同数量的参数列表、数据类型和参数名称。
- 对于 extern 函数,如果使用了返回类型,应该是相同的。
语法:
步骤 - 1: 方法声明 - extern 表示在类体外声明
extern function function_name;
extern task task_name;
步骤 - 2: 使用作用域解析运算符在类体外实现方法
function class_name::function_name;
task class_name::task_name;
代码片段:
class home;
string switch;
string fan = "OFF";
extern function void display();
endclass:home
function void home::display();
string switch="OFF";
$display("The switch is %0s that's why fan is %0s",fan,switch);
endfunction:display
module extern_example;
home h;
initial begin:BEGIN_I
h=new();
$display("Using extern keyword we are creating prototype for our function in the class");
$display("Then we are declaring the function outside the class");
h.display;
end:BEGIN_I
endmodule:extern_example
输出:
这里类方法是在类外定义的,但在使用类句柄访问后,它显示 switch 为 OFF,这就是为什么 fan 也为 OFF。
链式构造#
- 函数 new() 被称为类构造函数。此构造函数用于为类句柄创建对象。创建对象意味着我们为该类分配了一些内存。
- 链式构造意味着当我们从父类扩展子类时,父类的所有属性和方法都将传递到子类中。
- 如果在父类和子类中使用相同的方法,则子类将覆盖父类的方法。
- 但是在 new() 函数的情况下,您可以在父类和子类中都使用相同的 new(),但是覆盖父类 new() 没有意义。
- 默认情况下,模拟器将在扩展类中调用 super.new() 语句,这仅在 new() 方法中发生。
代码片段:
class parent;
int a;
function new();
a = 1;
endfunction
extern function void display();
endclass:parent
class child extends parent;
int b;
function new();
b = 2;
endfunction
extern function void display();
endclass:child
function void parent::display();
$display("a = %0d",a);
endfunction
function void child::display();
super.display(); // Super is a keyword used to get the method of same name from extended class.
$display("b = %0d",b);
endfunction
module basic_chain_construct();
child c;
initial begin
c = new;
c.display();
end
endmodule:basic_chain_construct
输出:
在上面的代码中,我们尝试创建两个类,每个类都有一个 new() 和一个 display() 方法。因此,我们在子构造函数中没有调用 super.new()。
尽管如此,我们仍然得到了我们在两个不同构造函数中设置的 a 和 b 的值。因此,我们可以说构造函数内部存在一个内部的 super.new()。
常量类属性#
为了使类属性成为只读,使用 const 修饰符。由于类对象是动态的,Systemverilog 提供了两种类型的常量。它们是
在两种情况下,我们都使用 ‘const’ 关键字声明常量值。
1.全局常量#
在变量声明期间,分配了一个初始值,这种类属性称为全局常量。变量的值可以在类构造函数内部更改,即使在声明之后也是如此。它不能覆盖常量变量的值在任何其他函数或任何模块中。
语法:
const int b=2;
代码片段:
class data;
string a;
const int b=1;//global constant
function new();
a="team";
endfunction:new
function void display();
$display("a=%0d,b=%0d",a,b);
endfunction:display
endclass:data
module global_class;
data p1;
initial begin:BEGIN_I
p1=new();
$display("");
p1.display();
// p1.b=2;// invalid usage of b
//-------------------------------------------------------
//Here we assigned the b value in class properties using 'const' again we
//should not assign the value for 'b' even though we declared the values it throws the
//errors.
//--------------------------------------------------------
$display("");
end:BEGIN_I
endmodule:global_class
输出:
在下图中,您可以看到 b 的值仅为 ‘1’,因为我们将其声明为一个常量值。即使我们想要更改 ‘b’ 的值,它也会抛出错误。在声明为常量后,它不会接受任何值。
2.实例常量#
在变量声明期间,未分配初始值,这种类属性称为实例常量。实例常量允许用户在类构造函数中运行时仅赋值一次。
语法:
const int b;
function new();
b=9;
endfunction
代码片段:
class data;
const int a;
string b;
function new();
a=5;
b="bhavana";
endfunction:new
function void display();
$display("a=%0d,b=%0d",a,b);
endfunction:display
endclass:data
module instance_class;
data t1;
initial begin:BEGIN_I
t1=new();
//t1.a=2;
//-------------------------------------------------------
// Here we should not assign value for the 'a' because
// we declared the 'a' as constant variable.
// so if we assign any value to the 'a' it throws errors
// it won't take any value eventhough we declared.
//-------------------------------------------------------
t1.b="bjt";
$display("");
t1.display();
$display("");
end:BEGIN_I
endmodule:instance_class
输出:
在下图中,我们可以看到一个值是常量,因为我们在这里将其声明为常量,并且在实例常量中,我们只能在构造函数中声明值。
参数化类#
当需要以不同方式实例化相同的类时,参数化类非常有用。可以在类定义中设置默认参数。这些参数可以在实例化时被覆盖。
参数值可用于定义类中的一组属性。默认值可以通过在实例化期间传递新的参数集来覆盖,这称为参数覆盖。
按值参数化#
语法:
class Vector #(parameter WIDTH=1);
bit [WIDTH-1:0] data;
endclass
代码片段:
class mirafra #(parameter branch,employes);
bit [branch-1:0]b1;
bit [employes-1:0]b2;
function new();
b1=13;
b2=9;
endfunction
function void disp();
$display("b1=%0d,b2=%0d",b1,b2);
endfunction
endclass:mirafra
mirafra#(3,2) m;
module value;
initial begin:BEGIN_I
m=new();
$display("");
$display("contents of m ");
m.disp();
$display("");
end:BEGIN_I
endmodule:value
输出
在下图中,我们可以看到按值参数化的参数化类的输出,其中我们将 int 参数分支和雇员视为 32 位宽度。
按类型(数据类型)参数化#
在这种情况下,数据类型是参数化的,并且在实例化时也可以被覆盖。
语法
class Stack #(parameter type T=int);
T items[64], idx=0;
function void push(input T val);
function T pop();
endclass
代码片段
class data #(parameter a,type team=string);//parameter declaration
bit [a-1:0]d;
team c;
function new();
d=20;
c="Mirafra";
endfunction
function void disp();
$display("d=%0d,c=%0s",d,c);
endfunction
endclass:data
data#(4) p1;
module test;
initial begin:BEGIN_I
p1=new();
$display("");
$display("contents of p1");
p1.disp();
$display("");
end:BEGIN_I
endmodule:test
输出:
在下图中,我们可以看到按数据类型参数化的参数化类的输出,其中我们将 T 视为字符串。
复制对象#
有 3 种类型的复制对象
1.类分配 2.浅拷贝 3.深拷贝
1.类分配#
使用类分配时,原始类和复制类都将指向同一内存位置。对任一类中的变量进行的任何更新都将影响另一个类。
语法
a=new();
b=a;
类分配的内存分配
在上面的内存分配中,我们说 p1 和 p2 共享相同的内存,如果我们在一个类(p1)中更改一个变量,它将在另一个类(p2)中更新。
代码片段:
class Mirafra;
string c;
int d;
function new();
c="team";
d=4;
endfunction:new
function void display();
$display("\t c=%0s,\t d=%0d",,c,d);
endfunction:display
endclass:Mirafra
module assignment;
Mirafra p1;
Mirafra p2;
initial begin:BEGIN_I
p1=new();
$display("contents of p1 before changes");
p1.display();
p2=p1;
$display("contents of p2 before changes");
p2.display();
p2.c="BJT";
p2.d=8;
$display("contents of p1 after changes");
p1.display();
$display("contents of p2 after changes");
p2.display();
end:BEGIN_I
endmodule:assignment
输出
在下图中,我们可以看到类分配操作,因为我们知道类分配具有相同的内存位置,所以首先我们可以复制对象,因此如果我们想要更改值,如果我们在一个句柄中更改一个值,则该更改也会反映在另一个句柄中。
2.浅拷贝#
当一个类被浅拷贝到另一个类时,原始类的所有变量都被复制到被复制的类中,但原始类内部的对象除外。如果原始类内部定义了一个类对象,那么该对象不会被复制,只会复制对象的句柄。这意味着对象的内存位置保持不变。因此,如果从复制的类中更新对象,则该对象的值也将更新为原始类,但如果更新复制的类的变量,则不会更新原始类的变量。
语法
a=new();
b=new a;
浅拷贝的内存分配
在上图中,我们可以看到 p1 和 p2 的内存分配是相同的,但变量更改只会更新在句柄变量中。
代码片段
class Mirafra;
string c;
int d;
function new();
c = "Teams";
d = 8;
endfunction:new
function void display();
$display("\t c=%0s,\t d=%0d",c,d);
endfunction:display
endclass:Mirafra
module shallow;
Mirafra p1;
Mirafra p2;
initial begin:BEGIN_I
p1 =new();
$display("contents of p1");
p1.display();
p2 =new p1;
$display("contents of p2");
p2.display();
p2.c="place";
$display("diplay contents of p1");
p1.display();
$display("diplay contents of p2");
p2.display();
end:BEGIN_I
endmodule:shallow
输出:
在下图中,我们可以看到浅拷贝,因为我们知道它具有相同的内存位置,但通过分割共享内存,首先复制值,如果我们要更改值,则只有更新,如我们在这里看到的,a 和 b 被更新,但 c 被更新在特定句柄中。
3.深拷贝#
SystemVerilog 深拷贝复制所有类成员及其嵌套类成员。这意味着它将所有成员复制到不同的内存位置,包括类内部的对象。一个类的成员的任何更改对另一个类成员没有影响。
语法
a=new();
b=new();
b.copy(a);
深拷贝的内存分配
在上图中,我们可以看到 p1 和 p2 有不同的内存,因此如果更改或更新任何变量,只有该类由于不同的内存位置而更新。
代码片段
class branches;
string c;
int d;
function new();
c="Banglore";
d=1;
endfunction:new
function void disp();
$display("\t c=%0d,\t d=%0d",c,d);
endfunction:disp
function void deep(branches copy);//copy
this.c=copy.c;
this.d=copy.d;
endfunction:deep
endclass:branches
module deep;
branches p1;
branches p2;
initial begin:BEGIN_I
p1=new();
p2=new();
p2.deep(p1);//deep copy
$display("");
$display("contents of branch p1 before changes");
p1.disp();
$display("contents of branch p2 before changes");
p2.disp();
p1.c="Manipal";
p2.c="Hyderabad";
$display("contents of branches p1 after changes");
p1.disp();
$display("contents of branches p2 after changes");
p2.disp();
$display("");
end:BEGIN_I
endmodule:deep
输出:
在下图中,我们可以看到它具有不同的内存位置,但我们可以使用 copy 方法复制值,如果要更改值,则值更新也会发生在我们考虑的特定句柄中。
虚方法#
使用关键字 virtual 声明的方法称为虚方法。
图-21 虚类成员类型
虚方法有两种类型
1.虚函数
2.虚任务
虚函数#
在函数关键字之前声明带有 virtual 关键字的函数称为虚函数,我们在基(父)类函数中使用虚函数关键字,然后只有子类会执行。
代码片段
class packet;
string a;
int b;
function new();
a="Team";
b=4;
endfunction:new
virtual function void display();
$display("a=%0d",a);
$display("b=%0d",b);
endfunction:display
endclass:packet
class pack extends packet;
string c;
int d;
function new();
c="BJT";
d=8;
endfunction:new
function void display();
// super.display();
$display("c = %0d",c);
$display("d = %0d",d);
endfunction:display
endclass:pack
class pack1 extends packet;
string e;
function new();
e="Manipal";
endfunction:new
function void display();
$display("e = %0d",e);
endfunction:display
endclass:pack1
packet pp0,pp1;
pack p2;
pack1 p3;
module virt_fun;
initial begin:BEGIN_I
p2=new();
p3=new();
pp0=p2;
pp1=new p3;
pp0.display();
pp1.display();
end:BEGIN_I
endmodule:virt_fun
输出:
在下图中,我们可以看到子类 1 和 2 的内容,因为我们将父类函数声明为虚函数,所以它提供了子类的值。
虚任务#
在任务关键字之前声明带有 virtual 关键字的任务称为虚任务。
代码片段
class packet;
string a;
int b;
virtual task display();
a="Team";
b=4;
$display("a=%0s",a);
$display("b=%0d",b);
endtask
endclass//class 1
//-----class 2-------
class pack extends packet;
string c;
int d;
task display();
c="BJT";
d=8;
$display("c=%0s",c);
$display("d=%0d",d);
endtask
endclass//class 2
packet p1;
pack p2;
module virtual_task;
initial begin:BEGIN_I
p2=new();
p1=p2;
$display("contents of pp0");
p1.display();
end:BEGIN_I
endmodule:virtual_task
输出
在下图中,我们可以看到子类的内容,因为我们将父类方法声明为虚任务,所以它提供了子类的内容。
覆盖类成员#
可以在子类或扩展类中定义与父类或基类属性和方法同名的类属性和方法,以覆盖类成员。
覆盖方法意味着给定一个具有方法的基类,我们可以定义一个从该基类扩展的子类,然后为给定方法提供一个新的定义。默认情况下,子类继承方法的基类实现,但如果程序员决定通过覆盖它来更改该定义 - 只需列出该方法的新版本,那么该方法的新版本将被用于替代父类的方法。
代码片段
//parentclass
class Mirafra;
string place;
int members;
int teams;
function new();
place="manipal";
members=13;
endfunction:new
function void display();
$display("place = %0d,\t members= %0d",place,members);
endfunction:display
endclass:Mirafra
//child-1
class Teams extends Mirafra;
string Teams;
int members;
function new();
Teams="mirafra-teams";
members=25;
endfunction:new
function void display();
$display("Teams=%0d,\t mem=%0d",Teams,members);
endfunction:display
endclass:Teams
//child-2
class bjt extends Mirafra;
string Team;
int members;
string place;
function new();
Team ="Team3";
members=4;
endfunction:new
function void display();
$display("Team=%0s,\t members=%0d,\t place=%0s",Team,members,place);
endfunction:display
endclass:bjt
module over_riding;
bjt c;
initial begin:BEGIN_I
c=new();
$display("contents before over-riding");
c.display();
c.place = "mirafra";
c.Team="BJT";//over-riding parent-class members
c.members = 8;
c.place="Manipal";
$display("contents after over-riding");
c.display();
end:BEGIN_I
endmodule:over_riding
输出:
在下图中,我们可以看到类的覆盖成员,在这里我们只是使用子类句柄访问父类,但父类和子类应该具有相同的类属性,如果我们看到,我们可以将其放置在父类中。
静态属性和方法#
可以使用关键字 static 创建类成员。带有关键字 static 的类成员称为静态类成员。类可以具有静态属性和静态方法(函数和任务)。静态变量的单个副本在多个实例之间共享。
图-24 静态类成员
静态属性#
在带有 static 关键字的类内声明的静态变量在所有类实例之间共享单个内存位置。
语法static <data_type> <variable_name>
代码片段
class Mirafra;
byte teams;
//declare the static property
static byte BJT;
function new();
//incrementing the BJT
BJT++;
//Assigning static byte to byte
teams=BJT;
endfunction:new
function void disp();
$display("teams=%0d",teams);
endfunction:disp
endclass:Mirafra
module static_properties;
Mirafra m[4];//declared array of m here
initial begin:BEGIN_I
foreach(m[i]) begin:BEGIN_LOOP
m[i] = new();
$display("contents of teams");
m[i].disp();
end:BEGIN_LOOP
end:BEGIN_I
endmodule:static_properties
输出
在下图中,当进行循环操作时,团队的内容值会更新,因为我们在这里将 BJT 视为静态属性,并将 BJT 分配给团队,所以团队值会更新。
静态方法#
静态方法与静态变量相同,也遵循类的访问规则和作用域。
静态函数和任务不能是虚拟的。 它们只能访问类的静态属性(静态成员)。访问非静态成员会导致编译错误,因为使用非法。但非静态函数或任务可以访问静态变量。 在类中,静态方法和静态成员都可以在不创建对象的情况下访问。
语法static function <method_name>
代码片段
class Mirafra;
static int team ;
function new();
//incrementing team
team ++;
endfunction:new
//declaring static method
static function void disp();
$display("\t team=%0d",team);
endfunction:disp
endclass:Mirafra
module static_method;
Mirafra m[3];//declaring array
initial begin:BEGIN_I
$display("");
foreach(m[i])begin:BEGIN_LOOP
m[i]=new;
end:BEGIN_LOOP
$display("\t contents of team");
m[2].disp();
$display("");
end:BEGIN_I
endmodule:static_method
输出
在下图中,我们看到了静态方法,因为我们将函数声明为静态函数,并且在此静态函数中只能访问静态属性,因此每次进行循环迭代时,值都会更新,所以在这里我们可以看到团队是 ‘3’。
继承#
继承是一种机制,允许子类继承基类的属性。子类的对象将可以访问父类的属性和方法。如果子类继承父类,则父类称为超类,子类称为子类。
继承是通过关键字**’extends’**来实现的,该关键字允许访问父类的所有类成员。
从上图-2中,我们可以清楚地看到所有的实例化步骤都简化为一个关键字**’extends’**,它允许将所有父类的类成员传递给子类。
代码片段:-
class A;
int a = 5;
function void disp();
$display("1.Value of a = %0d",a);
endfunction:disp
endclass:A
class B extends A;
int a = 6;
function void display();
$display("2.Value of a = %0d",a);
endfunction:display
endclass:B
module inh_sam();
B b1;
initial begin
b1 = new;
b1.a = 10;
b1.disp();
b1.display();
end
endmodule:inh_sam
输出:
在这段代码中,我们可以看到,初始时,在类 A(父类)中,我们将 ‘a’ 的值初始化为 5,在类 B(子类)中,我们将 ‘a’ 的值初始化为 6。 在模块中,我们仅为子类 B 创建了句柄 ‘b1’,并将其初始化为值 10。当我们使用子类 B 的句柄访问父类方法时,它将显示 a = 5,当我们使用子类句柄访问子类方法时,它将使用 a = 10 覆盖 a = 6,并显示输出。因此,我们能够仅通过子类 B 句柄访问这两个方法。
多态性#
多态性允许使用超类对象访问子类方法。任何子类对象都可以分配给超类对象。在多态性中,相同的句柄(或)方法采用不同的形式。重要的是要知道,所有方法名称应该对于父类和子类都是相同的,此外,如果我们在父类中的方法不使用 virtual,则它只会访问父类方法。
代码片段:-
class parent;
int a;
int b;
virtual function void display();
a = 1;
b = 2;
$display("This is parent class");
$display("a = %0d, b= %0d",a,b);
endfunction:display
endclass:parent
class child1 extends parent;
int c,d,e;
function void display();
a = 3;
b = 4;
e = a+b;
$display("This is child class");
$display("a = %0d, b = %0d,e = %0d",a,b,e);
endfunction:display
endclass:child1
class child2 extends parent;
int f;
function void display();
f= a*b;
$display("f = %0d",f);
endfunction:display
endclass:child2
module poly_ex();
parent p1[1:0];
child1 c;
child2 c2;
initial begin
c = new();
p1[0] = c;
p1[1] = new();
p1[0].display();
p1[1].display();
end
endmodule:poly_ex
输出:
在这段代码中,我们可以看到,父类和子类的方法是相同的。因此,子类方法将覆盖父类方法。因此,我们将子类句柄 ‘c’ 分配给父句柄 p[0]。因此,借助于 p[0] 句柄,我们能够访问子类方法。由于它是显示函数,它将打印 a=3,b=4,e=7。但是,当我们使用句柄 p[1] 访问类方法时,它将显示父类方法,即 a=1,b=2,因为我们没有将子类句柄 c 分配给父类句柄 p[1]。因此,在这里,父类句柄采用多种形式来访问子类方法,这确实称为poly,即多种形式的多态性。
抽象/虚拟类#
抽象类是一个虚拟类,不能直接在程序中实例化(换句话说,无法创建虚拟/抽象类的对象)。如果尝试这样做,它会抛出编译时错误。它们将用作继承任意数量的类的基类。通常我们使用抽象类来预先定义原型,也就是说,抽象类为一组派生类提供了一个模板。因此,您知道您已经有一组基本的方法/属性,这使得您更容易专注于创建这个派生类的新意图。
代码片段:-
virtual class A;
int a = 5;
function void disp();
$display("1.Value of a = %0d",a);
endfunction:disp
endclass:A
class B extends A;
int a = 6;
function void display();
$display("2.Value of a = %0d",a);
endfunction:display
endclass:B
module abstract_ex();
B b1;
initial begin
b1 = new;
b1.a = 10;
b1.disp();
b1.display();
end
endmodule:abstract_ex
输出:
在这段代码中,我们可以看到,最初在类 A(父类)中,我们将 ‘a’ 的值初始化为 5,在类 B(子类)中,我们将 ‘a’ 的值初始化为 6。 在模块中,我们仅为子类 B 创建句柄 ‘b1’,并将其初始化为值 10。当我们借助子类句柄访问父类方法时,它将显示 a = 5,当我们借助子类句柄访问子类方法时,它将用 a = 10 覆盖 a = 6 并显示输出。因此,我们可以通过子类 B 的句柄轻松访问这两种方法。
纯虚方法#
在抽象类中,虚方法可以通过关键字 ‘pure’ 声明,并称为 纯虚方法。如果使用了任何 纯虚方法,则类将自动将其视为抽象类。它应该只被添加到基类中。在基类中,我们不允许为纯虚函数添加任何函数定义。重要的是要知道,具有纯虚方法的基类的所有派生类应该分别实现所有纯虚方法。
代码片段:-
virtual class A;
int a,b,c;
pure virtual function void disp();
pure virtual task sum();
endclass:A
class B extends A;
virtual function void disp();
a =10;
$display("1.Value of a = %0d, b = %0d, c = %0d",a,b,c);
endfunction:disp
virtual task sum();
c = a+b;
$display("2.Value of a = %0d, b = %0d, c = %0d",a,b,c);
endtask:sum
endclass:B
module pure_vir_fun_ex();
B b1;
initial begin
b1 = new;
b1.disp();
b1.b = 35;
b1.sum;
end
endmodule:pure_vir_fun_ex
输出:
在代码中,我们可以看到,初始时,在一个虚类中,我们将声明两个方法 function void disp 和 task sum,我们应该在代码中使用它们而不定义它们。当我们将类 A 继承到 B 时,重要的是子类 B 中都应该使用这两个方法(function void disp 和 task sum)。类似地,如果有更多的子类,每个类都应该单独声明这两个方法,否则会引发错误。所以在类 B 中,我们赋值 a=10,但由于我们没有声明 b=0,c=0 的值。在 task 方法中,我们添加了 a 和 b 的值,但我们传递了 b=35 的值,因此我们将得到 a=10,b=35,c=45。
封装#
封装是一种将数据隐藏在类中并仅通过方法使其可用的技术。它封装了数据,并且只允许信任的用户访问(即通过类的方法)。
注意:它也适用于方法。
局部#
声明为 ’local’ 的成员仅在同一类的方法中可访问,子类将无法访问它。
代码片段:-
class parent;
bit [3:0] a,b;
local bit[5:0] c;
local int d = 5;
function int sum(bit[4:0]val1,val2);
c = val1 + val2;
return c;
endfunction:sum
function void display();
$display("sum = %0d",c);
$display("d = %0d",d);
endfunction:display
endclass:parent
module encap();
parent p;
int e;
initial begin
p = new();
e = p.sum(1010,101);
p.display();
end
endmodule:encap
Output:
在代码中,我们可以看到类中声明的局部变量 c 和 d 在类内部被访问。因为这是允许的,所以没有观察到编译错误。
如果我们通过子类访问局部变量,预期会出现的错误#
代码片段:
class parent;
bit [3:0] a,b;
local bit[5:0] c;
local int d = 5;
function int sum(bit[4:0]val1,val2);
c = val1 + val2;
return c;
endfunction:sum
function void display();
$display("sum = %0d",c);
$display("d = %0d",d);
endfunction:display
endclass:parent
class child extends parent;
function void disp();
display("d = %0d",d);
endfunction
endclass:child
module encap();
parent p;
child c1;
int e;
initial begin
p = new();
c1 = new();
e = p.sum(1010,0101);
p.display();
p.c = 5'b10;
p.display();
c1.disp();
end
endmodule:encap
输出:
在代码中,我们可以看到在类内部声明的局部变量 c 和 d 正在尝试通过子类的对象句柄以及在 module 块中使用的父句柄 p 从类外部进行访问。由于变量被声明为局部变量,这导致编译错误。
Protected#
有时需要通过子类访问父类的成员。这可以通过关键字 ‘protected’ 来实现。通过使用 protected,所有子类都将获得对被数据保护的基类成员的访问权限。
代码片段:
class parent;
bit [3:0] a,b;
protected bit[5:0] c;
protected int d = 5;
function int sum(bit[4:0]val1,val2);
c = val1 + val2;
return c;
endfunction:sum
function void display();
$display("sum = %0d",c);
$display("1.d = %0d",d);
endfunction:display
endclass:parent
class child extends parent;
function void disp();
$display("2.d = %0d",d);
endfunction
endclass:child
module prot();
parent p;
child c1;
int e;
initial begin
p = new();
c1 = new();
e = p.sum(1010,0101);
p.display();
// p.d = 10; //Cannot access local/protected member "p.d"
c1.disp();
end
endmodule:prot
输出:-
在代码中,我们可以看到在类内部声明的受保护变量 c、d 被类内部访问。但是因为它是 protected,所以继承也是被允许的,因为当我们尝试在子类中访问时不会抛出错误。但是如果我们尝试在 module 块中访问变量 c、d,会抛出错误。
公有#
默认情况下,如果我们没有用关键字 ’local’ 和 ‘protected’ 定义任何属性,则默认为 ‘public’。它为子类提供对所有属性和方法的完全访问权限。
代码片段:-
class parent;
bit [3:0] a,b;
bit[5:0] c;
int d = 5;
function int sum(bit[4:0]val1,val2);
c = val1 + val2;
return c;
endfunction:sum
function void display();
$display("sum = %0d",c);
$display("1.d = %0d",d);
endfunction:display
endclass:parent
class child extends parent;
function void disp();
$display("2.d = %0d",d);
endfunction
endclass:child
module pub();
parent p;
child c1;
int e;
initial begin
p = new();
c1 = new();
e = p.sum(1010,0101);
p.display();
c1.d = 10;
c1.disp();
end
endmodule:pub
输出:-
在代码中,我们可以看到在类内部声明的变量 c、d,默认情况下是 public,在类内部被访问。在这里,继承也是被允许的,因为当我们尝试在子类中访问时不会抛出错误。当我们尝试在 module 块中访问变量 c、d 时,也不会抛出错误,因为它将对所有属性具有完全访问权限。