22/05/2005
En el universo del diseño de hardware digital con Verilog, existen constructos que son la piedra angular sobre la que se edifican circuitos complejos. Uno de los más importantes y versátiles es, sin duda, el bloque always. A diferencia de las asignaciones continuas (assign), que modelan lógica puramente combinacional de forma concurrente, el bloque always nos permite describir comportamientos que se ejecutan secuencialmente en respuesta a eventos específicos. Es la herramienta fundamental para modelar tanto lógica secuencial, como flip-flops y contadores, como lógica combinacional compleja de una manera estructurada.

Comprender a fondo su funcionamiento, sus matices y sus reglas de uso no es solo una recomendación, es un requisito indispensable para cualquier ingeniero o desarrollador que busque crear diseños de hardware funcionales, eficientes y sintetizables. En este artículo, desglosaremos cada aspecto del bloque always, desde su sintaxis básica hasta las sutiles pero cruciales diferencias entre tipos de asignación.
¿Qué es Exactamente un Bloque `always`?
Un bloque always es un tipo de bloque procedimental en Verilog. Esto significa que las sentencias que contiene se ejecutan en el orden en que están escritas, una tras otra, a diferencia del comportamiento paralelo inherente del hardware. Sin embargo, el bloque en sí mismo se ejecuta de forma concurrente con otros bloques always y sentencias assign en el módulo.
La característica que define a un bloque always es que se activa o "dispara" en respuesta a un cambio en una o más señales. Esta condición de disparo se especifica en lo que se conoce como la lista de sensibilidad.
Sintaxis Básica
La estructura general de un bloque always es la siguiente:
always @(<lista_de_sensibilidad>) begin // Sentencias procedimentales que se ejecutan secuencialmente // ... endEl símbolo @ indica que el bloque espera un evento. Lo que sigue entre paréntesis () es la lista de sensibilidad, que dicta cuándo debe ejecutarse el código interno. Las palabras clave begin y end se utilizan para agrupar múltiples sentencias, de manera similar a las llaves {} en otros lenguajes de programación.
La Lista de Sensibilidad: El Gatillo del Comportamiento
La lista de sensibilidad es el corazón del control de un bloque always. Define con precisión los eventos que provocarán la ejecución del bloque. Un error común de los principiantes es omitir señales de esta lista, lo que puede llevar a comportamientos de simulación incorrectos y, peor aún, a la síntesis de hardware no deseado, como latches imprevistos.
Tipos de Disparadores
- Sensibilidad de Nivel: El bloque se ejecuta cada vez que el valor de cualquiera de las señales en la lista cambia. Se utiliza principalmente para modelar lógica combinacional. Las señales se separan por la palabra clave
or(en estándares más antiguos) o por comas.
// El bloque se ejecuta si 'a' cambia, o si 'b' cambia, o si 'c' cambia. always @(a, b, c) begin salida = (a & b) | c; endPara simplificar la descripción de la lógica combinacional, el estándar Verilog-2001 introdujo un atajo muy útil: @(*). Este comodín le dice al sintetizador que incluya automáticamente todas las señales leídas dentro del bloque en la lista de sensibilidad. Es la forma recomendada y más segura de modelar lógica combinacional hoy en día.

// Forma moderna y recomendada para lógica combinacional always @(*) begin salida = (a & b) | c; end- Sensibilidad de Flanco (Edge-Triggered): El bloque se ejecuta solo en un momento específico del cambio de una señal, ya sea su flanco de subida (de 0 a 1) o su flanco de bajada (de 1 a 0). Esto es fundamental para modelar lógica secuencial síncrona, como los registros y flip-flops que dependen de una señal de reloj (clock). Se utilizan las palabras clave
posedge(flanco de subida) ynegedge(flanco de bajada).
// El bloque se ejecuta solo en cada flanco de subida de la señal 'clk'. always @(posedge clk) begin q <= d; // Típico de un Flip-Flop D end- Múltiples Disparadores: Es común tener bloques que reaccionan a más de un evento, como un reloj y una señal de reset asíncrona. En estos casos, ambos eventos se incluyen en la lista de sensibilidad.
// El bloque se ejecuta en el flanco de subida de 'clk' O en el flanco de bajada de 'rst_n'. always @(posedge clk or negedge rst_n) begin if (!rst_n) begin // Reset asíncrono activo en bajo q <= 1'b0; end else begin q <= d; end endAsignación Bloqueante (=) vs. No Bloqueante (<=): La Distinción Clave
Dentro de un bloque always, la forma en que asignamos valores a las variables (del tipo reg) es de vital importancia y tiene un impacto directo en el hardware sintetizado. Verilog ofrece dos operadores de asignación procedimental, y usarlos incorrectamente es una de las fuentes de errores más comunes.
Tabla Comparativa de Asignaciones
| Característica | Asignación Bloqueante (=) | Asignación No Bloqueante (<=) |
|---|---|---|
| Símbolo | = | <= |
| Ejecución | Inmediata. La sentencia se completa y la variable se actualiza antes de que la siguiente sentencia comience su ejecución. Bloquea la ejecución de las siguientes líneas. | Programada. Todas las partes derechas de las asignaciones se evalúan primero. Luego, las asignaciones se "programan" para ocurrir al final del paso de tiempo actual de la simulación. No bloquea. |
| Comportamiento | Similar a la asignación en lenguajes de programación secuenciales como C. | Emula el comportamiento paralelo del hardware, donde todos los flip-flops de un circuito síncrono capturan sus nuevos valores simultáneamente en el flanco del reloj. |
| Uso Recomendado | Para modelar lógica combinacional dentro de un bloque always. | Para modelar lógica secuencial (elementos de memoria como flip-flops y registros). |
El Peligro de Usarlas Incorrectamente: Un Ejemplo Práctico
Imaginemos que queremos crear un simple registro de desplazamiento de dos bits, donde la salida del primer flip-flop (q1) alimenta la entrada del segundo (q2).
Versión Incorrecta con Asignación Bloqueante:
// ¡NO HACER ESTO PARA LÓGICA SECUENCIAL! always @(posedge clk) begin q1 = entrada_datos; // Se actualiza q1 inmediatamente q2 = q1; // q2 recibe el NUEVO valor de q1, no el antiguo endEn este caso, en un mismo ciclo de reloj, el valor de entrada_datos se propaga a través de q1 y llega hasta q2. El sintetizador no creará dos flip-flops en cascada, sino que probablemente optimizará q1 y conectará entrada_datos directamente a q2, eliminando un registro. El comportamiento no es el de un registro de desplazamiento.
Versión Correcta con Asignación No Bloqueante:
// FORMA CORRECTA PARA LÓGICA SECUENCIAL always @(posedge clk) begin q1 <= entrada_datos; // Se programa la actualización de q1 q2 <= q1; // Se programa la actualización de q2 usando el valor ANTIGUO de q1 endAquí, en el flanco de reloj, el simulador primero evalúa ambas asignaciones. Lee el valor de entrada_datos y el valor *antiguo* de q1. Luego, programa ambas actualizaciones para que ocurran simultáneamente. Esto modela perfectamente cómo dos flip-flops separados capturarían sus nuevos valores al mismo tiempo, resultando en un registro de desplazamiento funcional.
Regla de Oro:
- Usa asignación no bloqueante (
<=) para describir lógica secuencial. - Usa asignación bloqueante (
=) para describir lógica combinacional dentro de un bloquealways.
Modelando Lógica Combinacional con `always`
Aunque la sentencia assign es la forma más directa de describir lógica combinacional simple, el bloque always es preferible cuando la lógica es más compleja y requiere estructuras de control como if-else o case.
Ejemplo: Un Multiplexor 4 a 1
Un multiplexor (MUX) selecciona una de varias entradas basándose en una señal de selección (sel). Describirlo con un bloque always y una sentencia case es muy legible e intuitivo.

module mux4a1 ( output reg [7:0] salida, input [7:0] a, b, c, d, input [1:0] sel ); always @(*) begin case (sel) 2'b00: salida = a; 2'b01: salida = b; 2'b10: salida = c; 2'b11: salida = d; default: salida = 8'hXX; // Asignar un valor desconocido para casos no definidos endcase end endmoduleEn este ejemplo, usamos always @(*) para asegurar que el bloque se reevalúe si cambia cualquier entrada (a, b, c, d, o sel). La sentencia case implementa la lógica de selección de forma clara. La asignación bloqueante (=) se utiliza porque estamos describiendo una relación combinacional directa entre las entradas y la salida.
Preguntas Frecuentes (FAQ)
¿Qué pasa si mi simulación se queda "colgada" o en un bucle infinito?
Esto suele ocurrir si tienes un bloque always sin lista de sensibilidad y sin ninguna forma de control de tiempo (como un retardo #10). El bloque se ejecutará en un bucle infinito en el tiempo de simulación 0. Asegúrate de que todos tus bloques always sintetizables tengan una lista de sensibilidad adecuada.
¿Por qué mi código sintetizó un "latch" que no quería?
Un latch se infiere cuando, en un bloque always combinacional, no se especifica un valor para una salida en todas las posibles rutas de ejecución. Por ejemplo, en un if sin un else, o en un case sin una rama default. El sintetizador asume que, si no se cumple la condición, la salida debe mantener su valor anterior, creando así un elemento de memoria (un latch). Para evitarlo, asegúrate de que todas tus salidas tengan un valor asignado en todas las ramas de tu lógica combinacional.
¿Puedo mezclar asignaciones bloqueantes y no bloqueantes en el mismo bloque `always`?
Técnicamente es posible, pero es una muy mala práctica de codificación. Conduce a un código confuso, difícil de depurar y puede causar discrepancias entre la simulación y el hardware sintetizado. Sigue la regla de oro: un bloque para lógica secuencial (y solo <=), otro bloque para lógica combinacional (y solo =).
Conclusión
El bloque always es mucho más que una simple estructura de código; es la principal herramienta del diseñador para infundir comportamiento y temporalidad en el hardware descrito con Verilog. Dominar su uso implica entender a la perfección la función de la lista de sensibilidad para definir los disparadores y, de manera crítica, aplicar la disciplina de usar la asignación no bloqueante (<=) para la lógica secuencial y la asignación bloqueante (=) para la lógica combinacional. Al internalizar estos conceptos, estarás bien encaminado para diseñar circuitos digitales robustos, predecibles y correctos desde su concepción.
Si quieres conocer otros artículos parecidos a Bloque `always` en Verilog: Guía Completa puedes visitar la categoría Juegos.
