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:
- El sintetizador no siempre prueba que los cases son exhaustivos.
- El selector podría ser
xozen simulación, lo que no coincide con ningún case explícito. - 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:
casees 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 ifes 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.