Modelo de Programación Concurrente en SystemVerilog
SystemVerilog introduce nuevas herramientas avanzadas para manejar la concurrencia, esenciales para la verificación de diseños complejos. Ni que decir tiene que hereda toda la concurrencia que hemos visto en Veirlog; pero hace unas incorporaciones y mejoras interesantes-
- Lanzar procesos paralelos:
fork/join
- Aislamiento de variables: Variables locales a los threads.
- Intercomunicación: Mailboxes y variables compartidas.
- Arbitraje y exclusión mutua: Semáforos.
- Sincronización: Eventos.
Construcción fork
/join
SystemVerilog mejora el fork/join
de Verilog con modificadores que controlan cuándo el proceso «padre» continúa su ejecución.
join
: El padre espera a que todos los procesos hijos terminen. (Comportamiento por defecto) .join_any
: El padre espera a que el primer proceso hijo termine.join_none
: El padre no espera y continúa su ejecución en paralelo con los hijos.
Ejemplo de Control del join
:
initial begin fork begin fork ##1 mi_variable_any=2'd0; // child_1 (termina en t=1) ##2 mi_variable_any=2'd1; // child_2 (termina en t=2) ##3 mi_variable_any=2'd2; // child_3 (termina en t=3) join_any // El padre se reanuda en t=1 con join_any ##3 mi_variable_any=2'd3; // mi_variable pasa a 3 en t=4 con join_any ##2 ; end begin fork ##1 mi_variable_none=2'd0; // child_1 (termina en t=1) ##2 mi_variable_none=2'd1; // child_2 (termina en t=2) ##3 mi_variable_none=2'd2; // child_3 (termina en t=3) join_none // El padre se reanuda en t=0 con join_none ##3 mi_variable_none=2'd3; // mi_variable pasa a 3 en t=3 con join_none ##2 ; end begin fork ##1 mi_variable_all=2'd0; // child_1 (termina en t=1) ##2 mi_variable_all=2'd1; // child_2 (termina en t=2) ##3 mi_variable_all=2'd2; // child_3 (termina en t=3) join // El padre se reanuda en t=3 con join ##3 mi_variable_all=2'd3; // mi_variable pasa a 3 en t=6 con join ##2 ; end join $finish; end

Semáforos
Un semáforo es un mecanismo de arbitraje para controlar el acceso a recursos compartidos. Funciona como un contador que gestiona un número de «llaves» o «permisos».
new(keyCount)
: Crea un semáforo conkeyCount
llaves.get(N)
: SolicitaN
llaves. Si no hay suficientes, el proceso se bloquea hasta que estén disponibles.put(N)
: DevuelveN
llaves al semáforo, permitiendo que otros procesos bloqueados puedan continuar.try_get(N)
: Intenta obtenerN
llaves sin bloquearse. Devuelve1
si tiene éxito y0
si no.
Aplicación: Proteger un bus APB de accesos simultáneos por parte de un proceso principal y un manejador de interrupciones.
- Declarar e inicializar el semáforo (con una sola llave para el bus):
semaphore APB_semaph; initial APB_semaph = new(1);
- Proteger el acceso al recurso en las tasks:
task automatic write_apb; // ... inputs ... begin APB_semaph.get(); // Espera y toma la llave del bus // --- Sección Crítica --- // Acceso al bus APB... // ----------------------- APB_semaph.put(); // Devuelve la llave end endtask
Esto garantiza que la operación de acceso al bus sea atómica, evitando conflictos y condiciones de carrera.
Mailboxes
Un mailbox es un mecanismo de comunicación entre procesos, que funciona como una cola FIFO para pasar mensajes.
new(size)
: Crea un mailbox. Sisize
es 0, la capacidad es ilimitada.put(message)
: Coloca un mensaje en el mailbox. Si está lleno, el proceso se bloquea.get(message)
: Obtiene un mensaje del mailbox. Si está vacío, el proceso se bloquea.try_put()
/try_get()
: Versiones no bloqueantes.peek()
: Lee un mensaje sin quitarlo del mailbox.
Se pueden parametrizar para garantizar que solo se envíen datos de un tipo específico, evitando errores en tiempo de ejecución.
// Mailbox que solo acepta strings typedef mailbox #(string) string_mbox; string_mbox m = new();
Aplicación: Un manejador de interrupciones notifica al proceso principal qué periférico está libre.
// Mailbox para periféricos libres mailbox perif_libre; initial begin perif_libre = new(); // Inicialmente, todos los periféricos están libres perif_libre.put(1); perif_libre.put(2); end // Proceso principal // Espera a que un periférico esté libre perif_libre.get(perif_a_atender); // ... atiende al periférico ... // Manejador de interrupciones always @(IRQ) begin // ... lee el vector de interrupción ... // Notifica al proceso principal que el periférico está libre perif_libre.put(irq_vector); end
Eventos
Los eventos son la forma más fundamental de sincronización en SystemVerilog.
- Declaración:
event mi_evento;
- Disparo (Trigger):
->mi_evento;
(disparo bloqueante) - Disparo no bloqueante:
->>mi_evento;
- Espera:
@(mi_evento);
owait(mi_evento.triggered);
Ejemplo clásico:
// Declaración del evento event received_data; // Proceso que dispara el evento always @(posedge clk) if (last_dat_packet) -> received_data; // Proceso que espera el evento always @(received_data) data_buf = {data_pkt[0], data_pkt[1]};
SystemVerilog también permite operaciones más complejas como esperar una secuencia de eventos (wait_order
) y fusionar o comparar eventos.