Menu

If-Else em Verilog: Lógica condicional em blocos procedurais

Como if/else funciona dentro de um bloco always, a armadilha do latch que pega iniciantes e o hardware de priority-encoder que cadeias de else if produzem.

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

Sintaxe familiar, modelo mental diferente

if/else parece exatamente com C:

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

Mas as regras são diferentes porque Verilog não é software. Duas coisas para lembrar:

  1. if/else só vive dentro de um bloco procedural. Você não consegue escrever um if solto no nível do module.
  2. O que o if/else sintetiza depende do tipo do bloco. Em um bloco combinacional, vira um multiplexador ou priority encoder. Em um bloco clocked, vira um flip-flop com lógica de atualização condicional.

Dentro de um bloco combinacional

O bloco combinacional always @(*) re-roda sempre que a, b ou c muda. A cadeia if/else escolhe um ramo, atribui max e o bloco termina. Como max é sempre atribuído (cada caminho tem uma atribuição), o sintetizador produz lógica combinacional pura - sem latch.

Note que max é declarado reg mesmo sem haver um flip-flop no hardware. Mesma regra de sempre: qualquer coisa atribuída dentro de always precisa ser reg.

A armadilha do latch

Este é o bug mais comum em código combinacional de iniciante:

// ERRADO - infere um latch
always @(*) begin
    if (enable)
        out = data;
    // sem else! quando `enable` e baixo, o que `out` faz?
end

O sintetizador lê "quando enable é baixo, out não é atribuído" e decide que out precisa lembrar seu valor anterior. Lembrar um valor requer uma célula de armazenamento, então a ferramenta insere um latch. Latches em designs síncronos causam problemas de timing, são difíceis de resetar e quase nunca são o que você quis dizer.

Duas formas de corrigir:

Ambos produzem o mesmo hardware combinacional - um mux 2-para-1. O padrão "default no topo" escala melhor quando você tem muitas atribuições condicionais ao mesmo sinal.

Dentro de um bloco clocked

Note o que é diferente do caso combinacional:

  • O bloco é always @(posedge clk) - território de flip-flop.
  • A atribuição usa <= (non-blocking).
  • Não há else para o caso "nem reset nem enable". Tudo bem. Em um bloco clocked, quando nenhum ramo dispara, o flip-flop simplesmente mantém seu valor anterior - o que é exatamente o que um flip-flop fisicamente faz. Nenhum latch é inferido porque o sinal já é um register.

Esse é o único lugar onde omitir um else é seguro. Fora de blocos clocked, sempre lide com cada caminho.

else if encadeado: Um priority encoder

Uma cadeia de declarações else if tem prioridade implícita - condições anteriores superam as posteriores:

requests[0] é a prioridade mais alta - se está setado, o grant é 0 independente do que os bits de número maior fazem. O sintetizador transforma a cadeia em um mux em cascata: verifica o bit 0 primeiro, depois o bit 1, depois o bit 2, depois o bit 3. Cada nível adiciona um pouco de delay.

Se as condições são mutuamente exclusivas - digamos, decodificar uma entrada one-hot - uma declaração case (próximo doc) produz hardware mais achatado e rápido do que uma cadeia else if. Use a forma case quando não há um requisito genuíno de prioridade.

if sem else em código clocked

Um bloco clocked não precisa de else porque "manter valor anterior" é o padrão. É assim que você constrói enables:

always @(posedge clk) begin
    if (load) target <= incoming;
    // sem else: quando load e baixo, target mantem seu valor
end

Esse é um register com load enable. A maioria dos registers de pipeline, contadores e registers de configuração usam esse padrão.

begin/end e declarações únicas

Como em C, você pode omitir begin/end para uma única declaração:

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

Para qualquer coisa além de uma declaração, use o bloco:

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

Os dois padrões se misturam livremente. Guias de estilo geralmente recomendam sempre usar begin/end para tornar adicionar uma segunda declaração indolor.

O que vem a seguir

O próximo doc - Case Statement - cobre case, que é a ferramenta certa para decodificação multi-way (máquinas de estado, dispatch de opcode, tabelas ROM). Depois disso, loops for, que são sutilmente diferentes de seus primos de software porque são desenrolados em tempo de elaboração.

Perguntas frequentes

Como uma declaração if funciona em Verilog?

if (cond) statement; roda statement quando cond é não-zero. Você pode envolver múltiplas declarações em begin ... end. Adicione else statement; para o ramo alternativo, ou encadeie com else if (other_cond) .... if/else só existe dentro de blocos procedurais - initial ou always - não no topo de um module.

O que é um inferred latch em Verilog?

Um latch que a ferramenta de síntese criou sem você pedir, porque seu bloco always combinacional não atribuiu um sinal em todos os caminhos. A ferramenta vê 'se a então out = 1' sem else, decide que o caso não-atribuído precisa lembrar o valor anterior e produz um latch. Latches quase sempre estão errados; a correção é dar a cada sinal um valor padrão no topo do bloco ou um else explícito.

Como evito latches inferidos em Verilog?

Em um bloco always @(*) combinacional, certifique-se de que cada reg de saída seja atribuído em cada caminho de código. O padrão mais limpo é definir defaults no topo do bloco e depois sobrescrever condicionalmente. O compilador geralmente avisa quando infere um latch - trate o aviso como um erro.

O que sintetiza de uma cadeia if-else em Verilog?

Um priority encoder. O primeiro if tem prioridade mais alta, o próximo else if só é verificado se o primeiro for falso, e assim por diante. Em hardware isso vira uma cadeia de muxes com a ordem de prioridade embutida. Se as condições são mutuamente exclusivas, uma declaração case com a mesma lógica frequentemente sintetiza para hardware mais achatado e lê com mais clareza.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR