Menu

If-else en Verilog: lógica condicional en bloques procedurales

Cómo funciona if/else dentro de un bloque always, la trampa del latch que atrapa a los principiantes y el hardware de codificador de prioridad que producen los else if encadenados.

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

Sintaxis familiar, modelo mental distinto

if/else se ve exactamente como C:

if (condition) begin
    // ... statements ...
end else begin
    // ... statements ...
end

Pero las reglas son distintas porque Verilog no es software. Dos cosas que tener en cuenta:

  1. if/else solo vive dentro de un bloque procedural. No puedes escribir un if independiente al nivel de módulo.
  2. A qué sintetiza el if/else depende del tipo de bloque. En un bloque combinacional se convierte en un multiplexor o codificador de prioridad. En un bloque sincronizado se convierte en un flip-flop con lógica de actualización condicional.

Dentro de un bloque combinacional

El bloque combinacional always @(*) vuelve a ejecutarse cada vez que a, b o c cambian. La cadena if/else elige una rama, asigna max y el bloque termina. Como max se asigna siempre (cada camino tiene una asignación), el sintetizador produce lógica combinacional pura - sin latch.

Ten en cuenta que max se declara reg aunque no hay flip-flop en el hardware. La misma regla de siempre: cualquier cosa asignada dentro de always debe ser reg.

La trampa del latch

Este es el bug más común en código combinacional de principiante:

// WRONG - infers a latch
always @(*) begin
    if (enable)
        out = data;
    // no else! when `enable` is low, what does `out` do?
end

El sintetizador lee "cuando enable está en bajo, out no se asigna" y decide que out tiene que recordar su valor anterior. Recordar un valor requiere una celda de almacenamiento, así que la herramienta inserta un latch. Los latches en diseños síncronos causan problemas de timing, son difíciles de resetear y casi nunca son lo que querías.

Dos formas de arreglarlo:

Ambos producen el mismo hardware combinacional - un mux 2 a 1. El patrón "por defecto en la parte de arriba" escala mejor cuando tienes muchas asignaciones condicionales a la misma señal.

Dentro de un bloque sincronizado

Fíjate en lo que cambia respecto al caso combinacional:

  • El bloque es always @(posedge clk) - territorio de flip-flop.
  • La asignación usa <= (no bloqueante).
  • No hay else para el caso "ni reset ni enable". Eso está bien. En un bloque sincronizado, cuando ninguna rama dispara, el flip-flop simplemente mantiene su valor anterior - que es exactamente lo que hace un flip-flop físicamente. No se infiere ningún latch porque la señal ya es un registro.

Este es el único lugar donde omitir un else es seguro. Fuera de bloques sincronizados, maneja siempre cada camino.

Cadena else if: un codificador de prioridad

Una cadena de sentencias else if tiene prioridad implícita - las condiciones tempranas superan a las posteriores:

requests[0] es la prioridad más alta - si está activo, el grant es 0 sin importar lo que hagan los bits de número superior. El sintetizador convierte la cadena en un mux en cascada: comprueba el bit 0 primero, luego el bit 1, luego el bit 2, luego el bit 3. Cada nivel añade una pequeña cantidad de retardo.

Si las condiciones son mutuamente excluyentes - digamos, decodificar una entrada one-hot - una sentencia case (siguiente doc) produce hardware más plano y rápido que una cadena else if. Usa la forma case cuando no hay un requisito genuino de prioridad.

if sin else en código sincronizado

Un bloque sincronizado no necesita un else porque "mantener el valor anterior" es el por defecto. Así es como construyes enables:

always @(posedge clk) begin
    if (load) target <= incoming;
    // no else: when load is low, target keeps its value
end

Eso es un registro con load enable. La mayoría de los registros de pipeline, contadores y registros de configuración usan este patrón.

begin/end y sentencias únicas

Como C, puedes omitir begin/end para una única sentencia:

if (a) out = 1;
else   out = 0;

Para cualquier cosa que pase de una sentencia, usa el bloque:

if (a) begin
    out = 1;
    flag = 1;
end else begin
    out = 0;
    flag = 0;
end

Los dos patrones se mezclan libremente. Las guías de estilo generalmente recomiendan usar siempre begin/end para hacer que añadir una segunda sentencia sea trivial.

Qué viene a continuación

El siguiente doc - Case Statement - cubre case, que es la herramienta correcta para decodificación multivía (máquinas de estados, dispatch de opcodes, tablas ROM). Después de eso, los loops for, que son sutilmente distintos de sus primos del software porque se desenrollan en tiempo de elaboración.

Preguntas frecuentes

¿Cómo funciona una sentencia if en Verilog?

if (cond) statement; ejecuta statement cuando cond es distinto de cero. Puedes envolver varias sentencias en begin ... end. Añade else statement; para la rama alternativa, o encadena con else if (other_cond) .... if/else solo existe dentro de bloques procedurales - initial o always - no en el nivel superior de un módulo.

¿Qué es un latch inferido en Verilog?

Un latch que la herramienta de síntesis creó sin que se lo pidieras, porque tu bloque always combinacional no asignó una señal en cada camino. La herramienta ve 'si a entonces out = 1' sin else, decide que el caso no asignado tiene que recordar el valor anterior, y produce un latch. Los latches casi siempre son incorrectos; la solución es dar a cada señal un valor por defecto en la parte superior del bloque o un else explícito.

¿Cómo se evitan los latches inferidos en Verilog?

En un bloque always @(*) combinacional, asegúrate de que cada reg de salida se asigna en cada camino de código. El patrón más limpio es poner valores por defecto en la parte superior del bloque y luego sobrescribir condicionalmente. El compilador normalmente avisa cuando infiere un latch - trata el aviso como un error.

¿Qué sintetiza una cadena if-else en Verilog?

Un codificador de prioridad. El primer if tiene la prioridad más alta, el siguiente else if se comprueba solo si el primero es falso, y así sucesivamente. En hardware eso se convierte en una cadena de muxes con el orden de prioridad incorporado. Si las condiciones son mutuamente excluyentes, una sentencia case con la misma lógica a menudo sintetiza a hardware más plano y se lee con más claridad.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR