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.