Las asignaciones bloqueantes y no bloqueantes son dos formas de programar cuándo se actualiza una variable dentro de la simulación Verilog.
En C++, Java o Python, a = b siempre significa lo mismo: “Copia el valor de b en a ahora mismo”.
En Verilog, tenemos dos operadores de asignación: = y <=. Parecen inofensivos, pero confundirlos es la causa número 1 de errores indetectables, comportamientos aleatorios y Race Conditions (Condiciones de Carrera).
Hoy vamos a entender por qué existen y qué convención evita la mayoría de errores.
Dos filosofías de tiempo
Para entender la diferencia, tenemos que visualizar cómo funciona el simulador (y el hardware) por dentro.
Asignación bloqueante (=)
Es la asignación “clásica” del software. Se llama bloqueante porque “bloquea” la ejecución del bloque hasta que se completa.
- Las líneas se ejecutan en orden secuencial, una tras otra.
- El valor se actualiza inmediatamente. La siguiente línea de código ya ve el valor nuevo.
Analogía: Es como pasar una nota en clase. Yo escribo la nota, te la paso a ti, tú la lees y se la pasas al siguiente. El mensaje viaja secuencialmente.
Asignación no bloqueante (<=)
Esta es exclusiva de los HDLs. Se llama no bloqueante porque no detiene la ejecución.
- El simulador lee el valor de la derecha y “agenda” la actualización para el final del paso de tiempo actual.
- Todas las asignaciones dentro del bloque ocurren en paralelo.
- La siguiente línea de código todavía ve el valor viejo, porque la actualización aún no se ha “aplicado”.
Analogía: Es como si el profesor grita “¡Escribid todos el resultado en la pizarra!”. Todos los alumnos escriben a la vez. Nadie puede copiar lo que ha escrito el compañero en ese mismo instante porque todos están escribiendo simultáneamente.
El ejemplo del registro de desplazamiento
Vamos a ver qué pasa cuando usamos el operador incorrecto intentando crear un Shift Register (una tubería donde el dato pasa de A -> B -> C).
Supongamos que inicialmente: A = 1, B = 0, C = 0.
El error común usando =
// ❌ MAL: Intentando hacer lógica secuencial con bloqueante
always @(posedge clk) begin
B = A; // Paso 1: B toma el valor de A (1) INMEDIATAMENTE.
C = B; // Paso 2: C toma el valor de B... ¡que ahora es 1!
endResultado: En un solo ciclo de reloj, el valor de A ha viajado hasta C. La descripción ya no representa las dos etapas de pipeline que pretendíamos: el valor calculado para C depende directamente de A dentro del mismo evento.
La forma correcta usando <=
// ✅ BIEN: Lógica secuencial correcta
always @(posedge clk) begin
B <= A; // "Agenda" que B valdrá lo que vale A ahora (1).
C <= B; // "Agenda" que C valdrá lo que vale B ahora (0, el valor viejo).
end
// --- FIN DEL CICLO ---
// Ahora se aplican los cambios a la vez.Resultado:
Bpasa a valer 1.Cpasa a valer 0. El dato se ha movido solo una posición, tal como debe ocurrir en un registro de desplazamiento.
Condiciones de carrera
Una Race Condition ocurre cuando el resultado de tu circuito depende de la “suerte” (o más técnicamente, del orden arbitrario en que el simulador ejecuta las cosas).
Imagina que tienes dos bloques always separados interactuando entre sí.
// Bloque 1
always @(posedge clk) a = b;
// Bloque 2
always @(posedge clk) b = a;Si usas =, el resultado depende de qué bloque ejecute primero el simulador (algo que no puedes controlar).
- Si ejecuta el Bloque 1 primero:
acambia, y luego el Bloque 2 usa el nuevoa. - Si ejecuta el Bloque 2 primero:
bcambia, y luego el Bloque 1 usa el nuevob.
Esto genera una condición de carrera en la simulación y puede hacer que el modelo simulado no coincida con la intención del circuito sintetizado.
Si usas <=:
always @(posedge clk) a <= b;
always @(posedge clk) b <= a;No importa el orden. Ambos leen los valores viejos al principio, y ambos escriben al final. El resultado es un intercambio (swap) perfecto y estable.
¿Qué hardware se genera?
Es importante entender que el operador por sí solo no decide el hardware. La lista de sensibilidad, las condiciones y las dependencias del bloque determinan si se infiere lógica combinacional o registros; = y <= controlan sobre todo el orden de actualización del modelo.
Con = (bloqueante)
En un bloque combinacional, permite que una asignación posterior use inmediatamente el valor calculado por una anterior. En un bloque con posedge clk todavía puede inferir flip-flops, aunque su semántica puede introducir carreras o eliminar etapas lógicas pretendidas.
Con <= (no bloqueante)
En un bloque secuencial, hace que todas las expresiones lean el estado anterior y que las actualizaciones se apliquen al final del paso de simulación. Por eso representa con claridad el comportamiento simultáneo de los flip-flops.
Una convención práctica
Para evitar dolores de cabeza, la comunidad de diseño FPGA sigue un estándar estricto. Si lo sigues, nunca tendrás problemas de Race Conditions.
| Tipo de Circuito | Bloque Verilog | Operador a usar |
|---|---|---|
| Lógica Combinacional | always @(*) | = |
| Lógica Secuencial | always @(posedge clk) | <= |
| Asignación Continua | assign (fuera de always) | = |
¡Nunca mezcles! Jamás uses = y <= dentro del mismo bloque always. Es sintácticamente válido, pero el comportamiento es tan confuso que se considera un error grave de diseño.