Menu

Logica sincrona in Verilog: flip-flop, registri e pipeline

Come costruire registri, contatori, shift register e pipeline con blocchi always sincronizzati dal clock: il pattern portante di ogni progetto digitale sincrono.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Il mattone di base: il flip-flop D

Tutto il progetto sincrono si riduce a un minuscolo pezzo di hardware: il flip-flop D. Ha un ingresso di clock, un ingresso dati e un'uscita dati. A ogni fronte di salita del clock cattura il valore di D e lo mantiene fino al fronte successivo. Tutto qui.

In Verilog:

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

Tre righe, un flip-flop. <= è l'assegnazione non-blocking, che corrisponde esattamente al comportamento dei flip-flop reali (vedi Blocking vs Non-blocking). È la sensibilità a posedge clk a renderlo sequenziale.

Combinane tanti con logica combinatoria in mezzo e puoi costruire qualsiasi circuito sincrono.

Un registro con reset

I progetti reali hanno sempre bisogno di un reset, cioè di un modo per portare il sistema in uno stato noto all'accensione o su richiesta:

Questo è un reset sincrono: la condizione di reset viene valutata sul fronte di clock come qualsiasi altro ingresso. Il sintetizzatore produce un flip-flop con un mux 2 a 1 sull'ingresso dati: quando reset è alto il mux passa zero, altrimenti passa d.

Per la maggior parte dei progetti il reset sincrono è la scelta giusta. Ha una temporizzazione più semplice, si comporta bene sulle FPGA e il segnale di reset può arrivare da qualunque punto, senza bisogno di una sorgente allineata al clock.

Un registro con enable

Spesso vuoi un registro che si aggiorni solo quando qualcosa glielo dice. Usa un if dentro il blocco sincronizzato e affidati al comportamento implicito "mantieni il valore precedente" dovuto all'assenza di else:

È il classico "registro con abilitazione al caricamento". Lo trovi ovunque: registri di configurazione, stadi di pipeline che avanzano solo quando lo stadio a valle è pronto, contatori che si mettono in pausa e ripartono. L'assenza dell'else finale è voluta e sicura dentro un blocco sincronizzato: un flip-flop ricorda già il valore precedente, quindi "non fare nulla" significa "mantieni".

In un blocco combinatorio lo stesso codice dedurrebbe un latch. Regole diverse, stessa sintassi.

Un contatore

Un contatore è un registro il cui valore successivo è quello attuale più uno:

Il contatore si incrementa a ogni ciclo di clock quando enable è alto. Dopo 16 cicli torna a 0 (perché abbiamo dichiarato 4 bit e 15 + 1 va in overflow a 0). Questo ritorno a zero è intrinseco all'aritmetica a N bit ed è esattamente il comportamento di un contatore hardware reale.

Uno shift register

Mettendo in fila dei flip-flop ottieni uno shift register. Il trucco è fare tutti gli spostamenti in un'unica istruzione non-blocking:

Il corpo è out <= {out[WIDTH-2:0], in}: concatena i bit bassi dell'out attuale con il nuovo in e assegna il tutto. Essendo un'assegnazione non-blocking, il lato destro legge il vecchio out prima che il lato sinistro si aggiorni. L'effetto è uno spostamento pulito di N bit in un solo ciclo di clock.

Questo è il pattern dello shift register nella sua forma minima. Si generalizza a LFSR, trasmettitori seriali, deserializzatori: qualsiasi progetto in cui i dati attraversano una catena di flip-flop a ogni clock.

Pipeline

Una pipeline è una catena di registri separati da logica combinatoria. Ogni stadio elabora i dati dello stadio precedente e alimenta quello successivo:

Tre stadi, tre cicli di latenza, ma un nuovo risultato a ogni ciclo quando la pipeline è piena. La latenza è di 3 clock perché i dati attraversano tre flip-flop; il throughput è di 1 operazione per clock perché tutti e tre gli stadi lavorano contemporaneamente su ingressi diversi.

È così che i progetti ad alte prestazioni raggiungono i loro obiettivi di throughput: stadi brevi, pipeline più profonde e il parallelismo che fa il lavoro.

Reset asincrono (quando serve)

A volte non puoi aspettare un fronte di clock per attivare il reset: il chip si sta spegnendo, il clock è disabilitato, un watchdog esterno sta forzando la linea. In questi casi:

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

Ora la lista di sensibilità contiene sia un fronte di clock sia un fronte di reset. Il flip-flop risponde subito a entrambi. Per convenzione reset_n è attivo basso (attivo quando vale zero), ed è per questo che il test è ~reset_n.

Il reset asincrono ha dei compromessi: è più difficile da analizzare nei tempi, può causare metastabilità se il rilascio non è gestito con cura e non è portabile su tutte le architetture FPGA. Usa il reset sincrono come impostazione predefinita e quello asincrono solo quando il progetto lo richiede.

Cosa viene dopo

Ora puoi costruire qualsiasi datapath sincrono. La prossima pagina, Finite State Machines, unisce un registro sincronizzato a un'istruzione case per ottenere l'idioma standard delle FSM: il cavallo da tiro di ogni controller, motore di protocollo e blocco decisionale nel progetto digitale.

Domande frequenti

Cos'è la logica sincrona in Verilog?

È logica i cui aggiornamenti sono scanditi da un segnale di clock. L'idioma standard è always @(posedge clk) target <= next_value;: a ogni fronte di salita di clk, target cattura next_value. Quella sola riga descrive un flip-flop in hardware. Combinandone tanti con logica combinatoria in mezzo si costruiscono contatori, shift register, pipeline: tutto ciò che è sincrono.

Qual è la differenza tra reset sincrono e asincrono in Verilog?

Il reset sincrono usa always @(posedge clk) if (reset) ...: il reset viene campionato sul fronte di clock come qualsiasi altro ingresso. Il reset asincrono usa always @(posedge clk or negedge reset_n) if (~reset_n) ...: il blocco scatta su un fronte di clock oppure all'attivazione del reset, quindi il reset ha effetto immediato. Il reset sincrono è la scelta predefinita; quello asincrono si usa quando il reset deve funzionare anche se il clock è fermo.

Come si costruisce una pipeline in Verilog?

Metti in sequenza più stadi del tipo always @(posedge clk) stageN_reg <= stageN_combinational;: la logica combinatoria di ogni stadio alimenta il registro dello stadio successivo e tutti i registri catturano sullo stesso fronte di clock. Il risultato è una pipeline in cui a ogni ciclo entrano nuovi dati che escono N cicli dopo, con un throughput di un risultato per ciclo di clock.

Cos'è uno shift register in Verilog?

È una catena di flip-flop in cui l'uscita di ciascuno alimenta l'ingresso del successivo. A ogni fronte di clock ogni bit si sposta di una posizione. La versione canonica in Verilog usa assegnazioni non-blocking: out <= {out[N-2:0], in};. Quella sola riga crea uno shift register a N bit che prende un bit per ciclo da in.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA