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:
always @(*)- re-roda sempre que qualquer sinal lido no bloco muda. Constrói lógica combinacional.always @(posedge clk)- re-roda só na borda de subida declk. 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 dealwaysprecisa serreg. 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
outa partir deseldiretamente - 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 declk. Nada acontece entre bordas.- Non-blocking assignment
<=. Dentro de um bloco clocked, este é o operador certo. Ele diz "agendecountpara 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
defaultnecessário. Oifcobre 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:
| Bloco | Hardware |
|---|---|
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 serreg. Compilador faz cumprir. - Atribuir ao mesmo
rega partir de dois blocosalwaysdiferentes: 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.