Menu

Clocked Logic em Verilog: Flip-Flops, Registers e Pipelines

Como construir registers, contadores, shift registers e pipelines a partir de blocos always clocked - o padrão burro de carga de cada design digital síncrono.

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

O bloco de construção: Um flip-flop D

Tudo em design síncrono volta para uma pequena peça de hardware: o flip-flop D. Ele tem uma entrada de clock, uma entrada de dados e uma saída de dados. Em cada borda de subida do clock, captura o valor de D e o mantém até a próxima borda. Só isso.

Em Verilog:

always @(posedge clk) begin
    q <= d;
end

Três linhas, um flip-flop. O <= é non-blocking, que é exatamente o comportamento de flip-flops reais (coberto em Blocking vs Non-blocking). A sensitivity posedge clk é o que o torna sequencial.

Empilhe muitos desses com lógica combinacional entre eles e você tem qualquer circuito síncrono que quisesse construir.

Um register com reset

Designs reais sempre precisam de um reset - uma forma de colocar o sistema em um estado conhecido no power-on ou sob demanda:

Esse é um reset síncrono - a condição de reset é avaliada em uma borda de clock como qualquer outra entrada. O sintetizador produz um flip-flop com um mux 2-para-1 em sua entrada de dados: quando reset é alto, o mux alimenta zero; caso contrário alimenta d.

Para a maioria dos designs, reset síncrono é a escolha certa. É timing mais simples, funciona bem com FPGAs e o sinal de reset pode vir de qualquer lugar - não precisa de sua própria fonte alinhada ao clock.

Um register com enable

Frequentemente você quer um register que só atualiza quando algo diz a ele. Use um if dentro do bloco clocked e confie no comportamento implícito de "manter valor anterior" do else ausente:

Esse é o canônico "register com load-enable". Aparece em todo lugar - registers de configuração, estágios de pipeline que só avançam quando o downstream está pronto, contadores que pausam e retomam. A omissão do else final é deliberada e segura dentro de um bloco clocked: um flip-flop já lembra seu valor anterior, então "não fazer nada" significa "manter".

Em um bloco combinacional o mesmo código inferiria um latch. Regras diferentes, mesma sintaxe.

Um contador

Um contador é um register cujo próximo valor é seu valor atual mais um:

O contador incrementa em cada ciclo de clock quando enable é alto. Depois de 16 ciclos volta para 0 (porque declaramos 4 bits e 15 + 1 dá overflow para 0). Esse wrap é intrínseco à aritmética de N bits e é exatamente como um contador de hardware real se comporta.

Um shift register

Empilhar flip-flops te dá um shift register. O truque é fazer todos os shifts em uma declaração non-blocking:

O corpo é out <= {out[WIDTH-2:0], in} - concatena os bits mais baixos do out atual com o novo in, e atribui o todo. Como é non-blocking, o RHS lê o out antigo antes do LHS atualizar. O efeito é um shift limpo de N bits em um único ciclo de clock.

Esse é o padrão de shift register em sua forma menor. Generaliza para LFSRs, transmissores seriais, deserializers - qualquer design onde dados se movem por uma cadeia de flip-flops a cada clock.

Pipelines

Um pipeline é uma cadeia de registers separados por lógica combinacional. Cada estágio processa dados do estágio anterior e alimenta o próximo:

Três estágios, três ciclos de latência, mas um novo resultado a cada ciclo uma vez que o pipeline esteja cheio. A latência é 3 clocks porque os dados passam por três flip-flops; o throughput é 1 operação por clock porque os três estágios trabalham simultaneamente em entradas diferentes.

É assim que designs de alta performance atingem suas metas de throughput: mantenha estágios curtos, faça pipeline mais profundo e deixe o paralelismo fazer o trabalho.

Reset assíncrono (quando você precisa)

Às vezes você não pode esperar uma borda de clock para asserir reset - o chip está sendo desligado, o clock está gated, um watchdog externo está puxando a linha. Nesses casos:

always @(posedge clk or negedge reset_n) begin
    if (~reset_n) q <= 0;
    else          q <= d;
end

A sensitivity list agora tem uma borda de clock e uma borda de reset. O flip-flop responde imediatamente a qualquer um. reset_n é por convenção active-low (asserido quando baixo), que é o porquê do teste ser ~reset_n.

Reset assíncrono tem trade-offs: mais difícil de analisar timing, pode causar metaestabilidade se não for tratado cuidadosamente na des-asserção, não é portável entre todas as arquiteturas de FPGA. Use reset síncrono por padrão, assíncrono só quando o design exigir.

O que vem a seguir

Você agora consegue construir qualquer datapath síncrono. O próximo doc - Finite State Machines - junta um register clocked com uma declaração case para produzir o idioma padrão de FSM: o burro de carga de cada controlador, engine de protocolo e bloco de tomada de decisão em design digital.

Perguntas frequentes

O que é clocked logic em Verilog?

Lógica cujas atualizações são controladas por um sinal de clock. O idioma padrão é always @(posedge clk) target <= next_value; - em cada borda de subida de clk, target captura next_value. Essa única linha descreve um flip-flop em hardware. Empilhar muitos deles com lógica combinacional entre eles constrói contadores, shift registers, pipelines - tudo síncrono.

Qual a diferença entre reset síncrono e assíncrono em Verilog?

Reset síncrono usa always @(posedge clk) if (reset) ... - o reset é amostrado em uma borda de clock como qualquer outra entrada. Reset assíncrono usa always @(posedge clk or negedge reset_n) if (~reset_n) ... - o bloco dispara em uma borda de clock ou em uma asserção de reset, então o reset faz efeito imediatamente. Síncrono é a escolha padrão; assíncrono é usado quando o reset precisa ser garantido a fazer efeito mesmo se o clock estiver morto.

Como construo um pipeline em Verilog?

Empilhe múltiplos estágios de always @(posedge clk) stageN_reg <= stageN_combinational; - a lógica combinacional de cada estágio alimenta o register do próximo estágio, e todo register captura na mesma borda de clock. O resultado é um pipeline onde novos dados entram a cada ciclo e emergem N ciclos depois, com throughput de um resultado por clock.

O que é um shift register em Verilog?

Uma cadeia de flip-flops onde a saída de cada um alimenta a entrada do próximo. Cada borda de clock desloca cada bit uma posição. A versão canônica em Verilog usa non-blocking assignments: out <= {out[N-2:0], in}; - essa única linha cria um shift register de N bits que pega um bit por ciclo de in.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR