Guía completa del uso de la sentencia always en Verilog y SystemVerilog

目次

1. Introducción

¿Cuál es el papel de la sentencia always en Verilog?

En el lenguaje de descripción de hardware “Verilog HDL”, ampliamente utilizado en el diseño de circuitos digitales, la sentencia always cumple un rol fundamental. A diferencia del software, Verilog no describe cómo se ejecutan instrucciones de forma secuencial, sino que define bajo qué condiciones cambian las señales y cómo se comporta el circuito. Dentro de este esquema, la sentencia always es la construcción básica para describir acciones específicas que ocurren cuando se presentan ciertas condiciones.

¿Por qué es necesaria la sentencia always?

En Verilog existen principalmente dos formas de describir el comportamiento de un circuito:

  • Circuitos combinacionales: la salida cambia inmediatamente cuando cambia la entrada.
  • Circuitos secuenciales: la salida cambia sincronizada con una señal de reloj u otra referencia temporal.

Con solo la sentencia assign no es posible manejar condiciones complejas o almacenamiento de estados. Aquí es donde entra en juego always.

Por ejemplo, para implementar lógica con múltiples condiciones o describir registros tipo flip-flop, es necesario usar always junto con estructuras de control como if o case.

Patrones comunes de la sentencia always

Existen varias formas habituales de utilizar always, según el tipo de circuito que se desee implementar:

  • always @(*)
    → Usado para circuitos combinacionales
  • always @(posedge clk)
    → Usado para circuitos secuenciales sincronizados al flanco ascendente del reloj
  • always @(posedge clk or negedge rst)
    → Usado para circuitos secuenciales con reset asíncrono u otras condiciones de control

Comprender la sentencia always, una de las piezas centrales de Verilog, es un paso esencial para cualquier diseñador de hardware.

Objetivo de este artículo

En este artículo se explicará la sentencia always en Verilog, desde la sintaxis básica hasta usos avanzados, errores comunes a evitar y extensiones en SystemVerilog.

  • Aprender la forma correcta de escribir always
  • Identificar causas comunes de errores en síntesis lógica
  • Entender la diferencia entre = y <=
  • Evitar los errores típicos de principiantes

Este contenido está diseñado para ser una guía práctica y clara para quienes tengan dudas o dificultades con always.

2. Sintaxis básica y tipos de la sentencia always

Sintaxis básica de always

La sentencia always en Verilog se utiliza para ejecutar procesos repetidamente en función de una lista de sensibilidad (sensitivity list). La sintaxis general es:

always @(lista_de_sensibilidad)
begin
  // instrucciones
end

La parte clave es la “lista de sensibilidad”, que define qué señales deben cambiar para activar la ejecución del bloque.

Uso de always @(*) (circuitos combinacionales)

En circuitos combinacionales, la salida debe actualizarse inmediatamente ante cualquier cambio en las entradas. Para este caso se usa @(*) en la lista de sensibilidad:

always @(*) begin
  if (a == 1'b1)
    y = b;
  else
    y = c;
end

Con esta sintaxis, cada vez que cambie a, b o c, el bloque se ejecutará y actualizará y.

Ventajas de @(*)

  • Incluye automáticamente todas las señales usadas en el bloque.
  • Evita inconsistencias entre simulación y síntesis por omitir señales en la lista de sensibilidad.

Uso de always @(posedge clk) (circuitos secuenciales)

En circuitos secuenciales, el comportamiento depende de los flancos del reloj. Se utiliza posedge clk (flanco ascendente) en la lista de sensibilidad:

always @(posedge clk) begin
  q <= d;
end

Esto asegura que en cada flanco ascendente de clk, el valor de d se almacene en q. Aquí se emplea <= (asignación no bloqueante), típico en circuitos secuenciales.

posedge y negedge

  • posedge: ejecuta en el flanco ascendente
  • negedge: ejecuta en el flanco descendente

La elección depende del tipo de sincronización que se desee implementar.

Uso de always @(posedge clk or negedge rst) (con reset asíncrono)

En circuitos más complejos suele ser necesario un mecanismo de reset. Una descripción típica con reset asíncrono es:

always @(posedge clk or negedge rst) begin
  if (!rst)
    q <= 1'b0;
  else
    q <= d;
end

De esta forma, cuando la señal de rst se encuentra en “0”, q se reinicia inmediatamente. En caso contrario, q sigue el valor de d en sincronía con el reloj.

Diferencias entre circuitos combinacionales y secuenciales

Tipo de circuitoSentencia always usadaComportamiento
Combinacionalalways @(*)La salida responde inmediatamente a las entradas
Secuencialalways @(posedge clk)La salida cambia sincronizada con el reloj

3. Tipos de asignación dentro de always

Dos formas de asignación en Verilog

Dentro de un bloque always se utilizan dos operadores de asignación distintos:

  • =: asignación bloqueante (blocking assignment)
  • <=: asignación no bloqueante (non-blocking assignment)

Confundir o mezclar estas formas sin entender su diferencia puede causar comportamientos inesperados y discrepancias entre simulación y síntesis.

Asignación bloqueante (=)

La asignación bloqueante ejecuta cada instrucción en orden, similar al software tradicional. Un ejemplo:

always @(*) begin
  a = b;
  c = a;
end

Aquí, primero se asigna b a a, y luego a a c. El orden de las sentencias afecta directamente el resultado.

Usos comunes

  • Dentro de lógica combinacional con if o case
  • Procesos sin almacenamiento de estado

Asignación no bloqueante (<=)

La asignación no bloqueante evalúa todas las expresiones en paralelo y actualiza los valores al mismo tiempo tras el flanco de reloj. Ejemplo:

always @(posedge clk) begin
  a <= b;
  c <= a;
end

Aquí, c recibe el valor previo de a, no el actualizado en el mismo ciclo. Esto refleja el comportamiento real de registros en hardware.

Usos comunes

  • Circuitos secuenciales (flip-flops, registros)
  • Procesos donde se requiere coherencia entre múltiples señales

Comparación entre bloqueante y no bloqueante

AspectoBloqueante (=)No bloqueante (<=)
Orden de ejecuciónSecuencial, línea por líneaParalelo, actualiza en conjunto
Uso principalLógica combinacionalLógica secuencial
Aplicación del resultadoInmediataDespués del flanco de reloj
Errores comunesGeneración accidental de latchesSeñales que no se actualizan correctamente

¿Qué ocurre si se mezclan?

Combinar = y <= en el mismo bloque o sobre la misma señal debe evitarse. Por ejemplo:

always @(posedge clk) begin
  a = b;
  a <= c;
end

Este código genera ambigüedad: no queda claro qué valor se conservará finalmente en a, lo que causa inconsistencias entre simulación y hardware.

Reglas prácticas de uso

  • En lógica combinacional usar = (bloqueante).
  • En lógica secuencial usar <= (no bloqueante).

Seguir esta simple regla evita gran parte de los errores típicos al escribir código en Verilog.

4. Precauciones y errores comunes al usar always

Errores en la lista de sensibilidad

Si no se incluyen todas las señales, aparecen fallos

En Verilog, la lista de sensibilidad (@(...)) debe indicar claramente qué señales disparan la ejecución del bloque. Si se omite alguna, pueden producirse errores. Ejemplo:

always @(a) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

En este caso, los cambios en b no se detectan, por lo que y no se actualiza correctamente.

Solución: usar @(*)

Para evitar omisiones, se recomienda usar @(*), que incluye automáticamente todas las señales del bloque:

always @(*) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

Esto mejora la seguridad y mantenibilidad del código.

Generación accidental de latches

Faltan asignaciones en todas las condiciones

Si en una estructura if o case no se asigna un valor en todas las condiciones, el sintetizador puede generar un latch para “recordar” el valor previo:

always @(*) begin
  if (enable)
    y = d; // si enable=0, y no recibe un valor nuevo
end

Aunque parezca correcto, en realidad se crea un latch no deseado.

Solución: cubrir todos los casos

always @(*) begin
  if (enable)
    y = d;
  else
    y = 1'b0; // siempre se asigna un valor
end

De esta forma, nunca se generan latches accidentales.

Condiciones demasiado complejas

Si se usan if o case con muchas ramas sin cubrir todos los casos, puede haber comportamientos indefinidos.

Ejemplo: falta default en un case

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    // falta el caso 2'b11
  endcase
end

Si sel = 2'b11, el valor de y queda indefinido.

Solución: añadir default

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    default: y = 1'b0; // valor por defecto
  endcase
end

Así siempre habrá una salida definida, evitando errores de síntesis.

Controlar múltiples señales en un mismo bloque

Cuando se manejan varias señales dentro de un solo always, pueden surgir dependencias no deseadas o errores de asignación. En diseños complejos es recomendable separar en varios bloques always para mayor claridad.

Resumen de errores comunes

ProblemaCausaSolución
La salida no cambiaFaltan señales en la lista de sensibilidadUsar @(*)
Se generan latchesNo se asignan valores en todos los casosUsar else o default
Comportamiento indefinidoCondiciones incompletas en caseAñadir cláusula default
Dependencias inesperadasDemasiadas señales en un bloqueDividir en varios bloques always

5. Extensiones de la sentencia always en SystemVerilog

always_comb: exclusivo para lógica combinacional

Descripción

always_comb funciona de manera similar a always @(*), pero indica explícitamente que el bloque describe lógica combinacional.

always_comb begin
  y = a & b;
end

Ventajas principales

  • Genera automáticamente la lista de sensibilidad
  • Advierte si se producen latches accidentales
  • Evita interferencias con variables definidas previamente

always_ff: exclusivo para lógica secuencial (flip-flops)

Descripción

always_ff se utiliza para describir circuitos secuenciales controlados por reloj. Requiere condiciones de disparo explícitas como posedge clk o negedge rst:

always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    q <= 1'b0;
  else
    q <= d;
end

Ventajas principales

  • Solo permite asignaciones no bloqueantes (<=), evitando errores
  • El compilador verifica la lista de sensibilidad
  • Hace evidente que se trata de un bloque secuencial

always_latch: exclusivo para latches

Descripción

always_latch se emplea solo cuando se necesita describir un latch de manera intencionada:

always_latch begin
  if (enable)
    q = d;
end

Consideraciones

  • Si no todas las condiciones asignan un valor, se genera un latch
  • Debe usarse solo cuando sea estrictamente necesario

Comparación de las variantes en SystemVerilog

SentenciaUsoEquivalente en VerilogCaracterísticas
always_combLógica combinacionalalways @(*)Lista de sensibilidad automática, detección de latches
always_ffLógica secuencial (flip-flops)always @(posedge clk)Controlado por reloj, evita errores de asignación
always_latchLatchesalways @(*) (con condiciones incompletas)Permite describir latches intencionales

Tendencia actual en el diseño

En proyectos modernos, se recomienda el uso de las variantes de SystemVerilog por su mayor claridad y seguridad. Gracias al soporte de herramientas de verificación, el uso de always_ff y always_comb ayuda a evitar errores como “funciona en simulación pero no en hardware”.

En entornos colaborativos o proyectos grandes, estas variantes mejoran la legibilidad y la revisión del código, acelerando el mantenimiento y reduciendo riesgos.

6. FAQ: Preguntas frecuentes sobre la sentencia always

En esta sección se responden dudas comunes relacionadas con el uso de always en Verilog y SystemVerilog, desde principiantes hasta diseñadores intermedios.

Q1. ¿Debo usar if o case dentro de always?

A. Depende de la complejidad de las condiciones:

  • Para 2 o 3 condiciones simples, if es más claro
  • Si hay múltiples estados o ramas claramente diferenciadas, case es más legible

Además, case obliga a cubrir todos los casos, lo que reduce errores.

Q2. ¿Qué pasa si omito la lista de sensibilidad?

A. Si faltan señales, el bloque puede no ejecutarse en todos los cambios relevantes, provocando que las salidas no se actualicen. Esto genera diferencias entre la simulación y el hardware real.
La solución es usar @(*) o always_comb.

Q3. ¿Por qué se generan latches sin querer?

A. Cuando no se asignan valores en todas las condiciones de un if o case, el sintetizador asume que la señal debe “recordar” su valor, creando un latch automáticamente.

Ejemplo (incorrecto):

always @(*) begin
  if (en)
    y = d; // si en=0, y no se actualiza
end

Solución:

always @(*) begin
  if (en)
    y = d;
  else
    y = 1'b0;
end

Q4. ¿Puedo mezclar = y <= en el mismo bloque?

A. No es recomendable. Cada tipo de bloque tiene su regla:

  • Lógica combinacional → =
  • Lógica secuencial → <=

Mezclarlos en la misma señal puede causar resultados diferentes entre simulación y hardware real.

Q5. ¿Qué diferencia hay entre always_ff y always @(posedge clk)?

A. Aunque se comportan de forma similar, always_ff añade seguridad:

Criterioalways @(posedge clk)always_ff
Lista de sensibilidadDebe definirse manualmenteEl compilador la verifica automáticamente
Errores de asignaciónPuede compilar incluso con erroresDetecta y bloquea asignaciones inválidas
LegibilidadNo siempre refleja la intenciónExplicita que es un bloque secuencial

Q6. ¿Es seguro controlar múltiples señales en un mismo always?

A. Sí, pero en diseños complejos puede dificultar el mantenimiento. Se recomienda dividir en varios bloques si las señales tienen comportamientos independientes.

Q7. ¿Qué ocurre si uso <= en lógica combinacional?

A. Puede funcionar en simulación, pero durante la síntesis se corre el riesgo de generar un hardware no esperado. Para combinacional se debe usar =, y reservar <= para secuencial.

7. Conclusión

La sentencia always como base del diseño en Verilog

En el diseño digital con Verilog, la sentencia always es una de las herramientas más importantes, pues permite describir tanto lógica combinacional como secuencial. Dominar su uso amplía las posibilidades de diseño y permite controlar con precisión el flujo y la temporización de las señales. Es un conocimiento esencial tanto para principiantes como para profesionales.

Puntos clave del artículo

  • Diferencias y usos de always @(*) y always @(posedge clk)
  • Diferencia entre = (bloqueante) y <= (no bloqueante), y sus contextos adecuados
  • Buenas prácticas para listas de sensibilidad y cómo evitar latches accidentales
  • Ventajas de always_comb, always_ff y always_latch en SystemVerilog
  • Respuestas a preguntas frecuentes en la práctica de diseño

La precisión en la escritura define la calidad

El diseño en HDL se traduce directamente en hardware físico. Un error pequeño en el código puede provocar fallos en el circuito sintetizado. Con always, es crucial ser riguroso en la sintaxis, en la elección del tipo de asignación y en la cobertura de todas las condiciones.

Próximos pasos: hacia diseños más avanzados

Una vez dominado always, es posible avanzar hacia:

  • Diseño de máquinas de estados finitos (FSM)
  • Implementación de estructuras en pipeline o procesamiento en paralelo
  • Creación de IP cores e implementación en FPGA

Además, ampliar conocimientos hacia otros lenguajes como SystemVerilog o VHDL permite trabajar en entornos más amplios y profesionales.

Reflexión final

El diseño digital no consiste solo en “hacer que funcione”, sino en garantizar que funcione correctamente, de manera robusta y escalable. Dominar la sentencia always no solo es aprender sintaxis, sino adquirir una mentalidad de precisión y fiabilidad como diseñador.

Si este artículo te ayudó a comprender mejor always, habrás dado un paso sólido hacia un diseño de hardware más seguro y profesional.