Menu

Always Block em Verilog: Lógica combinacional e sequencial

Como blocos always funcionam, a diferença entre combinacional always @(*) e clocked always @(posedge clk) e as regras que decidem qual hardware cada um produz.

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

O burro de carga do Verilog comportamental

assign descreve lógica combinacional de equação única. Assim que você precisa de if/else, case ou memória, recorre a always. Um bloco always é uma peça de código procedural que re-roda sempre que sinais específicos mudam. Os sinais que disparam a re-execução são a sensitivity list.

Há duas formas de always que você verá com mais frequência:

  1. always @(*) - re-roda sempre que qualquer sinal lido no bloco muda. Constrói lógica combinacional.
  2. always @(posedge clk) - re-roda só na borda de subida de clk. Constrói lógica sequencial clocked (flip-flops).

Outras formas existem (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)) mas as duas acima representam quase todo bloco sintetizável que você vai escrever.

Combinacional always @(*)

Três coisas para notar:

  • out é reg. Qualquer coisa atribuída dentro de always precisa ser reg. A palavra-chave não significa "isso é um flip-flop"; aqui significa apenas "sou escrito a partir de um bloco procedural".
  • always @(*). O * diz "acorde sempre que qualquer coisa que eu leio mudar". O simulador automaticamente descobre a sensitivity list. Você pode escrever a lista manualmente - always @(sel) - mas @(*) é mais seguro porque omitir um sinal é uma fonte clássica de bugs.
  • Sem clock. Este bloco descreve lógica combinacional. O sintetizador produz uma peça de lógica que calcula out a partir de sel diretamente - sem flip-flops, sem necessidade de pino de clock.

O caso default não é opcional no espírito mesmo sendo opcional na sintaxe. Omita-o e qualquer valor de entrada não especificado deixa out mantendo seu valor anterior - o que sintetiza para um latch não intencional. Sempre inclua o default.

Sequencial always @(posedge clk)

As principais diferenças em relação à versão combinacional:

  • always @(posedge clk). O bloco re-roda só na borda de subida de clk. Nada acontece entre bordas.
  • Non-blocking assignment <=. Dentro de um bloco clocked, este é o operador certo. Ele diz "agende count para assumir seu novo valor no final do time step", que é exatamente como um flip-flop se comporta. O porquê e a alternativa estão em Blocking vs Non-blocking.
  • Sem default necessário. O if cobre ambos os ramos (reset e não-reset). Sem risco de latch.

O sintetizador vê essa forma - sensitivity clocked, non-blocking assignment - e produz um register de 4 bits (quatro flip-flops) mais a lógica combinacional que calcula count + 1 e o mux que escolhe entre reset e incremento.

A distinção de síntese

O mesmo fonte de module pode descrever duas peças completamente diferentes de hardware dependendo da forma do bloco always:

BlocoHardware
always @(*) y = expr;Lógica combinacional pura. Sem memória.
always @(posedge clk) y <= expr;Flip-flop. Captura expr uma vez por ciclo de clock.
always @(*) if (en) y = expr;Latch - geralmente um bug. O caso "else" mantém o valor antigo.
always @(posedge clk) if (en) y <= expr;Flip-flop com enable. Captura só quando en é alto.

O terceiro caso é a armadilha do latch. Um latch é uma célula de memória transparente que mantém sua saída quando a entrada não é asserida - útil em designs específicos, quase sempre um bug quando acidental. A maioria das ferramentas de síntese avisa alto quando infere um latch que você não pediu. Trate o aviso como um erro.

Variantes de sensitivity list

Você verá algumas sensitivity lists menos comuns:

  • always @(a or b or c) - lista explícita. Verilog-2001 adicionou o separador ,: always @(a, b, c). Qualquer um funciona.
  • always @(posedge clk or negedge reset_n) - reset assíncrono. O bloco roda em uma borda de subida de clock ou uma borda de descida de reset. Usado quando reset precisa fazer efeito imediatamente, sem esperar o próximo clock.
  • always @(negedge clk) - clocking na borda de descida. Raro; alguns designs o usam para flip-flops "negative-edge-triggered" que capturam na borda de descida em vez da subida.

Para designs novos, prefira always @(*) para combinacional e always @(posedge clk) para sequencial. Recorra a reset assíncrono só quando o design genuinamente precisar.

Dois blocos são duas peças de hardware

Múltiplos blocos always no mesmo module são independentes - cada um se torna sua própria peça de hardware:

O bloco clocked produz um register flip-flop. O bloco combinacional produz uma porta XOR. Vivem lado a lado; nenhum sabe do outro. As duas saídas mudam em cronogramas completamente diferentes.

O que blocos always não podem fazer

Algumas coisas que parecem tentadoras mas não são permitidas:

  • Atribuir a um wire: o alvo precisa ser reg. Compilador faz cumprir.
  • Atribuir ao mesmo reg a partir de dois blocos always diferentes: produz comportamento indefinido em simulação e não vai sintetizar. Um driver por sinal.
  • Ler e escrever o mesmo sinal no mesmo bloco combinacional de uma forma que cria um loop de feedback: always @(*) x = x + 1; é um loop de delay zero que o simulador não consegue resolver.

Os dois primeiros o compilador pega. O terceiro às vezes só aparece em tempo de simulação como um hang.

O que vem a seguir

O próximo doc - Initial Block - cobre o irmão do always: um bloco que roda exatamente uma vez no início da simulação. É o burro de carga dos testbenches. Depois disso, as regras para blocking vs non-blocking assignment que decidem se seu bloco clocked faz o que você quis dizer.

Perguntas frequentes

O que é um always block em Verilog?

always introduz um bloco procedural que re-roda sempre que os sinais em sua sensitivity list mudam. Há dois sabores: always @(*) constrói lógica combinacional (re-roda sempre que qualquer entrada muda), e always @(posedge clk) constrói lógica sequencial (re-roda em cada borda de subida de clk). O corpo de um bloco always pode conter if, case, for e atribuições procedurais.

Qual a diferença entre always @(*) e always @(posedge clk)?

always @(*) é sensível a qualquer sinal lido no bloco; produz lógica combinacional sem memória. always @(posedge clk) é sensível apenas à borda de subida de clk; produz flip-flops que capturam estado uma vez por ciclo de clock. O primeiro não tem clock nem register; o segundo tem ambos.

O que é uma sensitivity list em Verilog?

A lista de sinais depois de @ que determina quando um bloco always re-roda. @(*) é abreviação para 'cada sinal lido no bloco'. @(posedge clk) roda só na borda de subida de clk. @(posedge clk or negedge reset_n) roda em qualquer um dos eventos - usado para resets assíncronos. Errar a sensitivity list é uma das fontes mais comuns de mismatch entre simulação e síntese.

Posso atribuir a um wire dentro de um always block?

Não. Blocos always só podem atribuir a reg (ou logic em SystemVerilog). O compilador faz cumprir isso. Se você quer que um wire seja a saída de lógica procedural, declare um reg intermediário, conduza-o dentro de always e dê assign no wire a partir do reg por fora - ou apenas mude o wire para reg.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR