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
newasigna nueva memoria. Las llamadas sucesivas sobre el mismo handle liberan la memoria anterior. Un objeto se desasigna asignando su handle anullo 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 miembrolocalsolo es accesible desde métodos de la misma clase. No es visible para clases derivadas.protected: Un miembroprotectedes comolocal, 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