Menu

Blocking vs Non-Blocking Assignment em Verilog: Quando usar = vs <=

O tópico mais confuso para iniciantes em Verilog. O que = e <= realmente significam dentro de um bloco always, e a regra que evita a maioria das race conditions.

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

Os dois operadores

Dentro de blocos always e initial, Verilog tem dois operadores de atribuição:

  • = é uma atribuição blocking. Atualize o LHS agora, antes de avançar para a próxima declaração.
  • <= é uma atribuição non-blocking. Avalie o RHS agora, agende o LHS para ser atualizado no final do time step atual.

Fora de blocos procedurais (em assign) só existe = - assign a <= b é um erro de sintaxe. Dentro de blocos procedurais, ambos são legais, e escolher o certo é a decisão mais importante para iniciantes em Verilog.

O que blocking faz

Blocking é o que você esperaria de uma linguagem de software: linha por linha, de cima para baixo. A declaração N termina antes da declaração N+1 começar:

Isso é puramente sequencial. Cada declaração vê os efeitos das que estão acima. Combina com a intuição de software.

O que non-blocking faz

Non-blocking tem forma de hardware. Todos os lados direitos avaliam usando os valores do início do time step. Todos os lados esquerdos atualizam no final do time step. A ordem das declarações no fonte não muda as dependências entre sinais:

Esse trecho implementa uma rotação 3-vias de a → b → c → a em um passo. Com blocking, você precisaria de uma variável temporária para evitar sobrescrever um deles. Com non-blocking, a troca acontece atomicamente porque todo RHS lê os valores pré-passo.

É exatamente assim que três flip-flops se comportam em uma borda de clock: todos capturam suas entradas no mesmo instante, independente das dependências entre eles.

A regra que te salva

Use <= em blocos clocked. Use = em blocos combinacionais.

É só isso. Memorize. Codifique sem pensar. A maioria das race conditions, mismatches simulação/síntese e bugs "funciona pra mim mas não na síntese" vêm de violar essa regra.

A regra tem uma razão de hardware: blocos clocked modelam flip-flops que todos amostram suas entradas simultaneamente. Blocos combinacionais modelam lógica que propaga o mais rápido que consegue. A semântica do operador de atribuição combina com esses comportamentos.

Cada <= lê o valor atual do register fonte e agenda o register destino para assumir esse valor no final do time step. O efeito líquido é exatamente o que um shift register de hardware faz: cada flip-flop captura o valor do seu vizinho, todos na mesma borda de clock, sem race.

Agora considere o que teria acontecido com blocking assignment:

// ERRADO - isto NAO e um shift register!
always @(posedge clk) begin
    out[3] = out[2];   // out[3] vira out[2]
    out[2] = out[1];   // out[2] vira out[1], que acabamos de setar acima
    out[1] = out[0];
    out[0] = in;
end

Cada declaração sobrescreve a fonte antes da próxima declaração lê-la. Em um ciclo de clock, in propagaria por todo o caminho até out[3], porque cada linha vê o valor recém-escrito da que está acima. O comportamento em hardware real (que usa semântica non-blocking) seria completamente diferente do que o simulador mostrou.

Blocos combinacionais: Blocking é o certo

Para always @(*), blocking assignment é correto. Não há flip-flops, sem regra de captura simultânea para fazer cumprir, e variáveis intermediárias são úteis:

sum é calculado primeiro, depois result usa o valor recém-calculado. A lógica combinacional achata em uma única peça de hardware: result = ~(a + b). Sem flip-flops aparecem porque não há clock.

Se você usasse <= aqui, o simulador ainda atualizaria sum antes que result fosse avaliado contra ele (porque ambas as atualizações acontecem no final do passo), mas a ordem seria sutilmente diferente e muitas ferramentas de síntese reclamam. Não misture; escolha o operador que combina com o tipo do bloco.

O erro que mais machuca

Aqui está: um bloco clocked com blocking assignment.

// BUG: race condition esperando para acontecer
always @(posedge clk) begin
    a = b;
    b = c;
    c = a;
end

Em simulação, o simulador pode dar primeiro a, depois b, depois c, produzindo um conjunto de valores. O hardware vai produzir outro conjunto, porque flip-flops reais capturam simultaneamente. Os dois divergem silenciosamente e você vai desperdiçar um dia achando o bug. Use <= em blocos clocked.

Por que dois operadores existem afinal

Os designers do Verilog poderiam ter escolhido uma semântica de atribuição e ficado com ela. Não ficaram, porque a linguagem precisa modelar dois comportamentos distintos de hardware:

  • Lógica combinacional: sinais propagam continuamente, dependências importam, "o que esse gate calcula" faz sentido.
  • Lógica sequencial: uma borda de clock acontece, cada flip-flop captura simultaneamente, dependências entre entradas e saídas de flip-flop são desacopladas.

Blocking é para o primeiro. Non-blocking é para o segundo. O operador escolhe a semântica; o simulador faz o resto.

O que vem a seguir

Você agora tem as regras para escrever qualquer bloco procedural corretamente. O próximo capítulo sobe dos blocos individuais para as construções de controle de fluxo que vão dentro deles: if/else, case e loops for. As regras de blocking vs non-blocking ainda aplicam dentro de todos eles.

Perguntas frequentes

Qual a diferença entre blocking e non-blocking assignment em Verilog?

Blocking (=) atualiza o alvo imediatamente, antes da próxima declaração rodar. Modela execução sequencial. Non-blocking (<=) agenda a atualização para o final do time step atual - cada RHS non-blocking é avaliado usando os valores antigos de todos os sinais, depois cada LHS é atualizado em um único passo coordenado. Non-blocking é como flip-flops de fato se comportam.

Quando devo usar = vs <= em Verilog?

Regra: use <= em blocos always @(posedge clk) clocked, e use = em blocos always @(*) combinacionais. Essa única regra evita a classe inteira de race conditions que atribuições misturadas produzem. Dentro de blocos initial de testbench, = é a escolha normal; <= raramente aparece ali.

Por que non-blocking assignment é necessário em Verilog?

Porque flip-flops de hardware todos capturam suas entradas simultaneamente em uma borda de clock. Se você usasse = (blocking) em código clocked, a ordem das declarações no seu arquivo mudaria qual sinal vê o novo valor de qual outro sinal - uma race condition entre simulação e hardware real. <= combina com o comportamento de hardware avaliando todos os RHS primeiro, depois fazendo todas as atualizações.

O que acontece se eu misturar = e <= no mesmo always block do Verilog?

Você obtém uma race condition. A mistura produz hardware cujo comportamento depende de internos do simulador (ordem de scheduling de eventos), e pior, a simulação pode não combinar com o que a síntese produz. A maioria das ferramentas de lint sinaliza isso como erro. A correção é se comprometer com um ou outro baseado no papel do bloco - non-blocking para clocked, blocking para combinacional.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR