Menu

Case Statement em Verilog: Decodificação multi-way do jeito certo

Como case funciona para decodificação multi-way limpa, o default que você nunca deve pular e as diferenças entre case, casex e casez.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O multi-way branch

case é a construção de dispatch achatado do Verilog. Você dá a ele uma expressão; ele escolhe o ramo que casar:

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

É estruturalmente similar ao switch do C, mas:

  • Não há break - cada ramo é implicitamente terminado pelo próximo.
  • Padrões podem ser vetores, não apenas inteiros.
  • O sintetizador transforma o todo em um mux achatado (ou um decodificador one-hot quando os padrões permitem).

Um exemplo trabalhado: Mux 4-para-1

O corpo do case tem quatro padrões explícitos mais um default. O sintetizador vê uma entrada de 2 bits mapeada para um de quatro valores e emite um multiplexador 4-para-1. Limpo, achatado, rápido.

Por que você precisa de default

Para case combinacional, omitir default é a mesma armadilha de if sem else: qualquer valor de entrada não casado deixa out não atribuído, e o sintetizador infere um latch.

Para um sel de 2 bits, os padrões acima cobrem todos os quatro valores possíveis - então em teoria default é redundante. Na prática:

  1. O sintetizador nem sempre prova que os casos são exaustivos.
  2. O seletor pode ser x ou z em simulação, o que não casa com nenhum caso explícito.
  3. Adicionar um novo caso mais tarde pode deixar o comportamento default não especificado.

Sempre escreva default. Para máquinas de estado e lógica de mux onde você sabe que o default é inalcançável, atribuir 'x:

default: out = 8'bx;

…diz ao sintetizador "isso é um don't-care, otimize livremente" e exibe um x vermelho brilhante em simulação se o caso inalcançável for de alguma forma alcançado. Esse é o melhor dos dois mundos.

Uma máquina de estado em case

O uso clássico do case é a lógica de transição de estado de uma máquina de estado finita:

O bloco case (state) é a lógica de transição de estado. Cada ramo decide qual é o próximo estado e quanto tempo ficar nele. default é inalcançável aqui (cobrimos RED/GREEN/YELLOW exaustivamente no espaço de 2 bits), mas está lá como rede de segurança - se state de alguma forma virar 2'd3, a FSM reseta limpinha para RED em vez de ficar travada.

Finite State Machines vai mais a fundo nesse padrão.

Múltiplos padrões por ramo

Você pode listar vários padrões compartilhando uma única declaração, separados por vírgulas:

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

São dois opcodes ambos significando "subtrair", três significando "somar". O sintetizador faz OR dos padrões juntos para o comparador.

casez e casex: Casamento don't-care

Às vezes você quer casar um padrão com alguns bits não especificados - "qualquer opcode que começa com 010":

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

casez trata ? (e z) no padrão como don't-cares. Cada ? casa com 0 ou 1. Útil para decodificar formatos de instrução onde algumas posições de bit não são usadas para certas classes de opcode.

casex estende isso para tratar x como don't-care também. casex é perigoso porque sinais não inicializados (que são x em simulação) casam com todo caso, produzindo comportamento surpreendente. A maioria dos guias de estilo modernos recomenda casez e proíbe casex.

Em SystemVerilog você também ganha case inside, que é a versão mais limpa de todas - aceita ranges e listas - mas Verilog puro para em casez.

case vs cadeia if/else if

As duas construções podem expressar decisões multi-way, mas sintetizam para hardware diferente:

  • case é um dispatch achatado. O sintetizador pode produzir um decodificador one-hot, uma árvore de mux balanceada ou outras estruturas achatadas. Tempo constante para avaliar.
  • if/else if é uma cadeia de prioridade. O sintetizador produz uma cascade onde cada nível adiciona delay. Logaritmicamente mais lento.

Funcionalmente se sobrepõem. Estilisticamente: use case sempre que as condições são sobre o valor de uma única expressão. Use if/else if quando há uma prioridade real ou quando condições envolvem sinais diferentes.

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

// Melhor como 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;

As três primeiras condições são todas "qual é o sel?" - um case lê mais naturalmente e sintetiza mais achatado. As três segundas são eventos independentes com uma prioridade óbvia - if/else if é melhor encaixe.

O que vem a seguir

O último doc deste capítulo - For Loops - cobre o for do Verilog e a coisa surpreendente que acontece quando você o usa em código sintetizável. Depois entramos em lógica sequencial e FSMs para valer.

Perguntas frequentes

O que é a declaração case em Verilog?

case (expr) ... endcase é a construção multi-way branch do Verilog. Ela avalia a expressão uma vez e despacha para o ramo que combinar. É a escolha idiomática para máquinas de estado, decodificadores de opcode, seletores de mux e qualquer outra coisa que escolhe entre várias opções mutuamente exclusivas.

Qual a diferença entre case, casex e casez em Verilog?

case casa bit-exatamente, incluindo valores x e z. casez trata z (e ?) em itens case como don't-cares. casex trata tanto x quanto z como don't-cares. Casamento don't-care é útil para padrões de opcode onde algumas posições de bit são irrelevantes, mas casex é perigoso em simulação porque sinais não inicializados (x) podem acidentalmente casar com cada caso.

Por que preciso de default em uma declaração case Verilog?

Sem default, a ferramenta de síntese vê a possibilidade de nenhum caso casar, decide que o sinal de saída precisa manter seu valor anterior e infere um latch indesejado. O ramo default lida com cada valor não casado - tipicamente definindo a saída para um valor seguro ou marcando o caso como inalcançável com uma atribuição x. Sempre o inclua.

Quando devo usar case vs if-else em Verilog?

Use case quando as condições são mutuamente exclusivas e fazem dispatch no valor de uma única expressão - máquinas de estado, decodificadores de opcode, seletores de mux. Use if/else quando há uma ordem real de prioridade ou as condições envolvem sinais diferentes. case sintetiza para hardware mais achatado e rápido do que uma cadeia longa else if.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR