Menu

Bloque always en Verilog: lógica combinacional y secuencial

Cómo funcionan los bloques always, la diferencia entre always @(*) combinacional y always @(posedge clk) sincronizado por reloj, y las reglas que deciden qué hardware produce cada uno.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

El caballo de batalla del Verilog conductual

assign describe lógica combinacional de una sola ecuación. En cuanto necesitas if/else, case o memoria, recurres a always. Un bloque always es una pieza de código procedural que se vuelve a ejecutar cada vez que cambian unas señales específicas. Las señales que disparan la re-ejecución son la lista de sensibilidad.

Hay dos formas de always que verás con más frecuencia:

  1. always @(*) - se vuelve a ejecutar cuando cambia cualquier señal leída en el bloque. Construye lógica combinacional.
  2. always @(posedge clk) - se vuelve a ejecutar solo en el flanco de subida de clk. Construye lógica secuencial sincronizada (flip-flops).

Existen otras formas (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)) pero las dos de arriba cubren casi cada bloque sintetizable que vayas a escribir.

always @(*) combinacional

Tres cosas que observar:

  • out es reg. Cualquier cosa asignada dentro de always debe ser reg. La palabra clave no significa "esto es un flip-flop"; aquí solo significa "lo escribo desde un bloque procedural".
  • always @(*). El * dice "despierta cuando cambie cualquier cosa que lea". El simulador deduce automáticamente la lista de sensibilidad. Puedes escribir la lista manualmente - always @(sel) - pero @(*) es más seguro porque olvidar una señal es una fuente clásica de bugs.
  • Sin reloj. Este bloque describe lógica combinacional. El sintetizador produce una pieza de lógica que calcula out a partir de sel directamente - sin flip-flops, sin pin de reloj necesario.

El caso default no es opcional en espíritu aunque lo sea en sintaxis. Omítelo y cualquier valor de entrada no especificado dejará a out manteniendo su valor anterior - lo que sintetiza a un latch no intencionado. Incluye siempre el default.

always @(posedge clk) secuencial

Las diferencias clave respecto a la versión combinacional:

  • always @(posedge clk). El bloque se vuelve a ejecutar solo en el flanco de subida de clk. Nada sucede entre flancos.
  • Asignación no bloqueante <=. Dentro de un bloque sincronizado por reloj, este es el operador correcto. Dice "programa que count tome su nuevo valor al final del paso de tiempo", que es exactamente cómo se comporta un flip-flop. El porqué y la alternativa están en Blocking vs Non-blocking.
  • No se necesita default. El if cubre las dos ramas (reset y no-reset). Sin riesgo de latch.

El sintetizador ve esta forma - sensibilidad sincronizada por reloj, asignación no bloqueante - y produce un registro de 4 bits (cuatro flip-flops) más la lógica combinacional que calcula count + 1 y el mux que elige entre reset e incremento.

La distinción de síntesis

El mismo código de módulo puede describir dos piezas de hardware completamente distintas dependiendo de la forma del bloque always:

BloqueHardware
always @(*) y = expr;Lógica combinacional pura. Sin memoria.
always @(posedge clk) y <= expr;Flip-flop. Captura expr una vez por ciclo de reloj.
always @(*) if (en) y = expr;Latch - normalmente un bug. El caso "else" mantiene el valor antiguo.
always @(posedge clk) if (en) y <= expr;Flip-flop con enable. Captura solo cuando en está en alto.

El tercer caso es la trampa del latch. Un latch es una celda de memoria transparente que mantiene su salida cuando la entrada no está activa - útil en diseños específicos, casi siempre un bug cuando es accidental. La mayoría de las herramientas de síntesis avisan ruidosamente cuando infieren un latch que no pediste. Trata el aviso como un error.

Variantes de la lista de sensibilidad

Verás algunas listas de sensibilidad menos comunes:

  • always @(a or b or c) - lista explícita. Verilog-2001 añadió el separador ,: always @(a, b, c). Cualquiera de los dos funciona.
  • always @(posedge clk or negedge reset_n) - reset asíncrono. El bloque se ejecuta en un flanco de subida del reloj o en un flanco de bajada del reset. Se usa cuando el reset tiene que tener efecto inmediatamente, sin esperar al siguiente reloj.
  • always @(negedge clk) - sincronizado por flanco de bajada. Raro; algunos diseños lo usan para flip-flops "disparados por flanco negativo" que capturan en el flanco de bajada en vez del de subida.

Para diseños nuevos, prefiere always @(*) para combinacional y always @(posedge clk) para secuencial. Recurre al reset asíncrono solo cuando el diseño lo necesite realmente.

Dos bloques son dos piezas de hardware

Varios bloques always en el mismo módulo son independientes - cada uno se convierte en su propia pieza de hardware:

El bloque sincronizado produce un registro flip-flop. El bloque combinacional produce una puerta XOR. Viven uno al lado del otro; ninguno sabe del otro. Las dos salidas cambian en horarios completamente distintos.

Qué always no puede hacer

Algunas cosas que parecen tentadoras pero no están permitidas:

  • Asignar a un wire: el destino debe ser reg. El compilador lo impone.
  • Asignar al mismo reg desde dos bloques always distintos: produce comportamiento indefinido en simulación y no sintetiza. Un único driver por señal.
  • Leer y escribir la misma señal en el mismo bloque combinacional de una manera que crea un bucle de realimentación: always @(*) x = x + 1; es un bucle de retardo cero que el simulador no puede resolver.

Las dos primeras las pilla el compilador. La tercera a veces solo aparece en tiempo de simulación como un cuelgue.

Qué viene a continuación

El siguiente doc - Initial Block - cubre el hermano de always: un bloque que se ejecuta exactamente una vez al inicio de la simulación. Es el caballo de batalla de los testbenches. Después de eso, las reglas de blocking vs non-blocking que deciden si tu bloque sincronizado hace lo que querías.

Preguntas frecuentes

¿Qué es un bloque always en Verilog?

always introduce un bloque procedural que se vuelve a ejecutar cada vez que cambian las señales de su lista de sensibilidad. Hay dos sabores: always @(*) construye lógica combinacional (se vuelve a ejecutar cuando cualquier entrada cambia), y always @(posedge clk) construye lógica secuencial (se vuelve a ejecutar en cada flanco de subida de clk). El cuerpo de un bloque always puede contener if, case, for y asignaciones procedurales.

¿Cuál es la diferencia entre always @(*) y always @(posedge clk)?

always @(*) es sensible a cualquier señal leída en el bloque; produce lógica combinacional sin memoria. always @(posedge clk) es sensible solo al flanco de subida de clk; produce flip-flops que capturan estado una vez por ciclo de reloj. El primero no tiene reloj ni registro; el segundo tiene ambos.

¿Qué es una lista de sensibilidad en Verilog?

La lista de señales que va después de @ que determina cuándo se vuelve a ejecutar un bloque always. @(*) es una forma corta de 'cada señal leída en el bloque'. @(posedge clk) se ejecuta solo en el flanco de subida de clk. @(posedge clk or negedge reset_n) se ejecuta en cualquiera de los dos eventos - se usa para resets asíncronos. Equivocarse en la lista de sensibilidad es una de las fuentes más comunes de discrepancia entre simulación y síntesis.

¿Puedes asignar a un wire dentro de un bloque always?

No. Los bloques always solo pueden asignar a reg (o logic en SystemVerilog). El compilador lo impone. Si quieres que un wire sea la salida de lógica procedural, declara un reg intermedio, excítalo dentro de always y conecta el wire desde el reg mediante assign fuera - o simplemente cambia el wire a reg.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR