Il cavallo da tiro del Verilog comportamentale
assign descrive logica combinatoria fatta di una sola equazione. Appena ti servono if/else, case o memoria, passi ad always. Un blocco always è un pezzo di codice procedurale che viene rieseguito ogni volta che cambiano determinati segnali. I segnali che fanno scattare la riesecuzione formano la lista di sensibilità.
Le due forme di always che vedrai più spesso sono:
always @(*): si riesegue quando cambia qualsiasi segnale letto nel blocco. Costruisce logica combinatoria.always @(posedge clk): si riesegue solo sul fronte di salita diclk. Costruisce logica sequenziale sincronizzata dal clock (flip-flop).
Esistono altre forme (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)), ma le due qui sopra coprono quasi tutti i blocchi sintetizzabili che scriverai.
always @(*) combinatorio
Tre cose da notare:
outèreg. Tutto ciò che viene assegnato dentroalwaysdeve esserereg. La parola chiave non significa "questo è un flip-flop"; qui vuol dire solo "vengo scritto da un blocco procedurale".always @(*). L'asterisco*dice "svegliati ogni volta che cambia qualcosa che leggo". Il simulatore ricava da solo la lista di sensibilità. Puoi scriverla a mano, per esempioalways @(sel), ma@(*)è più sicuro perché dimenticare un segnale è una classica fonte di bug.- Nessun clock. Questo blocco descrive logica combinatoria. Il sintetizzatore produce una rete logica che calcola
outdirettamente dasel: niente flip-flop, nessun pin di clock.
Il caso default è facoltativo nella sintassi, ma non nella sostanza. Se lo ometti, qualsiasi valore di ingresso non previsto lascia out fermo al valore precedente, e questo si sintetizza in un latch indesiderato. Includi sempre il default.
always @(posedge clk) sequenziale
Le differenze principali rispetto alla versione combinatoria:
always @(posedge clk). Il blocco si riesegue solo sul fronte di salita diclk. Tra un fronte e l'altro non succede nulla.- Assegnazione non bloccante
<=. Dentro un blocco sincronizzato dal clock, questo è l'operatore giusto. Dice "fai assumere acountil nuovo valore alla fine del passo temporale", che è esattamente il comportamento di un flip-flop. Il perché e l'alternativa sono in Blocking vs Non-blocking. - Nessun
defaultnecessario. L'ifcopre entrambi i rami (reset e non reset). Nessun rischio di latch.
Il sintetizzatore riconosce questa forma (sensibilità al clock, assegnazione non bloccante) e produce un registro a 4 bit (quattro flip-flop), più la logica combinatoria che calcola count + 1 e il mux che sceglie tra reset e incremento.
La distinzione in sintesi
Lo stesso sorgente di un modulo può descrivere due pezzi di hardware completamente diversi a seconda della forma del blocco always:
| Blocco | Hardware |
|---|---|
always @(*) y = expr; | Logica combinatoria pura. Nessuna memoria. |
always @(posedge clk) y <= expr; | Flip-flop. Cattura expr una volta per ciclo di clock. |
always @(*) if (en) y = expr; | Latch: di solito un bug. Il caso "else" conserva il valore vecchio. |
always @(posedge clk) if (en) y <= expr; | Flip-flop con enable. Cattura solo quando en è alto. |
Il terzo caso è la trappola del latch. Un latch è una cella di memoria trasparente che mantiene l'uscita quando l'ingresso non è attivo: utile in progetti specifici, quasi sempre un bug quando nasce per caso. La maggior parte degli strumenti di sintesi avvisa in modo evidente quando deduce un latch che non hai chiesto. Tratta l'avviso come un errore.
Varianti della lista di sensibilità
Ti capiterà di vedere alcune liste di sensibilità meno comuni:
always @(a or b or c): lista esplicita. Verilog-2001 ha aggiunto il separatore,:always @(a, b, c). Funzionano entrambe.always @(posedge clk or negedge reset_n): reset asincrono. Il blocco si esegue su un fronte di salita del clock oppure su un fronte di discesa del reset. Si usa quando il reset deve avere effetto subito, senza aspettare il clock successivo.always @(negedge clk): sincronizzazione sul fronte di discesa. È rara; alcuni progetti la usano per flip-flop "attivati dal fronte negativo", che catturano sul fronte di discesa invece che su quello di salita.
Nei progetti nuovi, preferisci always @(*) per la logica combinatoria e always @(posedge clk) per quella sequenziale. Usa il reset asincrono solo quando il progetto ne ha davvero bisogno.
Due blocchi sono due pezzi di hardware
Più blocchi always nello stesso modulo sono indipendenti: ognuno diventa un pezzo di hardware a sé:
Il blocco sincronizzato produce un registro di flip-flop. Il blocco combinatorio produce una porta XOR. Convivono fianco a fianco e nessuno dei due sa dell'altro. Le due uscite cambiano con tempi completamente diversi.
Cosa non possono fare i blocchi always
Alcune cose sembrano allettanti ma non sono permesse:
- Assegnare un
wire: la destinazione deve esserereg. Il compilatore lo impone. - Assegnare lo stesso
regda due blocchialwaysdiversi: in simulazione il comportamento è indefinito e il codice non si sintetizza. Un solo driver per segnale. - Leggere e scrivere lo stesso segnale nello stesso blocco combinatorio creando un anello di retroazione:
always @(*) x = x + 1;è un ciclo a ritardo zero che il simulatore non riesce a risolvere.
I primi due li intercetta il compilatore. Il terzo a volte si manifesta solo in simulazione, sotto forma di blocco del simulatore.
Cosa viene dopo
La prossima pagina, Initial Block, tratta il fratello di always: un blocco che si esegue esattamente una volta all'avvio della simulazione. È il cavallo da tiro dei testbench. Subito dopo arrivano le regole sulle assegnazioni bloccanti e non bloccanti, che decidono se il tuo blocco sincronizzato fa davvero ciò che intendevi.
Domande frequenti
Cos'è un blocco always in Verilog?
always introduce un blocco procedurale che viene rieseguito ogni volta che cambiano i segnali della sua lista di sensibilità. Esistono due varianti: always @(*) costruisce logica combinatoria (si riesegue quando cambia un qualsiasi ingresso) e always @(posedge clk) costruisce logica sequenziale (si riesegue a ogni fronte di salita di clk). Il corpo di un blocco always può contenere if, case, for e assegnazioni procedurali.
Qual è la differenza tra always @(*) e always @(posedge clk)?
always @(*) è sensibile a qualsiasi segnale letto nel blocco e produce logica combinatoria senza memoria. always @(posedge clk) è sensibile solo al fronte di salita di clk e produce flip-flop che catturano lo stato una volta per ciclo di clock. Il primo non ha né clock né registro; il secondo ha entrambi.
Cos'è una lista di sensibilità in Verilog?
È l'elenco di segnali dopo @ che stabilisce quando un blocco always viene rieseguito. @(*) è una scorciatoia per 'ogni segnale letto nel blocco'. @(posedge clk) si esegue solo sul fronte di salita di clk. @(posedge clk or negedge reset_n) si esegue su uno qualsiasi dei due eventi: si usa per i reset asincroni. Sbagliare la lista di sensibilità è una delle cause più comuni di differenze tra simulazione e sintesi.
Si può assegnare un wire dentro un blocco always?
No. I blocchi always possono assegnare solo a reg (o logic in SystemVerilog) e il compilatore lo impone. Se vuoi che un wire sia l'uscita di logica procedurale, dichiara un reg intermedio, pilotalo dentro always e collega il wire al reg con un assign esterno. Oppure trasforma semplicemente il wire in reg.