User Tools

Site Tools


es_vhdl_simu_01

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$, allora $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
	process
	begin
		y<='1' after 100 ns, '0' after 200 ns, '1' after 300 ns;
                wait; --this means no other event will be generated by this process.
	end process;
 
end architecture behav;

L'entità “sig_gen” ha una sola uscita (“y”) e la sua architettura contiene un solo processo con la sensitivity list vuota. Questo significa che il processo verrà attivato una ed una sola volta all'inizio dei tempi ($t=0$). Si noti anche che all'inizio dei tempi, in accordo con il modo di operare del simulatore GHDL, “y” vale '0' (valore più a sinistra del tipo). L'unica riga di codice che compare nel corpo del process ha come effetto quello di programmare una transizione per l'uscita da '0' a '1' a 100 ns, una transizione da '1' a '0' a 200 secondi e una transizione novamente da '0' a '1' a 300 ns.

Ora il problema è “collegare” il generatore di segnali all'ingresso dell'inverter per osservare il comportamento dell'uscita di quest'ultimo. Il problema è che in in fase di simulazione si può fare riferimento ad una ed una sola entità. D'altra parte in VHDL si possono creare e descrivere entità che contengono altre entità collegate fra loro come parte della loro architettura. Una possibile entità che consente di ottenere il risultato che ci interessa è la seguente:

entity testbench is
end entity testbench;
 
 
architecture struct of testbench is
 
signal filo:bit;
signal uscita:bit;
 
begin 
 
generatore:entity work.sig_gen(behav)
		port map (y=>filo);
portinv:entity work.inverter(lenta)
		port map (a=>filo,y=>uscita);
 
end architecture struct;

Come si vede, l'entità testbench è priva di uscite e di ingressi. Il corcop dell'architettura è costituito dall'indicazione che devono essere usate due entità. Alla prima entità abbiamo dato il nome “generatore” e abbiamo specificato che si tratta dell'entità di nome sig_gen che si trova nella libreria di default “work” (vedi più avanti per una spiegazione di come l'entità sig_gen prima discussa diventa parte della libreria work) In particolare si richiede di usare l'architettira behav (l'unica definita, del resto). Alla seconda entità abbiamo dato il nome protinv, abbiamo specificato che si tratta di una entità di tipo inverter (presente nella libreria di default di tipo work) e che bisogna usare l'archotettura “lenta”. Le riche con “port map” indicano a quale “segnale” deve essere collegata ciascuna delle porte definite al momento della creazione delle entità. I “segnali” possono per il momento essere visti come l'equivalente dei “fili di collegamento” di un circuito reale. I segnali che si intendono usare devono essere definiti prima dell'inizio (begin) dell'architettura vera e propria e devono essere dello stesso tipo delle porte alle quali devono essere collegati. Il fatto che la porta “y” del generatore risulta connessa allo stesso segnale della porta “a” dell'inverter, significa, appunto, che abbiamo collegato l'uscita del generatore all'ingresso dell'inverter. Il secondo segnale serve, come faremmo in laboratorio, per “collegare un filo” alla porta di uscita dell'inverter per osservare il valore dell'uscita mediante un apposito strumento di misura. Vale la pena di osservare che l'inclusione di una entità come parte di una architettura equivale a far diventare l'architettura dell'entità inclusa parte dell'architettura stessa. In altre parole, nel nostro caso specifico, i processi dell'architettura inclusa diventano i processi dell'architettura includente, con i segnali definiti che prendono il posto delle porte.

Vediamo ora come si procede operativamente alla simulazione del sistema con il simulatore GHDL. Supponimao che le tre entità definite (inverter, sig_gen e testbench) siano contenute (dichiarzione di entità e architetture) in tre file che chiamiamo rispettivamente inverter.vhdl, sig_gen.vhdl e testbench.vhdl (i nomi dei file non devono avere necessariamente relazione con i nomi delle entità: in uno stesso file possono essere inserite più definizioni di entità). Supponiamo che i file siano contenuti nella stessa directory e di lavorare con un terminale in questa directory. Si parte eseguendo un'analisi dei file. Questo di ottiene con il comando

  • GHDL -a <nomefile>

per esempio:

ghdl -a inverter.vhdl

Se il comando ha successo non viene restituito alcun errore. Inoltre, tutte le entità definite nel file analizzato entrano automaticamente a far parte della libreria predefinita “work” che è automaticamente accessibile da tutti i file vhdl presenti nella directory. Nota bene: se un file vhdl fa uso di altre entità (è il caso dell'entità testbench), le entità da utilizzare devono far già parte della libreria work. Pertanto, dopo aver analizzato il file inverter.vhdl, procediamo con il file sig_gen.vhdl e solo dopo con il file testbench.vhdl:

ghdl -a sig_gen.vhdl
ghdl -a testbench.vhdl

A questo punto siamo pronti a “corstruire” la struttura di simulazione. QUesto si ottiene con il comando

  • ghdl -a <topentity>

dove <topentity> è l'entità il cui comportamento vogliamo studiare e che comprende, eventualmente altre entità. Eseguiremo dunque:

ghdl -e testbench 

A questo punto possimao far partire la simulazione vera e propria, specificando anche un file nel quale conservare i risultati della simulazione e specificando anche il tempo (tempo simulato) per il quale vovliamo protrarre l'analisi. Supponiamo di voler analizzare il nostro sistema per una durata massima di 500 ns e che vogliamo conservare il risultato in un file in formato vcd (leggibile dal programma di analisi grafica gtkwave) di nome out.vcd Scriveremo allora:

ghdl -r testbench --vcd=out.vcd --stop-time=500ns  

Si noti che il simulatore si interrompe automaticamente se la lista degli eventi viene easurita prima del tempo massimo specificato.

Una volta completata la simulazione possiamo utilizzare gtkwave per visulizzare i risultati:

gtkwave out.vcd 

Dopo aver selezionato i segnali da visulizzare (“filo” e “uscita”, nel nostro caso) e aver opportunamente regolato la scala dei tempi per la visualizzazione otteniamo quanto mostrato in figura. L'interpretazione di quanto accade dovrebbe essere completamente comprensibile sulla base di quanto esposto fino ad ora. Se non lo fosse (valori inziziali, prime transizioni ecc. ecc.), si invita a rileggere questa pagina dall'inizio.

Come ultimo esempio in questa introduzione, costruiamo ora un sistema composto da una catena di inverter. In questo esmpio useremo l'entità “inverter” con l'architettura “veloce”.

Il file vhdl seguente (catena.vhdl) descrive una catena di 10 inverter sollecitati dal generatore sig_gen prima definito.

entity testcatena is
end entity testcatena;
 
architecture struct of testcatena is
signal gen_to_in_1:bit;
signal out_1_to_in_2:bit;
signal out_2_to_in_3:bit;
signal out_3_to_in_4:bit;
signal out_4_to_in_5:bit;
signal out_5_to_in_6:bit;
signal out_6_to_in_7:bit;
signal out_7_to_in_8:bit;
signal out_8_to_in_9:bit;
signal out_9_to_in_10:bit;
signal out_10:bit;
begin 
 
gen    :entity work.sig_gen(behav)
		port map (y=>gen_to_in_1);
inv_01:entity work.inverter(veloce)
		port map (a=>gen_to_in_1, y=>out_1_to_in_2);
inv_02:entity work.inverter(veloce)
		port map (a=>out_1_to_in_2, y=>out_2_to_in_3);
inv_03:entity work.inverter(veloce)
		port map (a=>out_2_to_in_3, y=>out_3_to_in_4);
inv_04:entity work.inverter(veloce)
		port map (a=>out_3_to_in_4, y=>out_4_to_in_5);
inv_05:entity work.inverter(veloce)
		port map (a=>out_4_to_in_5, y=>out_5_to_in_6);
inv_06:entity work.inverter(veloce)
		port map (a=>out_5_to_in_6, y=>out_6_to_in_7);
inv_07:entity work.inverter(veloce)
		port map (a=>out_6_to_in_7, y=>out_7_to_in_8);
inv_08:entity work.inverter(veloce)
		port map (a=>out_7_to_in_8, y=>out_8_to_in_9);
inv_09:entity work.inverter(veloce)
		port map (a=>out_8_to_in_9, y=>out_9_to_in_10);
inv_10:entity work.inverter(veloce)
		port map (a=>out_9_to_in_10, y=>out_10);
 
end architecture struct;

Procedendo come in precedenza per eseguire la simulazione della nuova entità, si ottiene il risultato nella figura seguente (sono stati visulizzati il segnale di ingresseo e i segnali di uscita ogni 2 inverter).

Si invita il lettore a interrogarsi sui trasitori iniziali che si possono osservere nei segnali in uscita alle porte più lontane dal segnale di ingresso.

es_vhdl_simu_01.txt · Last modified: 2021/03/05 08:57 by admin

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki