This is an old revision of the document!
Esempio di simulazione in VHDL ( strumenti software: GHDL e gtkwave)
L'esempio in questa pagina è inteso come una prima guida introduttiva ed esemplificativa per chi non conosce il VHDL. La discussione è pertanto necessariamente incompleta e, a tratti, approssimativa.
Descrizione di un inverter in VHDL
Una unità logica (un sistema con un certo numero di ingressi e di uscite) in VHDL viene chiamata “entity”. La descrizione di una entity prevede un primo blocco di codice nel quale si dichiara il nome che invididua la “entity” e i collegamenti verso l'esterno della entity. In una secondo blocco di codice si descrive il comportamento della “entity”. Il blocco di codice che descrive il comportamento di una entity si chiama “architecture”.
Per definire una “entity” che possa essere usata per descrivere un inverter dobbiamo semplicemente dire che si tratta di una unità logica che prevede un ingresso e una uscita. Il codice seguente definisce tale unità.
entity inverter is port(a:in bit; y:out bit); end entity inverter;
Come si vede, la dichiarazione di “entity” (che chiameremo da ora in poi entità) è piuttosto semplice.
Nella prima riga si dichiara l'entità di nome “inverter” e si dice che essa è (“is”) uno scatolo (al momento “vuoto”) con un ingresso (“a”) e una uscita (“y”). Il fatto che “a” sia un ingresso è specificato con la parola chiave “in” dopo i due punti; allo stesso modo si specifica che “y” è una uscita usando la parola chiave “out” dopo i due punti. Semplificando molto, le porte (“a” e “y”) sono gli elementi di connessione dell'entità appena definita con altre entità per il tramite di oggetti che in VHDL prendono il nome di “signal” (segnale/segnali). Semplificando al massimo, i “signal” sono l'equivalente dei fili di collegamento fra dispositivi logici reali. In realtà i “signal” VHDL sono oggetti più complessi e possono assumere natura diversa. Per questa ragione è necessario specificare quale “tipo” di segnali si prevede che siano collegati alle porte. Nel nostro esempio i segnali collegabili alle porte sono di tipo “bit”. Il tipo “bit” è un tipo predefinito per enumerazione, con due soli valori possibili corrispondenti ai simboli '0' e '1'.
Successivamente alla dichiarazione di entità, in uno stesso file di testo o in un file differente, bisogna specificare il comportamento dell'entità stessa. Nel linguaggio VHDL questo risultato si ottiene specificando almeno una “architecture” (architettura) per l'entità. Notiamo esplicitamente che ad una singola unità possono corrispondere più architetture. All'atto dell'effettivo utilizzo dell'entità come parte di un sistema più complesso, il VHDL prevede meccanismi per specificare a quale delle architetture definite per l'entità si deve fare riferimento. Per fissare le idee potremmo pensare di definire due architetture, una che si riferisce al caso di prestazioni ottimali in termini di velocità (chiameremo questa architettura “veloce”) e una che si riferisce al caso in cui nella realtà si dovessero ottenere prestazioni peggiori (chiameremo questa architettura “lenta”). Quello che segue è il codice che specifica le due architetture, che si può pensare di includere nello stesso file di testo che definisce l'entità, subito dopo la definizione dell'entità.
architecture veloce of inverter is begin fai:process (a) begin if (a='0') then y<='1' after 5 ns; else y<='0' after 7 ns; end if; end process fai; end architecture veloce; architecture lenta of inverter is begin fai:process (a) begin if (a='0') then y<='1' after 10 ns; else y<='0' after 14 ns; end if; end process fai; end architecture lenta;
Facciamo ora riferimento a una sola delle due architetture (per esempio l'architettura “lenta”) e, prima di entrare nel dettaglio del codice VHDL, riflettiamo un momento su quello che significa descrivere il comportamento di un sistema digitale. Per semplicità riferiamoci, per il momento, al caso di sistemi digitali combinatoriali. Se l'ingresso (o gli ingressi)di un sistema combinatoriale restano costanti nel tempo a partire da un certo istante $t_1$, possiamo essere certi che a partire da un certo istante $t_2>t_1$, una volta che siano completamente estinti i transitori conseguenti all'ultima variazione degli ingressi, l'uscita non si modificherà. Solo se dovessero cambiare gli ingressi, potrà succedere che le uscite, nei tempi derivanti dalla struttura interna del circuito, si modificheranno.
I blocchi “process” costituiscono il cuore di ogni architettura e rendono possibile la descrizione del comportamento di un blocco logico secondo la sequente modalità. Ciascun process ha una “sensitivity list”, ovvero un elenco di porte o di segnali ai quali risulta “sensibile”. Nel caso del nostro esempio la “sensitivity list” è composta dalla sola porta di ingresso “a”. In VHDL si dice che un process si “attiva” se uno dei componenti della sensitivity list cambia di valore. Fin tanto che nessuno degli oggetti che compongono la sensitivity list cambia valore, il processo rimane inattivo.
Inserire la porta “a” nella sensitivity list del process significa che il process trrà costantemente sotto controllo “a”. Se il process si accorge che a un certo istante $t_T$ il segnale cambia valore (in VHDL si dice che il segnale/porta subisce una “transition” o transizione), allora devono essere svolte le azioni che sono descritte nel corpo del process, con il valore di “a” e di qualunque altro segnale o porta del sistema che mantengono il valore assunto a $t_T^+$. Capire bene il senso dell'ultima proposizione è di fondamentale importanza per la comprensione del modo in cui in VHDL si descrivono i circuiti. In sistanza, tutte le volte che un qualunque process del sistema si accorge che uno degli elementi della sua sensitivity list ha subito una transizione, è come se si interrompesse immediatamente lo scorrere del tempo. Con il tempo “fermo” (sistema immobile) l'algoritmo che costituisce il corpo del process è come se si svolgesse in una dimensione parallela. L'unico fine di un process è predisporre, sulla base della situzione attuale (quella del sistema con il tempo “congelato”, avvenimenti che avranno luogo quando il tempo riprenderà a scorrere. Una volta che questi avvenimenti sono stati “predisposti”, il tempo del sistema ricomincia a scorrere, e sono proprio gli avvenimenti predisposti dai porcess del sistema che, intervenendo a modificare il valore dei segnali, possono portare a una nuova attivazione di un process, al congelamento del tempo nel sistema e alla predisposizione di nuovi avvenimenti. Gli avvenimenti di cui parliamo si chiamono “eventi” in VHDL. Quando di simula il comportamento di un sistema VHDL è come se esistesse una divinità capace di fermare e far ripartire il corso del tempo. Questa divinità è “onnisciente” nel senso che è a conoscenza di tutti gli “eventi” predisposti da tutti i processi. Egli raccoglie tutti gli eventi in una lista ordinata (se un evento $E_1$ deve avvenire al tempo $t_1$ e un evento $E_2$ deve avvenire al tempo $t_2" e $t_2>t_1$ allore $E_1$ precede $E_2$ nella lista degli eventi). Ogni volta che un processo predispone un nuovo evento, la lista viene aggiornata, così Egli sa sempre, a priori, qual'è il prossimo evento che dovrà verificarsi e si prepara a fermare il tempo e ad attivare tutti i processi che sono sensibili al segnale o alla porta che subisce l'evento.
Nel caso nel nostro esempio, se “a” cambia da '1' a '0', il corpo del process predisporrà l'evento descritto dalla riga :
y<='1' after 10 ns;
che si può interpretare in questo modo: y “dieventerà” (simbolo “⇐”) '1' dopo 10 ns a partire dall'istante in cui il tempo riprenderà a scorrere.
Questo evento viene inserito nella lista degli eventi. Verosimilmente l'uscita dell'inverter che stiamo prendendo in considerazione è “collegata” all'ingresso di un'altra porta logica (vedremo più avanti come si realizzano i collegamenti fra entità), per cui nel'istante $t_T^++10 ns$ ($t_T$ è l'istante in cui è cambiato “a”), il tempo si fermerà nuovamente e il processo che è sensibile al valore di “y” si attiverà per predisporre nuovi eventi.
Sulla base di quanto discusso fino a questo momento, dovrebbe essere chiaro il meccanismo secondo il quale un simulatore VHDL (la divinità di cui sopra) procede per “calcolare” l'evoluzione del sistema:
- Si guarda la lista degli eventi e si aspetta (o ci si porta) all'istante corrispodente al primo evento che deve avvenire;
- Si fa avvenire l'evento, si ferma il tempo e si attivano tutti i process che sono sensibili all'evento;
- Si raccolgono gli eventi predisposti da tutti i processi che sono stati attivati e si inseriscono, in ordine, nella lista degli eventi;
- Si ricomincia.
In sostanza eventi causano altri eventi, che causano altri eventi, che causano altri eventi… e così via.
Se a un certo istante la lista degli eventi dovesse essere vuota (sono finiti gli eventi già inseriti e nessun processo ne ha inseriti di nuovi), evdientemente la simulazione termina perché non c'è nessuna ulteriore azione da compiere.
Come è facile capire, però, questo meccanismo ha un “piccolo” problema: se è vero (come è vero) che in VHDL si descrivono sistemi fatti da entità collegate fra loro e che le entità operano sulla base di process che obbediscono al modello descritto più sopra, allora, perché possa esserci un evento, deve necessariamente essercene uno in un tempo precedente! Ma al tempo “0” non ci sono eventi perché nessun processo è stato attivato e ha potuto generare eventi…….
Questo problema è comune a tutti i simulatori ad eventi discreti (categoria nella quale ricadono i simulatori VHDL). In VHDL il probelma viene superato nel modo seguente:
- All'inizio del tempo ($t=0$) tutti i segnali e le porte che corrispondono a tipi definiti per enumerazione assumono il valore “più a sinistra” del tipo (nel caso del tipo bit, il valore più a sinistra del tipo è '0');
- All'inizio del tempo ($t=0$), TUTTI i “process” vengono attivati, INDIPENDENTEMENTE dalla loro “sensitivity list”.
Supponiamo ora di voler verificare il comportamento dell'inverter che abbiamo descritto. Abbiamo, come minimo, bisogno di un “generatore di segnali” che sia collegato all'ingresso dell'inverter e che produca, a istanti da noi prestabiliti, transizioni da '0' a '1' e viceversa. Il VHDL opera solo con entità, quindi dobbiamo trovare il modo di creare una entità che si comporti come un generatore di segnale. Questo può essere ottenuto con il codice seguente.
entity sig_gen is port(y:out bit); end entity sig_gen; architecture behav of sig_gen is begin fai: process wait for 100 ns; y<='1'; wait for 100 ns; y<='0'; wait; end process fai; end architecture behav;
