SV之class

目录

class

类的定义方式 

类的实例化和对象的创建 

类的构造方法new 

this关键字

类的静态属性和静态方法

 Class Assignment 类的赋值

Shallow Copy 简单复制对象

deep copy 深层复制

Parameterised Classes 常数类

Classes Inheritance 类的继承 

Overriding class members 覆盖类的成员

super关键字

cast对象转型

Data Hiding and Encapsulation 数据隐藏和封装

Abstract Classes and Virtual Methods 抽象类和虚方法

Scope Resolution Operator ::  作用域操作符

Extern Methods 外部方法

typedef class  定义类


class

类包括数据以及操作数据的任务和函数,在类中任务和函数统称为方法。类可以通过对象句柄来动态创建、删除、分配和访问对象。

类的定义方式 

下面的例子中,类包括变量x和两个方法 。

class sv_class;
  //class properties
  int x;
  
 function new(int num);//构造方法,在创建对象时自动调用,一般用于初始化
    x=num;
  endfunction

  //method-1
  task set(int i);
    x = i;
  endtask
 
  //method-2
  function int get();
    return x;
  endfunction
endclass

类的实例化和对象的创建 

类可以认为是用户定义的一种数据类型,因此可以像定义其他数据类型那样来定义类:

sv_class class_1;//句柄就是指针,它指向了一个对象,在没有赋值前默认值为null

上面的代码相当于是定义了sv_class类的句柄class_1,这个句柄指向sv_class的一个对象。

也可以创建句柄数组

sv_class class_1[3];//创建了一个句柄数组,该数组包括3个元素,没有对象数组的说法

类中的属性和方法只有创建类的对象之后才能被引用,下面的语句创建了一个对象,并把该对象的句柄赋给了class_1

class_1 = new();//new函数会创建一个对象,并返回对象的句柄,这里将返回的句柄赋给class_1

也可以在创建句柄的同时创建对象,并将对象的句柄赋给创建的句柄

sv_class class_1 = new();
// 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;//默认值为null
 
    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 object, and not NULL");
 
      // Display the class member using the "handle"
      $display ("count = %0d", pkt.count);
    end
endmodule

 结果:

创建了对象后就可以引用类中的属性和方法

class_1.set(10); //calling set method to set value 10 to x
$display("Vlaue of x = %0d",class_1.get(););

 下面看一个完整的过程:

class sv_class;
  //class properties
  int x;
 
  //method-1
  task set(int i);
    x = i;
  endtask
 
  //method-2
  function int get();
    return x;
  endfunction
endclass
 
module sv_class_ex;
 sv_class class_1; //Creating Handle
 
  initial begin
    sv_class class_2 = new(); //Creating handle and Object
    class_1 = new(); //Creating Object for the Handle
 
    //Accessing Class methods
    class_1.set(10);
    class_2.set(20);
    $display("\tclass_1 :: Value of x = %0d",class_1.get());
    $display("\tclass_2 :: Value of x = %0d",class_2.get());
  end
endmodule

运行结果如下:

 

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'h'1, bit [2:0] mode = 5);//构造方法
    this.header = header;
    this.encode = 0;
    this.mode   = mode;
    this.stop   = 1;
  endfunction
 
  function display ();//成员方法
    $display ("Header = 0x%0h, Encode = %0b, Mode = 0x%0h, Stop = %0b", 
               this.header, this.encode, this.mode, this.stop);
  endfunction
endclass
//顶层模块实例化类
module tb_top;
  myPacket pkt0 [3];//创建句柄数组
 
  initial begin
      for(int i = 0; i < $size (pkt0); i++) begin
            pkt0[i] = new ();//调用构造方法实例化对象
           pkt0[i].display ();
       end
     end
endmodule

 结果:

类的构造方法new 

当一个类实例化对象时,这个过程是通过自动调用构造方法来实现的,构造方法还可以初始化对象。构造方法可以显示定义,也可以不定义。不定义时,编译器会自动为类创建一个不带参数的构造方法,该构造方法不进行任何初始化,作用仅仅是创建对象。构造方法没有返回值,也没有返回值类型,但是可以有参数.

下面来看构造方法显示定义和隐式定义的情况:

// Define a class called "Packet" with a 32-bit variable to store address
// Initialize "addr" to 32'hfade_cafe in the new function, also called constructor
class Packet;
  bit [31:0] addr;
 
  function new ();//显示定义构造方法,并在方法中对addr进行初始化
    addr = 32'hfade_cafe;
  endfunction
endclass
 
module tb;
 
  // Create a class handle called "pkt" and instantiate the class object
  initial begin
    // The class's constructor new() fn is called when the object is instantiated
    Packet pkt = new;   
 
    // Display the class variable - Because constructor was called during 
    // instantiation, this variable is expected to have 32'hfade_cafe;
    $display ("addr=0x%0h", pkt.addr);
  end
endmodule

 结果:

可以看到实例化对象后,addr有初始值.

// Define a simple class with a variable called "addr"
// Note that the new() function is not defined here
class Packet;//没有显示定义构造方法
  bit [31:0] addr;
endclass
 
module tb;
 
    // When the class object is instantiated, then the constructor is
    // implicitly defined by the tool and called
  initial begin
    Packet pkt = new;   
    $display ("addr=0x%0h", pkt.addr);
  end
endmodule

结果:

 没有构造方法,所以addr为默认值0.

再来看子类继承父类的情况;

// Define a simple class and initialize the class member "data" in new() function
class baseClass;
  bit [15:0] data;
 
  function new ();
    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:0] 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=0x%0h id=%0d mode=%0d", sc2.data, sc2.id, sc2.mode);
  end
endmodule

在子类构造方法中调用父类的构造方法采用关键字super.new, 子类中调用父类的成员都需要用到关键字super,结果如下:

构造方法的修饰符只能是local或protected,而不能是virtual和static.看下面例子;

class ABC;
  string fruit;
 
  // Note that the constructor is defined as "virtual" which is not allowed
  // in SystemVerilog. Class constructor cannot be "static" either
  virtual function new ();
    fruit = "Apple";
  endfunction
 
endclass
 
// Instantiate the class object and print its contents
module tb;
    initial begin
      ABC abc = new();
      $display ("fruit = %s", abc.fruit);
    end
endmodule

this关键字

this关键字用于引用类的属性。看下面例子:

class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  bit        write;
  string     pkt_type;
 
  //constructor
  function new(bit [31:0] addr,data,bit write,string pkt_type);//new是构造函数,用于对象的初始化
    addr  = addr;//方法中局部变量与类的成员变量同名
    data  = data;
    write = write;
    pkt_type = pkt_type;
  endfunction
 
  //method to display class prperties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0h",addr);
    $display("\t data  = %0h",data);
    $display("\t write = %0h",write);
    $display("\t pkt_type  = %0s",pkt_type);
    $display("---------------------------------------------------------");
  endfunction
endclass
 
module sv_constructor;
  packet pkt;
  initial begin
    pkt = new(32'h10,32'hFF,1,"GOOD_PKT");
    pkt.display();
  end
endmodule

运行结果如下:

可以看出,参数输出了自己的默认值,这是由于方法的局部变量的变量名与类的成员变量名相同导致的,解决这一问题的办法就是使用this关键字。

class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  bit        write;
  string     pkt_type;
  //constructor
  function new(bit [31:0] addr,data,bit write,string pkt_type);
    this.addr  = addr;//关键字this其实就是类的指针,它指向了类的对象,所以可以通过this.成员变量名来访问类的成员变量
    this.data  = data;//this.data代表类的成员变量,而等号右边的data为方法的局部变量
    this.write = write;
    this.pkt_type = pkt_type;
  endfunction
 
  //method to display class prperties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0h",addr);
    $display("\t data  = %0h",data);
    $display("\t write = %0h",write);
    $display("\t pkt_type  = %0s",pkt_type);
    $display("---------------------------------------------------------");
  endfunction
endclass
 
module sv_constructor;
  packet pkt;
  initial begin
    pkt = new(32'h10,32'hFF,1,"GOOD_PKT");
    pkt.display();
  end
endmodule

输出结果如下:

类的静态属性和静态方法

可以在类的属性和方法名前加上关键字static,这表示静态属性和静态方法静态属性和静态方法归类所有,表明在内存中单独开辟了一块区域来存储这些静态成员。静态成员可以通过"类名::静态成员名"来访问,而不需要创建对象。也就是说对于类的每一个对象,类的静态成员都是使用同一块内存地址!!即静态成员是每个类的对象共有的!!

请注意,对于静态方法,它只能访问类的静态成员而不能访问非静态成员,否则会报错

静态变量一般在定义时初始化,要保证在创建第一个对象之前静态变量就已经完成了初始化

首先来看非静态的例子:

class Packet;
  bit [15:0]   addr;
  bit [7:0]   data;
 
  function new (bit [15:0] ad, bit [7:0] d);
    addr = ad;
    data = d;
    $display ("addr=0x%0h data=0x%0h", 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

 对于非静态成员,类的每一个对象都会为它们单独开辟一块存储区域,每一个对象的同名成员都互不干扰,所以结果如下:

看下面静态成员的例子:

class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  bit        write;
  string     pkt_type;
  bit        pkt_id;
 
  //static property to keep track of number of pkt's created
  static int no_of_pkts_created;//定义静态变量
 
  //constructor
  function new(bit [31:0] addr,data,bit write,string pkt_type);
    this.addr  = addr;
    this.data  = data;
    this.write = write;
    this.pkt_type = pkt_type;
    //increment pkt count on creating an object
    no_of_pkts_created++;
    pkt_id     = no_of_pkts_created;
  endfunction
 
  //method to display class properties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0h",addr);
    $display("\t data  = %0h",data);
    $display("\t write = %0h",write);
    $display("\t pkt_type  = %0s",pkt_type);
    $display("---------------------------------------------------------");
  endfunction
 
  //static method to display next_pkt id 静态方法
  static function void next_pkt_id;
    $display("---------------------------------------------------------");
    $display("\t no of pkt's created  = %0d",no_of_pkts_created);
    $display("\t next pkt id  = %0d",no_of_pkts_created+1);
    $display("---------------------------------------------------------");
  endfunction
endclass
 
module static_properties_method;
  packet pkt[3];
  packet pkt_2;
 
  initial begin
    foreach(pkt[i]) begin
      pkt[i] = new(32'h10*i,32'hF*i,1,"GOOD_PKT");
      pkt[i].display();
    end
    pkt[1].next_pkt_id;
    //static class properties and methods are accessed without 
    //creating an object for pkt_2 handle.
    pkt_2.next_pkt_id;//调用静态方法
  end
endmodule

运行结果:

 

对比静态变量和非静态变量:

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是3个对象共有的,所有在p3时输出为3;而ctr、addr、data每个对象都有,所以输出每个对象各自的值.

普通成员只能通过例化对象来进行外部访问,而静态成员可以通过类名加上作用域操作符"::"来访问,格式如下:

类名::静态成员名;

举例:

 
class Packet;
  static int ctr=0;
 
   function new ();
      ctr++;
   endfunction
 
  static function get_pkt_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();   // Static call using :: operator "使用类名::静态成员名" 访问
    pkt[5].get_pkt_ctr();   // 普通成员只能通过例化对象访问
  end
endmodule

 结果:

 Class Assignment 类的赋值

看下面代码:

 packet   pkt_1;//定义句柄
  pkt_1  = new();//实例化对象
  packet   pkt_2;
  pkt_2  = pkt_1;

由于将pkt_1赋值给了pkt_2,两个变量都是句柄,所以最后两个句柄都指向同一个对象;所以通过pkt_1改变对象的任何属性,都会立即反映到pkt_2上。看下图的解释:

 

 看下面例子:

class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  bit write;
  string pkt_type;
  //constructor
  function new();
    addr  = 32'h10;
    data  = 32'hFF;
    write = 1;
    pkt_type = "GOOD_PKT";
  endfunction
 
  //method to display class properties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0d",addr);
    $display("\t data  = %0h",data);
    $display("\t write = %0d",write);
    $display("\t pkt_type  = %0s",pkt_type);
    $display("---------------------------------------------------------");
  endfunction
endclass
module class_assignment;
  packet pkt_1;
  packet pkt_2;
 
  initial begin
    pkt_1 = new();
    $display("\t****  calling pkt_1 display  ****");
    pkt_1.display();
    //assigning pkt_1 to pkt_2
    pkt_2 = pkt_1;//将句柄1赋值给pkt_2
    $display("\t****  calling pkt_2 display  ****");
    pkt_2.display();
    //changing values with pkt_2 handle
    pkt_2.addr = 32'hAB;
    pkt_2.pkt_type = "BAD_PKT";
 
    //changes made with pkt_2 handle will reflect on pkt_1 通过句柄2改变的属性会反映到句柄1上,因为两个句柄指向同一个对象
    $display("\t****  calling pkt_1 display  ****");
    pkt_1.display();
  end
endmodule

运行结果如下:

Shallow Copy 简单复制对象

 看下面代码:

packet   pkt_1;
pkt_1  = new();
packet   pkt_2;
pkt_2  = new pkt_1;//通过关键字new来复制句柄

请注意,对象并不能复制,只能复制它们的句柄。上面通过关键字new将pkt_1复制给pkt_2,从而创建了一个新的对象,只是这个对象的内容与pkt_1相同,但是两个句柄的值是不同的,这需要注意

具体的操作情况看下面的图 :

可以看出,复制之后改变pkt_2的属性并不会改变pkt_1的属性,即两者之间没有关联

 可以看出采用这种复制方式,只会对最高层次的类的对象进行复制;如果这个类中还包括指向其他类的句柄,那么下层的对象并不会复制只会复制下层对象的句柄。所以此时,复制之后的两个对象共享一个下层对象,如上图中的pkt_1.ad_r和pkt_2.ad_r位同一个对象。

看下面具体例子:

//-- class ---
class address_range;
  bit [31:0] start_address;
  bit [31:0] end_address  ;
  function new();
    start_address = 10;
    end_address   = 50;
  endfunction
endclass
 
//-- class ---  
class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  address_range ar; //class handle 类的句柄
 
  //constructor
  function new();
    addr  = 32'h10;
    data  = 32'hFF;
    ar = new(); //creating object
  endfunction
  //method to display class prperties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0h",addr);
    $display("\t data  = %0h",data);
    $display("\t start_address  = %0d",ar.start_address);
    $display("\t end_address  = %0d",ar.end_address);
    $display("---------------------------------------------------------");
  endfunction
endclass
 
// -- module ---
module class_assignment;
  packet pkt_1;
  packet pkt_2;
 
  initial begin
    pkt_1 = new();   //creating pkt_1 object
    $display("\t****  calling pkt_1 display  ****");
    pkt_1.display();
  
    pkt_2 = new pkt_1;   //creating pkt_2 object and copying pkt_1 to pkt_2 复制对象
    $display("\t****  calling pkt_2 display  ****");
    pkt_2.display();
 
    //changing values with pkt_2 handle
    pkt_2.addr = 32'h68;
    pkt_2.ar.start_address = 60;//修改下层对象的属性
    pkt_2.ar.end_address = 80;
    $display("\t****  calling pkt_1 display after changing pkt_2 properties ****");
 
    //changes made to pkt_2.ar properties reflected on pkt_1.ar, so only handle of the object get copied, this is called shallow copy
    pkt_1.display();
    $display("\t****  calling pkt_2 display after changing pkt_2 properties ****");
    pkt_2.display(); //
  end
endmodule

运行结果如下:

 

可以看出在pkt_2修改了对象ar中的属性start_address后,pkt_1和pkt_2中的ar的属性都改变了,也就是说这种简单复制并没有将下层对象复制,只是将最高层次的对象进行了复制,此时下层对象为两者共享。

请注意这种简单复制与上面类的赋值的不同,看下图:

类的赋值只是重新定义了一个句柄,该句柄与原来的句柄指向同一个对象;而上面的简单复制是重新创建了一个对象,只是这个对象的内容和原来对象的内容相同。

deep copy 深层复制

 对于简单复制,对象并没有复制,复制的只是句柄。为了进行深层复制,需要自定义复制函数在自定义函数中,将创建新对象,并将所有类属性复制到新句柄,并返回新句柄

看下面这个例子,在这个例子中copy()方法被添加到每一层的类中,具体如下:

//-- class ---
class address_range;//定义下层类
  bit [31:0] start_address;
  bit [31:0] end_address  ;
 
  function new();//构造方法初始化对象
    start_address = 10;
    end_address   = 50;
  endfunction
  //copy method
  function address_range copy;//下层类中的copy函数
    copy = new();
    copy.start_address = this.start_address;
    copy.end_address   = this.end_address;
    return copy;//返回值的类型为该类
  endfunction
endclass
 
//-- class ---  
class packet;
  //class properties
  bit [31:0] addr;
  bit [31:0] data;
  address_range ar; //class handle
 
  //constructor
  function new();
    addr  = 32'h10;
    data  = 32'hFF;
    ar = new(); //creating object  例化下层类的对象,一般在上层类的构造方法中实现
  endfunction
 
  //method to display class prperties
  function void display();
    $display("---------------------------------------------------------");
    $display("\t addr  = %0h",addr);
    $display("\t data  = %0h",data);
    $display("\t start_address  = %0d",ar.start_address);
    $display("\t end_address  = %0d",ar.end_address);
    $display("---------------------------------------------------------");
  endfunction
 
  //copy method
  function packet copy();//上层类中的copy函数
    copy = new();
    copy.addr = this.addr;
    copy.data = this.data;
    copy.ar   = ar.copy;//calling copy function of tr
    return copy;
  endfunction
endclass
// -- module ---
module class_assignment;
  packet pkt_1;
  packet pkt_2;
  initial begin
    pkt_1 = new();   //creating pkt_1 object
    $display("\t****  calling pkt_1 display  ****");
    pkt_1.display();
    pkt_2 = new();   //creating pkt_2 object
    $display("\t****  calling pkt_2 display  ****");
    pkt_2.display();
    pkt_2 = pkt_1.copy(); //calling copy method 调用上层类的copy函数
    //changing values with pkt_2 handle
    pkt_2.addr = 32'h68;//修改赋值后的属性
    pkt_2.ar.start_address = 60;
    pkt_2.ar.end_address = 80;
    $display("\t****  calling pkt_1 display after changing pkt_2 properties ****");
    pkt_1.display();
    $display("\t****  calling pkt_2 display after changing pkt_2 properties ****");
    pkt_2.display();
  end
endmodule

运行结果:

可以看出,包括下层类在内都已经实现了复制,复制之后两个对象在不同的地址中,互不影响。

Parameterised Classes 常数类

 看下面例子:

class packet #(parameter int ADDR_WIDTH = 32,DATA_WIDTH = 32);
  bit [ADDR_WIDTH-1:0] address;
  bit [DATA_WIDTH-1:0] data   ;
 
  function new();
    address = 10;
    data    = 20;
  endfunction
endclass

常数类类似于verilog中的常数module,常数可以用于描述类中的属性,例如上面的位宽。常数值在类例化对象时可以进行修改并覆盖。

packet pkt;  //创建一个对象并且该对象的成员变量固定的位宽

packet #(32,64) pkt;  //用修改后的参数值改变固定的位宽

Classes Inheritance 类的继承 

 新的类可以在已存在的类的基础上被创建,也就是继承。被继承的类为父类,新产生的类为子类子类可以继承并修改父类的所有属性和方法,也可以创建自己的属性和方法。看下面一个简单的例子:

class parent_class;
  bit [31:0] addr;
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
endclass
 
module inheritence;
  initial begin
    child_class c = new();
    c.addr = 10;
    c.data = 20;
    $display("Value of addr = %0d data = %0d",c.addr,c.data);
  end
endmodule

运行结果如下:

Overriding class members 覆盖类的成员

父类的成员可以在子类中修改并覆盖(这是重写不是重载,注意两者的区别,看下面的例子:

class parent_class;
  bit [31:0] addr;
 
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
  function display();//覆盖父类中的display方法
    $display("Data = %0d",data);
  endfunction
endclass
 
module inheritence;
  initial begin
    child_class c=new();
    c.addr = 10;
    c.data = 20;
    c.display();
  end
endmodule

 

super关键字

子类可以通过关键字super引用父类中的成员当父类的成员被子类成员覆盖时,必须用super来访问父类成员。看下例:

class parent_class;
  bit [31:0] addr;
 
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
 
  function display();
    super.display();//获取父类的display方法
    $display("Data = %0d",data);
  endfunction
 
endclass
 
module inheritence;
  initial begin
    child_class c=new();
    c.addr = 10;
    c.data = 20;
    c.display();
  end
endmodule

运行结果:

class Packet;
  int addr;
 
  function display ();
    $display ("[Base] addr=0x%0h", addr);
  endfunction
endclass
 
class extPacket extends Packet;
  function display();
    super.display();                          // Call base class display method
    $display ("[Child] addr=0x%0h", addr);
  endfunction
 
  function new ();
    super.new ();
  endfunction
endclass
 
module tb;
   Packet p;
    extPacket ep;
 
    initial begin
      ep = new();
      p = new();
      ep.display();
    end
endmodule

 结果:

cast对象转型

 动态转换关键字$cast可以强制将父类句柄(或引用)转换为子类的句柄(或引用)如果强制转换无效,这是因为所指向的对象的实际类型不是所需子类的类型,则转换失败

我们来看它的用法:

一般来说将一个子类的句柄赋给一个父类的句柄(向上转型),这通常是合法的;但是将一个父类句柄赋值给一个子类的句柄(向下转型),这通常是非法的。这时我们就可以利用关键字$cast将一个父类句柄强制转换为子类句柄。看下面的例子:

class parent_class;
  bit [31:0] addr;
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
  function display();
    super.display();
    $display("Data = %0d",data);
  endfunction
endclass
 
module inheritence;
  initial begin
    parent_class p=new();
    child_class  c=new();
    c.addr = 10;
    c.data = 20;
    p = c;        //将子类句柄赋给父类句柄,这不会有问题
    c.display();
  end
endmodule

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指向一个子类对象,但是bc本身还是一个父类句柄
      
		bc.display ();//子类和父类中都有display方法
		sc.display ();
	end
endmodule

结果:

可以看出虽然bc在被赋给子类句柄后,但是bc.display()调用的方法还是父类的display()方法,这是因为普通方法(不包括虚方法)调用取决于句柄类型而不是句柄所指向的对象类型.

class parent_class;
  bit [31:0] addr;
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
 
  function display();
    super.display();
    $display("Data = %0d",data);
  endfunction
endclass
 
module inheritence;
  initial begin
    parent_class p=new();
    child_class  c=new();
    c.addr = 10;
    c.data = 20;
    c = p;        //将父类句柄赋给子类句柄,这会报错
    c.display();
  end
endmodule

可以看出这样直接将父类句柄赋给子类,编译器会报错;

class parent_class;
  bit [31:0] addr;
 
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
  function display();
    super.display();
    $display("Data = %0d",data);
  endfunction
endclass
module inheritence;
  initial begin
    parent_class p;//注意这里并没有实例化对象,只是创建了句柄,注意这里和声明例子的区别
    child_class  c=new();
    child_class  c1;
    c.addr = 10;
    c.data = 20;
    p  = c;        //p 是父类的句柄,但是指向了子类的对象
    c1 = p;        //将父类句柄赋给子类
    c1.display();
  end
endmodule

将父类指向子类对象的句柄赋给子类句柄还是会报错,结果如下:

class parent_class;
  bit [31:0] addr;
 
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
class child_class extends parent_class;
  bit [31:0] data;
 
  function display();
    super.display();
    $display("Data = %0d",data);
  endfunction
endclass
 
module inheritence;
  initial begin
    parent_class p;
    child_class  c=new();
    child_class  c1;
    c.addr = 10;
    c.data = 20;
 //$cast(c,p) 在没有执行p=c,也就是将p指向一个子类对象之前,这样强制转换是错误的
    p = c;         //p 指向子类对象
    $cast(c1,p);   //强制将p转换为子类句柄c1,此时c1指向p指向的对象
 //$cast(a,b)方法的条件是句柄a、b指向的对象类型要相同
    c1.display();
  end
endmodule

 没有报错

Data Hiding and Encapsulation 数据隐藏和封装

 将类中的数据隐藏,并且只可以通过方法来访问的这种技术叫做封装。在默认的情况下,类中的所有成员都可以通过对象的句柄来访问,但是有些情况下这可能会破坏某些类成员的值。为了限制外部对象对类内部成员的访问,可以在成员名前加上前缀:

  • local
  • protected

(1)local

可以通过声明成员为本地成员避免对类成员的外部访问父类的对象也不能访问,这种访问也是属于外部访问),任何违规都可能导致编译错误,local成员对于子类来说也不可见,所以不能通过子类来访问local成员。语法如下:

 local integer x;

看下例:

class parent_class;
  local bit [31:0] tmp_addr;//声明为本地成员
   
  function new(bit [31:0] r_addr);
    tmp_addr = r_addr + 10;
  endfunction
 
  function display();
    $display("tmp_addr = %0d",tmp_addr);
  endfunction
endclass
 
 
//   module
module encapsulation;
  initial begin
    parent_class p_c = new(5);
        
    p_c.tmp_addr = 20;  //修改本地成员是违法的
    p_c.display();
  end
endmodule

 编译错误,类的本地成员禁止外部访问;

local成员只能在类的内部访问,看下例:

class parent_class;
  local bit [31:0] tmp_addr;
   
  function new(bit [31:0] r_addr);
    tmp_addr = r_addr + 10;//内部访问本地成员
  endfunction
 
  function display();
    $display("tmp_addr = %0d",tmp_addr);
  endfunction
endclass
 
//   module
module encapsulation;
  initial begin
    parent_class p_c = new(5);
    p_c.display();
  end
endmodule

 此时没有报错

(2)protected

有时候需要定义一些只能被子类访问的成员父类的对象也不能访问,这种访问也是属于外部访问),这时需要在这些成员前面加上protected关键字,语法如下:

protected integer x;

 看下面例子:

class parent_class;
  protected bit [31:0] tmp_addr;//受保护的属性
   
  function new(bit [31:0] r_addr);
    tmp_addr = r_addr + 10;
  endfunction
 
  function display();
    $display("tmp_addr = %0d",tmp_addr);
  endfunction
endclass
 
class child_class extends parent_class;
  function new(bit [31:0] r_addr);
    super.new(r_addr);//调用父类的构造方法,一般这需要在子类的第一句声明
  endfunction
   
  function void incr_addr();
    tmp_addr++;//子类访问受保护对象
  endfunction 
endclass
 
//   module
module encapsulation;
  initial begin
    parent_class p_c = new(5);
    child_class  c_c = new(10);
         
    // 父类的对象访问受保护的属性,这是非法的
    p_c.tmp_addr = 10;
    p_c.display();
    
    c_c.incr_addr();  //Accessing protected variable in extended class
    c_c.display();
  end
endmodule

 在父类对象中访问受保护的属性是违法的,所以报错。

class parent_class;
  protected bit [31:0] tmp_addr;
   
  function new(bit [31:0] r_addr);
    tmp_addr = r_addr + 10;
  endfunction
 
  function display();
    $display("tmp_addr = %0d",tmp_addr);
  endfunction
endclass
 
class child_class extends parent_class;
  function new(bit [31:0] r_addr);
    super.new(r_addr);
  endfunction
   
  function void incr_addr();
    tmp_addr++;//通过子类来访问受保护的属性
  endfunction 
endclass
 
//   module
module encapsulation;
  initial begin
    child_class  c_c = new(10);
     
    c_c.incr_addr();  //Accessing protected variable in extended class
    c_c.display();
  end
endmodule

 运行结果:

Abstract Classes and Virtual Methods 抽象类和虚方法

在类名前加上关键字virtual的类称为抽象类抽象类不能实例化,只能被继承。语法如下:

virtual class packet;

抽象类中可以有虚方法,虚方法是一种基本的多态构造,它覆盖所有父类中的方法。 

//abstract class
virtual class packet;
  bit [31:0] addr;
 
  function display();
    $display("Addr = %0d",addr);
  endfunction
endclass
 
module virtual_class;
  initial begin
    packet p;
    p = new();//实例化抽象类会报错
    p.display();
  end
endmodule

 

virtual method 

在上面讲继承的时候我们看到,即使将一个子类句柄赋给一个父类句柄后,当调用子类和父类中同名的方法时,赋值后的父类句柄调用的方法还是父类的方法,这是因为普通方法的调用取决于调用该方法的句柄类型而不是句柄指向对象的类型。但是假如在方法名前加virtual关键字修饰变成虚方法后呢?情况会如何? 

第一种普通方法情况:

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

module tb;
   Packet bc;
   ExtPacket sc;

	initial begin
        sc = new (32'hfeed_feed, 32'h1234_5678);	

        bc = sc; //将子类句柄赋给父类句柄
		bc.display ();
	end
endmodule

可以看出此时bc调用的display()方法为父类中的方法;下面将父类的display()方法加上virtual:

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

 结果:

可以看出此时bc调用的display()方法为父类中的方法;需要值得注意的是,如果想在子类中覆盖父类的方法,最好在父类的方法前加上关键字virtual,也就是将父类方法变为虚方法(子类的方法加不加virtual都行,但是假如子类的方法被下一层次的子类覆盖,此时子类方法也要变成虚方法).也就是说对于虚方法的调用取决于句柄指向的对象类型而非句柄类型!

pure virtual method 纯虚方法 

抽象类中的虚方法可以用关键字pure定义为纯虚方法,纯虚方法没有方法体,只能在继承抽象类的子类中实现.看下例:

virtual class BaseClass;//定义抽象类
  int data;
 
  pure virtual function int getData();//抽象类中的纯虚方法
endclass
 
class ChildClass extends BaseClass;//继承抽象类
  virtual function int getData();//重写抽象类的纯虚方法。重写后的方法必须与原方法有相同的返回值
    data = 32'hcafe_cafe;
    return data;
  endfunction
endclass
 
module tb;
  ChildClass child;
  initial begin
    child = new();
    $display ("data = 0x%0h", child.getData());
  end
endmodule

结果:

Scope Resolution Operator ::  作用域操作符

类作用域操作符:: 用于唯一地标识特定类的成员 ,允许从类的外部访问·,以及在子类中访问public或protected成员。

//class
class packet;
         bit [31:0] addr;
  static bit [31:0] id;
 
  function display(bit [31:0] a,b);
    $display("Vlaues are %0d %0d",a,b);
  endfunction
endclass
 
module sro_class;
  int id=10;
  initial begin
    packet p;
    p = new();
    packet::id = 20;//访问静态成员
    p.display(packet::id,id);
  end
endmodule

Extern Methods 外部方法

方法的方法体可以在类的外部定义;此时需要在类内部定义完整的方法名和参数列表以及加上关键字extern;在类的外部需要在方法名前加上类名和类作用域操作符。具体看下例:

//class with extern function
class packet;
  bit [31:0] addr;
  bit [31:0] data;
 
  //function declaration - extern indicates out-of-body declaration
  extern virtual function void display();//声明外部方法
endclass
 
//function implementation outside class body
function void packet::display();//外部方法
  $display("Addr = %0d Data = %0d",addr,data);
endfunction
   
module extern_method;
  initial begin
    packet p;
    p = new();
    p.addr = 10;
    p.data = 20;
    p.display();
  end
endmodule

typedef class  定义类

有些情况下某个类需要在它被定义之前声明,此时因为编译器还不认识这个新的类就会报错,这种情况下就需要用到关键字typedef来声明这个类名。看下例:

typedef class c2;//声明类c2
//class-1
class c1;
  c2 c;    //using class c2 handle before declaring it.
endclass
 
//class-2定义类c2
class c2;
  c1 c;
endclass
  
module typedef_class;
  initial begin
    c1 class1;
    c2 class2;
  end
endmodule

  • 27
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值