Aggiornato il 30/07/2007
Dovendo assegnare
qualcosa ad una variabile si usa di regola il segno di uguale, il più naturale
per tutti. Negli HDL, non solo in Verilog, le
assegnazioni hanno invece due simboli diversi, per forma e significato.
Le assegnazioni blocking, fatte appunto con il segno = oppure l’operatore di assegnazione nonblocking che è indicato come <=.
I nomi, non
proprio auto esplicativi, derivano dal fatto che con queste assegnazione il
sintetizzatore può seguire due strade assai diverse.
Il blocking prende il nome, per inverso dall’altro caso, poiché blocca il
flusso del sintetizzatore ad ogni dichiarazione; in pratica il software di
sintesi si ferma fino a che tutta
l’espressione non è stata messa a posto. Il lato sinistro, chiamato LHS left hand side, della
dichiarazione è cioè assegnato al momento stesso della lettura.
Tutte le
assegnazioni continue del tipo assign
out = !sel ? in1 : in2; sono sempre e solo eseguite con questo operatore.
Il nonblocking prende il nome dal fatto
che l’espressione che la usa viene esaminata in due passi dal sintetizzatore.
Questi valuta la
parte destra della frase, quella a destra del simbolo detta RHS e la pone nello
stack.
In seguito, alla
fine del blocco(*) di dichiarazioni in genere segnato da un always @(…edge), tutti i destination,
la parte sinistra dell’espressione vengono assegnati.
In pratica il
flusso di sintesi non è “bloccato” dalla dichiarazione ma il software va avanti
in attesa della fine del blocco dichiarativo.
Si noti che una
dichiarazione nonblocking non può ovviamente essere
usata in una assegnazione continua pena un errore del software né, di regola,
al di fuori di un always su clock.
(*) In realtà
nessuno ha mai saputo se il termine usato si riferisse realmente al verbo
bloccare o al trattamento del blocco dei dati. Block
in Inglese, ha lo stesso nome, come in Italiano per indicare la stessa cosa: o
un verbo o un sostantivo. Può però essere utile per ricordarsi che il simbolo
del nonblocking, <=, prende campo proprio solo
alla fine del blocco.
Iniziamo con un
esempio semplice ma chiaro. Supponiamo di aver bisogno di eseguire lo swap di
un bus a 16 bit che cattura i dati sul fronte di un clock che pilota un flipflop e lo inverte sul fronte successivo del ff stesso. Si tratta di esempi volutamente essenziali per
far capire. Nulla di più semplice quindi e basta scrivere:
Figura 1
Peccato che il
circuito non funzionerà come è possibile vedere dalla simulazione:
Figura 2
Osservando l’RTL
generato si capisce bene il perché:
Figura 3
Il motivo è
semplice, essendo la dichiarazione = di tipo blocking il sintetizzatore attua come prima cosa la
prima dichiarazione, perdendo parte del contenuto del registro. La seconda
frase è quindi inutile, nel senso che non ha più il contenuto precedente. Il
sintetizzatore se ne accorge e ottimizza il circuito, errato, togliendo la
parte inutile.
Ripetiamo la
stessa cosa ora con del codice nonblocking.
Figura 4
Il risultato
questa volta è corretto e i byte vengono scambiati regolarmente tra LSB e MSB
della word.
Figura 5
Corretto quindi
anche il circuito RTL derivato ( Figura 6 ) con un mux che scambia, quando occorre i dati. La
differenza è evidente ed è dovuta al fatto che il sintetizzatore si è accorto che
una parte dei dati era modificata nel corso del blocco di dichiarazioni ed ha
provveduto con un circuito corretto che ne salva il contenuto.
Figura 6
Occorre però
stare molto attenti nell’uso di queste dichiarazioni tanto che molti testi riportano
l’avvertenza di usare le dichiarazioni blocking
solo nelle assegnazioni continue lasciando invece sempre e solo le nonblocking
nei circuiti sequenziali.
Un esempio di stranezze.
Qui vi è un
circuito che possiamo assimilare ad uno shift register a 2 bit oppure ad uno stadio risincronizzatore,
come preferite. Scritto come visibile in Figura 7 tutto è corretto e funziona come si vede
dalla simulazione e dal relativo circuito sintetizzato.
Figura 7
Figura 8
Questa è la
simulazione, corretta, del circuito.
Figura 9
Anche con una
notazione blocking potremmo ottenere lo stesso
risultato come visibile qui.
Figura 11
Attenzione però!
In questo caso basta invertire l’ordine delle righe di codice e il circuito si
trasforma in quello visibile a fianco del codice di Figura 12: errato. L’unica modifica, come si vede sono le due assegnazioni invertite cosa che invece potevamo fare con
l’assegnazione non blocking senza problema.
Figura 12
Ecco cosa si ottiene simulandolo, nulla
che vedere con il voluto shift o sincronizzatore…
Figura 13
NOTA BENE
Se
osserviamo solo l’uscita RTL del sintetizzatore troveremo che nel caso del
codice di Figura 12 i flipflop saranno
lo stesso due,
ma non è vero. L’ottimizzatore nella passata successiva toglierà
l’evidentemente inutile latch lasciano al suo posto quanto visto nella figura
stessa.
Figura 14
Ciò
è dovuto come avrete già capito al fatto che il sintetizzatore nel primo caso
analizza correttamente il sorgente ed assegna in via definitiva le variabili
senza perdere informazione. Nel secondo caso trovandosi già b assegnato non farà altro che duplicare
l’uscita b, già valutata non scordiamolo, con il
segnale di nome c.
Paolo Lavacchini