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.