Manejo básico de clases 1ª parte

Clases

Las clases se pueden declarar en un programa, un módulo o un paquete. Un elemento de una clase puede ser una propiedad (class property) o un método (class method).

Sintaxis:

 [ virtual ] class class-name [ parameter-list ] [ extends class-type [ ( argument-list ) ] ] ; { class-item } endclass [ : class-name ] 

Ejemplo: Una clase para una transacción de bus

class BusTran;
// Propiedades de la clase
logic [31:0] addr, crc, data[8];
// Métodos de la clase
task display;
    $display("BusTran: addr = %0h", addr);
endtask : display

function void compute_crc();
    crc = addr ^ data.xor;
endfunction : compute_crc;
endclass : BusTran

Objetos

Los objetos son instancias de clases que se crean dinámicamente.

 BusTran btran; // Declara un handle nulo (valor null) 

btran = new; // Crea un objeto BusTran y hace que btran apunte a él

 // Inicializa todas las variables del objeto a 'X'

 

Se puede acceder a las variables y métodos del objeto a través de su nombre:

 btran.addr = 32'h127;
 btran.display;
 

Constructores

Un constructor es una función especial new que se define dentro de la clase. No tiene tipo de retorno y devuelve un objeto del tipo de la clase.

Constructor sin argumentos:

class BusTran;
logic [31:0] addr, crc, data[8];
function new;
    addr = 0;
    foreach (data[i]) data[i] = 0;
endfunction : new
endclass : BusTran

Constructor con argumentos:

class BusTran;
logic [31:0] addr, crc, data[8];
function new (logic [31:0] addr = 0, data = 0);
    this.addr = addr;
    foreach (this.data[i]) this.data[i] = data;
endfunction : new
endclass : BusTran

Gestión de la memoria y Copia

  • Gestión de memoria: Cada llamada a new asigna nueva memoria. Las llamadas sucesivas sobre el mismo handle liberan la memoria anterior. Un objeto se desasigna asignando su handle a null o cuando sale de su ámbito.
  • Asignación y Renombrado: No hay que confundir el handle (la variable que apunta) con el objeto (la instancia en memoria). Asignar un handle a otro solo renombra el puntero.
  • Shallow Copy (Copia Superficial): Copia las variables del objeto, pero si hay objetos anidados, solo copia sus handles. b3 = new b1; realiza una copia superficial. Lo veremos con más detalle en la seguna parte dedicada a manejo básico de clases.
  • Deep Copy (Copia Profunda): Copia el objeto y, de forma recursiva, todos los objetos anidados. Requiere un método de copia personalizado. Lo veremos con más detalle en la segunda parte dedicada a manejo básico de clases.
module testbench;
Bustran btran,btran2;
initial
begin
btran = new;		// allocates a new BusTran object
btran = new;	 // allocates a second one, frees the first
btran2 = new;	 // allocates a third one
btran =null      //frees the second
end

Variables y Métodos Estáticos

Las variables y métodos estáticos son compartidos por todas las instancias de una clase. Se puede acceder a ellos sin necesidad de crear un objeto.

Variables Estáticas:

class BusTran;
static int count = 0; // Variable compartida
int id;
function new ();
    id = ++count; // Cada objeto nuevo incrementa el contador
endfunction : new
endclass : BusTran

Veamos en el siguiente ejemplo cómo hacerlo. Destaca la utilización recomendada del operador ::

module testbench;
Bustran btran1,btran2;
initial
begin
b1::count=10 //no object creation needed
btran1 = new();		// allocates a new BusTran object, with btran1.id=11
btran2 = new btran1;	 // allocates a second one, with btran2.id=11
btran2 =new(33)      //fbtran2.id=12
end

Métodos Estáticos:

Al igual que las subrutinas regulares, no puede tener acceso a miembros de clase no estáticos (variables y métodos).
Se puede llamar sin creación de objetos

class BusTran;
static int count = 0;
static function int next_id ();
    next_id = ++count;
endfunction : next_id;
endclass : BusTran

Veamos ahora su uso. Fijémonos que ahora los «handle» los declararé dentro del bloque initial, con lo cual cuando dicho initial finalice el objeto será recolectado y dejará de ocupar espacio de memoria

module testbench;

initial begin
Bustran b1,b2:
b1::count=b1:next_id();
// Se puede llamar sin crear un objeto
int next = BusTran::next_id();
end

Ocultación y Encapsulación de Datos

  • local: Un miembro local solo es accesible desde métodos de la misma clase. No es visible para clases derivadas.
  • protected: Un miembro protected es como local, pero sí es visible para las clases derivadas.
class BusTran;
local int id;
function int compare_id (BusTran b);
    // Es legal acceder a b.id porque estamos dentro de la misma clase
    return (this.id == b.id);
endfunction : compare_id
endclass : BusTran

Clases Derivadas y Herencia

Relación «Is a» (Herencia): Una clase puede derivarse de una clase padre, heredando todas sus propiedades y métodos.

// Clase padre
class BusTran;
logic [31:0] addr, crc, data[8];
int id;
endclass : BusTran

// Clase derivada
class PCITran extends BusTran;
logic [31:0] data; // Sobrescribe la propiedad de la clase padre
endclass : PCITran

Upcasting (Seguro): Asignar un objeto de una clase derivada a un handle de su clase padre.

BusTran Bus_padre;
PCITran Bus_hijo;

// creamos un objeto PCITran
Bus_hijo=new();
//asignación legal
Bus_padre=Bus_hijo;

Downcasting (Verificación segura): Asignar un handle de clase padre a uno de clase derivada. Requiere el uso de $cast.

 if ($cast(handle_derivado, handle_base)) 
begin // Éxito: $cast verificó en tiempo de ejecución que handle_base 
// SÍ apuntaba a un objeto compatible con MiClaseDerivada. // Ahora es seguro usar handle_derivado. handle_derivado.metodo_especifico_derivado(); 
end 
else begin // Fallo: handle_base no apuntaba a un objeto MiClaseDerivada. 
end