Menu

Sentencia case en Verilog: decodificación multivía bien hecha

Cómo funciona case para una decodificación multivía limpia, el default que nunca deberías saltarte, y las diferencias entre case, casex y casez.

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

La bifurcación multivía

case es la construcción de despacho plano de Verilog. Le das una expresión; elige la rama que coincide:

case (expression)
    pattern_1: statement_1;
    pattern_2: statement_2;
    pattern_3: begin
        statement_3a;
        statement_3b;
    end
    default: default_statement;
endcase

Es estructuralmente similar al switch de C, pero:

  • No hay break - cada rama está terminada implícitamente por la siguiente.
  • Los patrones pueden ser vectores, no solo enteros.
  • El sintetizador convierte el conjunto en un mux plano (o un decodificador one-hot cuando los patrones lo permiten).

Un ejemplo trabajado: mux 4 a 1

El cuerpo del case tiene cuatro patrones explícitos más un default. El sintetizador ve una entrada de 2 bits mapeada a uno de cuatro valores y emite un multiplexor 4 a 1. Limpio, plano, rápido.

Por qué necesitas default

Para un case combinacional, omitir default es la misma trampa que if sin else: cualquier valor de entrada no emparejado deja a out sin asignar, y el sintetizador infiere un latch.

Para un sel de 2 bits, los patrones de arriba cubren los cuatro valores posibles - así que en teoría default es redundante. En la práctica:

  1. El sintetizador no siempre prueba que los cases son exhaustivos.
  2. El selector podría ser x o z en simulación, lo que no coincide con ningún case explícito.
  3. Añadir un nuevo case más adelante podría dejar el comportamiento por defecto sin especificar.

Escribe siempre default. Para máquinas de estados y lógica de mux donde sabes que el default es inalcanzable, asignar 'x:

default: out = 8'bx;

…le dice al sintetizador "esto es don't-care, optimiza libremente" y muestra un x rojo brillante en simulación si el case inalcanzable se alcanza de alguna manera. Eso es lo mejor de los dos mundos.

Una máquina de estados en case

El uso clásico de case es la lógica de transición de estados de una máquina de estados finitos:

El bloque case (state) es la lógica de transición de estados. Cada rama decide cuál es el siguiente estado y cuánto quedarse en él. default es inalcanzable aquí (cubrimos RED/GREEN/YELLOW exhaustivamente en el espacio de 2 bits), pero está ahí como red de seguridad - si state de alguna manera se convierte en 2'd3, la FSM se resetea limpiamente a RED en vez de quedarse enganchada en un latch.

Finite State Machines profundiza en este patrón.

Varios patrones por rama

Puedes listar varios patrones que compartan una única sentencia, separados por comas:

case (opcode)
    4'h0, 4'h1, 4'h2: result = a + b;
    4'h3, 4'h4:       result = a - b;
    4'h8:             result = a & b;
    default:          result = 8'd0;
endcase

Eso es dos opcodes que significan "restar", tres que significan "sumar". El sintetizador hace OR de los patrones para el comparador.

casez y casex: emparejamiento con don't-care

A veces quieres emparejar un patrón con algunos bits sin especificar - "cualquier opcode que empiece por 010":

casez (opcode)
    8'b010?_????: instruction = ALU_OP;
    8'b110?_????: instruction = LOAD_OP;
    8'b1110_????: instruction = JUMP_OP;
    default:      instruction = UNKNOWN;
endcasez

casez trata ? (y z) en el patrón como don't-cares. Cada ? coincide con 0 o 1. Útil para decodificar formatos de instrucciones donde algunas posiciones de bit no se usan para ciertas clases de opcode.

casex extiende esto para tratar x también como don't-care. casex es peligroso porque las señales no inicializadas (que son x en simulación) coinciden con cada case, produciendo comportamiento sorprendente. La mayoría de las guías de estilo modernas recomiendan casez y prohíben casex.

En SystemVerilog también tienes case inside, que es la versión más limpia de todas - acepta rangos y listas - pero Verilog clásico se queda en casez.

case vs cadena if/else if

Las dos construcciones pueden expresar decisiones multivía, pero sintetizan a hardware distinto:

  • case es un despacho plano. El sintetizador puede producir un decodificador one-hot, un árbol de mux equilibrado u otras estructuras planas. Tiempo constante para evaluar.
  • if/else if es una cadena de prioridad. El sintetizador produce una cascada donde cada nivel añade retardo. Logarítmicamente más lento.

Funcionalmente se solapan. Estilísticamente: usa case cuando las condiciones son sobre el valor de una única expresión. Usa if/else if cuando hay una prioridad real o cuando las condiciones involucran señales distintas.

// Better as case:
if      (sel == 2'd0) out = a;
else if (sel == 2'd1) out = b;
else if (sel == 2'd2) out = c;
else                  out = d;

// Better as if/else if:
if      (urgent_event)  next_state = HANDLE_URGENT;
else if (timer_expired) next_state = TIMEOUT;
else if (data_ready)    next_state = PROCESS;
else                    next_state = state;

Las tres primeras condiciones son todas "¿qué es sel?" - un case se lee de forma más natural y sintetiza más plano. Las tres segundas son eventos independientes con una prioridad obvia - if/else if encaja mejor.

Qué viene a continuación

El último doc de este capítulo - For Loops - cubre el for de Verilog y lo sorprendente que ocurre cuando lo usas en código sintetizable. Luego entramos en lógica secuencial y FSMs en serio.

Preguntas frecuentes

¿Qué es la sentencia case en Verilog?

case (expr) ... endcase es la construcción de bifurcación multivía de Verilog. Evalúa la expresión una vez y despacha a la rama que coincida. Es la elección idiomática para máquinas de estados, decodificadores de opcodes, selectores de mux y cualquier otra cosa que elija entre varias opciones mutuamente excluyentes.

¿Cuál es la diferencia entre case, casex y casez en Verilog?

case empareja bit por bit exacto, incluidos los valores x y z. casez trata z (y ?) en los items de case como don't-cares. casex trata tanto x como z como don't-cares. La coincidencia don't-care es útil para patrones de opcode donde algunas posiciones de bit son irrelevantes, pero casex es peligroso en simulación porque las señales no inicializadas (x) pueden coincidir accidentalmente con cada case.

¿Por qué necesito default en una sentencia case de Verilog?

Sin default, la herramienta de síntesis ve la posibilidad de que ningún case haya coincidido, decide que la señal de salida tiene que mantener su valor anterior, e infiere un latch no deseado. La rama default maneja cualquier valor no emparejado - típicamente poniendo la salida a un valor seguro o marcando el case como inalcanzable con una asignación x. Inclúyelo siempre.

¿Cuándo debería usar case vs if-else en Verilog?

Usa case cuando las condiciones son mutuamente excluyentes y se despachan sobre el valor de una única expresión - máquinas de estados, decodificadores de opcode, selectores de mux. Usa if/else cuando hay un verdadero orden de prioridad o las condiciones involucran señales distintas. case sintetiza a hardware más plano y rápido que una cadena else if larga.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR