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:
- O sintetizador nem sempre prova que os casos são exaustivos.
- O seletor pode ser
xouzem simulação, o que não casa com nenhum caso explícito. - 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.