类和面向对象

类是用户定义的数据类型。类包含一组**“属性”“方法”**。属性和方法都被视为类的成员。
通常来说,

  • 类属性是类内部的变量,
  • 类方法是类内部的函数/任务。
    类声明本身不占用任何内存。我们可以在SystemVerilog中在模块内部或外部定义一个类。

Untitled Diagram drawio (37)

类声明语法

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 中,我们可以看到它显示对象为空。因此,我们可以说对象尚未创建。

class_handle

类构造函数#

“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_object

访问类属性和方法

借助 ‘.’ 我们可以访问类属性和方法。类属性和方法可以通过使用对象名后跟属性或方法名来访问。

语法

<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.lighth1.fan 访问类属性,并通过 h1.open_electricity 访问类方法。

输出

在下图 fig-4 中,我们看到访问属性并将 light 的值设为 1 和 fan 的值设为 1,打印出 light 为 1 和 fan 也为 1。

assessing

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_keyword

如果在上面的代码中不使用 “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。

without_this

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。

super

作用域解析#

在类中,我们有一个作用域解析运算符,用于使用类名访问类属性和方法,后跟符号 ::,然后是类的属性和方法。

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。

extern

链式构造#

  • 函数 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()。

chain_construct

常量类属性#

为了使类属性成为只读,使用 const 修饰符。由于类对象是动态的,Systemverilog 提供了两种类型的常量。它们是

1.全局常量
2.实例常量

在两种情况下,我们都使用 ‘const’ 关键字声明常量值。

constant-class-image

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’ 的值,它也会抛出错误。在声明为常量后,它不会接受任何值。

global

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

输出

在下图中,我们可以看到一个值是常量,因为我们在这里将其声明为常量,并且在实例常量中,我们只能在构造函数中声明值。

instance

参数化类#

当需要以不同方式实例化相同的类时,参数化类非常有用。可以在类定义中设置默认参数。这些参数可以在实例化时被覆盖。
参数值可用于定义类中的一组属性。默认值可以通过在实例化期间传递新的参数集来覆盖,这称为参数覆盖。

按值参数化#

语法

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 位宽度。

parameter

按类型(数据类型)参数化#

在这种情况下,数据类型是参数化的,并且在实例化时也可以被覆盖。

语法

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 视为字符串。

parameter-datatype

复制对象#

有 3 种类型的复制对象

1.类分配 2.浅拷贝 3.深拷贝

Untitled Diagram drawio (16)

1.类分配#

使用类分配时,原始类和复制类都将指向同一内存位置。对任一类中的变量进行的任何更新都将影响另一个类。

语法
a=new();
b=a;

类分配的内存分配

Untitled Diagram drawio (23)

在上面的内存分配中,我们说 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

输出

在下图中,我们可以看到类分配操作,因为我们知道类分配具有相同的内存位置,所以首先我们可以复制对象,因此如果我们想要更改值,如果我们在一个句柄中更改一个值,则该更改也会反映在另一个句柄中。

class-assignment (2)

2.浅拷贝#

当一个类被浅拷贝到另一个类时,原始类的所有变量都被复制到被复制的类中,但原始类内部的对象除外。如果原始类内部定义了一个类对象,那么该对象不会被复制,只会复制对象的句柄。这意味着对象的内存位置保持不变。因此,如果从复制的类中更新对象,则该对象的值也将更新为原始类,但如果更新复制的类的变量,则不会更新原始类的变量。

语法
a=new();
b=new a;

浅拷贝的内存分配

memory allocation for shallow copy

在上图中,我们可以看到 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 被更新在特定句柄中。

shallow

3.深拷贝#

SystemVerilog 深拷贝复制所有类成员及其嵌套类成员。这意味着它将所有成员复制到不同的内存位置,包括类内部的对象。一个类的成员的任何更改对另一个类成员没有影响。

语法
a=new();
b=new();
b.copy(a);

深拷贝的内存分配

Untitled Diagram drawio (38)

在上图中,我们可以看到 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 方法复制值,如果要更改值,则值更新也会发生在我们考虑的特定句柄中。

deepcopy (2)

虚方法#

使用关键字 virtual 声明的方法称为虚方法。

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 的内容,因为我们将父类函数声明为虚函数,所以它提供了子类的值。

Untitled Diagram drawio (34)

虚任务#

在任务关键字之前声明带有 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

输出

在下图中,我们可以看到子类的内容,因为我们将父类方法声明为虚任务,所以它提供了子类的内容。

Untitled Diagram drawio (35)

覆盖类成员#

可以在子类或扩展类中定义与父类或基类属性和方法同名的类属性和方法,以覆盖类成员。

覆盖方法意味着给定一个具有方法的基类,我们可以定义一个从该基类扩展的子类,然后为给定方法提供一个新的定义。默认情况下,子类继承方法的基类实现,但如果程序员决定通过覆盖它来更改该定义 - 只需列出该方法的新版本,那么该方法的新版本将被用于替代父类的方法。

代码片段

//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

输出:

在下图中,我们可以看到类的覆盖成员,在这里我们只是使用子类句柄访问父类,但父类和子类应该具有相同的类属性,如果我们看到,我们可以将其放置在父类中。

over riding

静态属性和方法#

可以使用关键字 static 创建类成员。带有关键字 static 的类成员称为静态类成员。类可以具有静态属性和静态方法(函数和任务)。静态变量的单个副本在多个实例之间共享。

Untitled Diagram drawio (25)

                        图-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 分配给团队,所以团队值会更新。

properties

静态方法#

静态方法与静态变量相同,也遵循类的访问规则和作用域。

静态函数和任务不能是虚拟的。 它们只能访问类的静态属性(静态成员)。访问非静态成员会导致编译错误,因为使用非法。但非静态函数或任务可以访问静态变量。 在类中,静态方法和静态成员都可以在不创建对象的情况下访问。

语法
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’。

static methods

继承#

继承是一种机制,允许子类继承基类的属性。子类的对象将可以访问父类的属性和方法。如果子类继承父类,则父类称为超类,子类称为子类

Inheritance-1

继承是通过关键字**’extends’**来实现的,该关键字允许访问父类的所有类成员。

Inheritance-2new-Page-2 (1)

从上图-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 句柄访问这两个方法。

inh_output_new-Page-3

多态性#

多态性允许使用超类对象访问子类方法。任何子类对象都可以分配给超类对象。在多态性中,相同的句柄(或)方法采用不同的形式。重要的是要知道,所有方法名称应该对于父类和子类都是相同的,此外,如果我们在父类中的方法不使用 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,即多种形式的多态性。

poly-output new-Page-2

抽象/虚拟类#

抽象类是一个虚拟类,不能直接在程序中实例化(换句话说,无法创建虚拟/抽象类的对象)。如果尝试这样做,它会抛出编译时错误。它们将用作继承任意数量的类的基类。通常我们使用抽象类来预先定义原型,也就是说,抽象类为一组派生类提供了一个模板。因此,您知道您已经有一组基本的方法/属性,这使得您更容易专注于创建这个派生类的新意图。

代码片段:-

  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 的句柄轻松访问这两种方法。

abstract-output new

纯虚方法#

在抽象类中,虚方法可以通过关键字 ‘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 disptask 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。

pvf_new

封装#

封装是一种将数据隐藏在类中并仅通过方法使其可用的技术。它封装了数据,并且只允许信任的用户访问(即通过类的方法)。

注意:它也适用于方法。

Encap

局部#

声明为 ’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 在类内部被访问。因为这是允许的,所以没有观察到编译错误。

local_errors_new-Page-2

如果我们通过子类访问局部变量,预期会出现的错误#

代码片段

 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 从类外部进行访问。由于变量被声明为局部变量,这导致编译错误。

local_errors_new drawio

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,会抛出错误。

protected_new

公有#

默认情况下,如果我们没有用关键字 ’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 时,也不会抛出错误,因为它将对所有属性具有完全访问权限。

pub_new