Sei sulla pagina 1di 305

Esistono

  vari   modi   di   rappresentare   le   informazioni,   inteso   come   valore   numerico,   in  


particolare   ci   rifaremo   spesso   a   numeri   interi,   razionali   e   reali.   Generalmente   non   si   lavora  
mai  sui  numeri  interi  come  entità  astratte,  ma  su  delle  loro  rappresentazioni,  cioè  un  qualcosa  
che   assume   forma   di   simboli   sui   quali   possiamo   eseguire   delle   operazioni   automatiche  
(quando   è   possibile),   per   arrivare   alla   rappresentazione   del   risultato.   Il   sistema   decimale,   il  
sistema   romano,   per   esempio,   sono   alcuni   dei   possibili   sistemi   di   rappresentazione,   ovvero  
uno  dei  possibili  modi  attraverso  cui  posso  rappresentare  delle  grandezze.  
13  non  è  il  numero  “tredici”,  ma  una  rappresentazione  del  numero  “tredici”.  I  numeri  che  sono  
facilmente  definibili  in  astratto,  almeno  per  ora,  sono  i  numeri  naturali.  
Ci  sono  due  algoritmi  fondamentali  che  vengono  utilizzati  per  fare  i  conti,  basati  sul  modo  in  
cui   vengono   rappresentati   i   numeri:   somma   e   differenza.   Sulla   base   di   questi   due   algoritmi,  
possiamo  effettuare  qualunque  altro  tipo  di  operazione.  
Non   tutti   i   tipi   di   rappresentazione   che   utilizziamo   godono   di   queste   proprietà,   come   ad  
esempio   la   rappresentazione   del   tempo.   Tutto   questo   per   dire   che   esistono   molti   modi   per  
rappresentare   un   numero.   Per   ogni   sistema   di   numerazione   esiste   un   certo   numero   di  
elementi   fondamentali   che   possono   essere   definiti.   Non   è   possibile   avere   una  
rappresentazione  basata  su  un  numero  infinito  di  simboli.  In  generale  un  numero  può  essere  
rappresentato   da   una   serie   di   cifre   (o   simboli)   in   numero   finito:   {Xn-­‐1   ,   Xn-­‐2   ,   …   ,   X0   }   (dove  
l’ordine  con  cui  vengono  utilizzati  questi  simboli  ha  un  suo  significato;  conta  la  posizione  dei  
simboli  nella  stringa).  “n”  rappresenta  il  numero  di  simboli.  Per  ciascuno  di  questi  simboli  è  
definito  un  insieme  di  possibili  valori  {  Di  }  (insieme  dei  possibili  valori  che  possono  assumere  
i  singoli  Xi  ;  per  il  sistema  decimale  i  valori  possibili  sono  0,1,2,…9).  La  cardinalità  è  l’insieme  
dei   valori   che   può   assumere   il   generico   simbolo   Xi   .A   questo   punto   serve   una   regola   di  
interpretazione,   ovvero   una   regola   che   ci   consenta   di   passare   dalla   scrittura   in   termini   di  
simboli  al  numero  vero  e  proprio.  Il  contrario  (ovvero  passare  da  un  numero  ad  una  stringa  di  
simboli)   non   è   sempre   possibile   in   quanto   il   sistema   potrebbe   essere   ridondante.   Il   numero  
massimo   di   valori   numerici   rappresentabili,   per   sistemi   non   ridondanti,   è   dato   dal   prodotto  
delle   cardinalità   di   ogni   elemento   della   stringa:  𝑁 =   !!! !!! |𝐷! |.   Le   regole   di   interpretazione  
più   semplici,   ovvero   quelle   che   ci   portano   più   facilmente   ad   ottenere   la   corrispondenza   tra  
una  cifra  e  il  suo  numero  in  maniera  tale  che  sia  facile  eseguire  dei  calcoli  sulle  cifre  (ho  due  
numeri   -­‐>   li   rappresento   -­‐>   effettuo   calcoli   operando   sulla   rappresentazione   mediante  
algoritmo   tipico   della   rappresentazione   -­‐>   ottengo   la   rappresentazione   del   risultato   come  
numero),   si   basano   sul   fatto   che   ogni   cifra   ha   un   peso   in   dipendenza   della   posizione.  
Considerando   l’insieme   dei   pesi   {Wn-­‐1   ,   Wn-­‐2   ,   …   ,   W0   },   un   numero   N   sarà   dato   da:  𝑁 =
!!!
!!! 𝑤! 𝑥! .  Nel  decimale  W0  =  1,  W1  =  10,  etc…,  nel  tempo  W0  =  1  ,  W1=  60,  W2  =  3600  .  
Nell’insieme   delle   rappresentazioni   c’è   quella   pesata   su   radice,   cioè   un   sistema   in   cui,   ad   ogni  
passo,  si  fa  riferimento  ad  un  vettore  di  radici,  cioè  un  sistema  in  cui  si  ha:  
 
W0  =  1  W1  =  W0  r0   W2  =  W1r1  
 
Se  il  vettore  delle  radici  è  costante,  il  sistema  sarà  a  radice  fissa  e  si  avrà:   !!! !!! 𝑥! 𝑟! .  Tra  i  vari  
sistemi   di   numerazione   posizionale   pesati   a   radice   fissa,   oltre   al   sistema   decimale,   c’è  anche  il  
sistema   binario   naturale,   dove   le   cifre   possibili   sono   {0,1}.   Il   bello   di   questi   sistemi   è   che   la  
somma   tra   due   numeri   di   può   ricondurre   sempre   all’algoritmo   della   somma,  
indipendentemente  dalla  radice.    
L’algoritmo   della   somma   funziona   nel   seguente   modo:   si   somma   una   cifra   alla   volta   e  
l’eventuale  riporto  si  mette  a  sinistra.  Facendo  ricorso  ad  una  tabella  in  cui,  per  ogni  cifra  di  
un   addendo   e   per   ogni   altra   cifra   dell’altro   addendo,   possiamo   scrivere   qual   è   la   somma   e  
l’eventuale  riporto  (considerando  un  eventuale  riporto  precedente)  .  
 
  0   1   2   3   4   5   6   7   8   9  
0                      
1                      
2                      
3       r=0  -­‐>                
 S=5  
 r=0  
4          
           
5          
           
6          
           
7          
           
8          
           
9          
           
 
Nel   caso   del   sistema   binario   dobbiamo   prendere   in   considerazione   molte   meno   situazioni  
rispetto  al  sistema  decimale.    
 
A   B   C  (riporto  precedente)   S   COUT  
0   0   0   0   0  
0   1   0   1   0  
1   0   0   1   0  
1   1   0   0   1  
0   0   1   1   0  
0   1   1   0   1  
1   0   1   0   1  
1   1   1   1   1  
 
 
L’altra   operazione   fondamentale   è   la   differenza,   operazione   che   può   essere   ricondotta   alla  
somma   grazie   all’utilizzo   dei   numeri   relativi   (esiste   un   certo   numero   di   modi   per   poterli  
rappresentare).  La  differenza  tra  due  numeri  a  e  b  può  quindi  essere  vista  come  la  somma  di  a  
con   –b.   Abbiamo   quindi   bisogno   di   poter   visualizzare   sia   numeri   positivi   che   negativi,   e   la  
rappresentazione   di   un   numero   negativo   consiste   nell’aggiungere   un   meno   davanti   ad   un  
numero   che   costituisce   la   rappresentazione   di   un   numero   naturale   positivo.   L’algoritmo   della  
sottrazione  tra  due  numeri  si  basa  sui  seguenti  passi:  trovo  il  numero  più  grande  in  modulo,  
effettuo  la  sottrazione  e  il  segno  del  risultato  sarà  quello  del  numero  più  grande  in  modulo.    
Trasferire  il  tutto  in  un  sistema  di  calcolo  automatico  è  complicato.  Fortunatamente  esistono  
dei   modi   per   rappresentare   i   numeri   in   modo   tale   che   l’operazione   tra   due   numeri   attraverso  
questa   rappresentazione   si   riconduca   sempre   allo   stesso   algoritmo,   indipendentemente   dal  
fatto  che  siano  rappresentazioni  di  numeri  positivi  o  negativi.  Il  tipo  di  rappresentazione  che  
viene   utilizzata   si   chiama   rappresentazione   in   complemento.   Per   arrivare   a   capire   di   cosa   si  
tratta   facciamo   un   esempio   abbastanza   utile.   Immaginiamo   di   dover   lavorare   sulla   distanza  
rispetto  ad  un  punto  (che  per  noi  sarà  l’origine)  e  che  il  metro  utile  per  misurare  le  distanze  
sia   un’automobile   con   un   contachilometri   (di   quelli   a   rotelle);   supponiamo   che   il  
contachilometri  abbia  5  rotelle  (arriverà  massimo  a  99999  km).  
 
[figura  macchinina]  
 
 
 
Immaginiamo  che  le  distanze  siano  misurate  sia  nel  senso  positivo  (sorge  immediatamente  il  
problema   che   possiamo   univocamente   determinare   le   distanze   dei   punti   soltanto   se   questi  
punti   si   trovano   a   distanze   rispetto   allo   zero   inferiori   a   99999   km),   che   negativo.   Se   questo  
punto   si   trovasse   a   100000   km,   porterebbe   la   stessa   indicazione   di   quella   nel   caso   in   cui  
questo  punto  si  trovasse  a  distanza  1  km  dall’origine.    
Immaginiamo  che  la  macchina  vada  indietro:  se  andasse  indietro  di  1  km,  il  contachilometri  
segnerebbe   99999   km   e   così   via.   Potremmo   quindi   fare   così:   i   numeri   99999,   99998,   etc…,  
potrei   interpretarli   come   se   la   macchina   si   fosse   spostata   indietro   di   1   km,   2   km,   etc….   Divido  
in  pratica  il  mio  intervallo  in  tante  distanze  positive  e  tante  distanze  negative.  Da  0  a  49999  
rappresenterò   le   distanze   positive,   da   50000   a   99999   rappresenterò   le   distanze   negative.   Il  
limite  superiore  sarà  49999  (rappresentazione  numeri  positivi),  il  limite  inferiore  sarà  50000  
(rappresentazione  numeri  negativi).  
 
[disegno  rappresetnazione]  
 
 
Con  questo  schemino  abbiamo  visto  che  se  io  mi  limito  a  calcolare  distanze  limitate  a  destra  e  
a   sinistra,   riesco   formalmente   a   specificare   sia   numeri   interi   positivi   che   numeri   interi  
negativi.  Avendo  messo  50000  come  limite  inferiore,  si  nota  che  tutti  i  numeri  che  cominciano  
con   0,1,2,3,4   rappresentano   distanze   positive,   tutti   i   numeri   che   cominciano   con   5,6,7,8,9  
rappresentano  distanze  negative.  
La   cosa   interessante   di   questo   tipo   di   rappresentazione   è   che   posso   sommare   due   numeri,  
purché  non  escano  dai  limiti,  senza  distinguere  tra  numeri  positivi  e  numeri  negativi.    
 
Esempio  1:  
 
Numero       Rappresentazione  
 
x1=-­‐1       X1=99999        
x2=-­‐10       X2=99990        
x1  +  x2  =       X1+  X2=        99999  +  
                                                                     99990        
             -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐  
               199989  
 
Se  mi  limito  a  prendere  in  considerazione  solo  le  5  cifre  di  questo  conto,  buttando  via  la  cifra  
più   significativa,   99989   con   il   sistema   appena   definito   rappresenta   proprio   -­‐11,   che   è   la  
somma  di  x1  e  x2  .  
 
Esempio  2:  
 
Numero       Rappresentazione  
 
x1=-­‐10       X1=99990        
x2=+37       X2=37        
x1  +  x2  =       X1+  X2=        99990+  
                                                                     00037        
             -­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐-­‐  
               100027  
 
Questo   tipo   di   rappresentazione   si   chiama   rappresentazione   in   complemento   alla   radice.   Se  
x≥0  XR  =  x,  se  x<0  XR  =  100000+x.    
L’obiettivo   è,   applicando   degli   opportuni   algoritmi,   quello   di   riuscire   ad   ottenere,   nel   dominio  
di   xR ,  la  rappresentazione  di  operazioni  fatte  su   X .  
Ad   esempio   abbiamo   due   numeri   relativi   x  e   y ,   e   dobbiamo   fare   z = x + y .   Se  
rappresentiamo   x  con   xR  ed   y  con   y R ,   vogliamo   vedere   se   esiste   un   modo   per   cui   la  
rappresentazione  di   z R  sia  ottenibile  in  maniera  semplice  da   xR  ed   y R .  Vediamo  che  ciò  non  è  
scontato   ma   lavorando   con   questi   sistemi,   cioè   rappresentando   x  in   complemento,   le  
operazioni   di   somma,   sottrazione   o   negazione,   facendo   un’opportuna   scelta   del   valore   della  
costante   di   complementazione   C,   possono   ridursi   di   fatto   nell’utilizzo   di   un   unico   blocco  
funzionale  con  alcune  aggiunte  che  poi,  quando  si  tratterà  di  fare  sistemi  efficienti  o  più  veloci  
o  altro,  definirà  l’architettura  di  questi  sistemi.  
Ricordiamo   che   per   rappresentazione   in   complemento   intendiamo   che   xR ,   che   è   un   numero  
positivo  che  va  a  rappresentare  un  numero  negativo,  sia  ottenibile  dalla  relazione:  
xR = x mod C  
Che  porta  ad  un  numero  positivo  dalla  sua  definizione.  Graficamente:  
 
xR=x mod C

-2C -C
-C/2 C/2
C 2C x

 
 
dove  i  pallini  rappresentano  l’estremo  incluso.  
Per  ottenere  una  rappresentazione  univoca,  ovvero  perché  poi  a  partire  da   xR  si  possa  risalire  
ad   x ,   siamo   costretti   a   prendere   solamente   un   intervallo   di   lunghezza   pari   a   C   e   dato   che  
solitamente  dobbiamo  rappresentare  numeri  si  positivi  che  negativi,  generalmente  cerchiamo  
di  centrare  questo  intervallo  nell’origine.  
Si   noti   che   se   C   è   pari   C/2   è   un   numero   intero   e   +C/2   e   –C/2   sono   valori   che   x ,   che   è   un  
numero   relativo,   può   assumere;   se   C,   come   accade   solitamente   nei   casi   di   maggiore   interesse,  
è  un  numero  dispari  +C/2  e  –C/2  sono  valori  che  non  possono  in  nessun  caso  essere  assunti  
da   x .  
x <C 2
Generalmente  ci  occuperemo  di  quei  valori  di   x  tali  che   .  In  questa  maniera  però  nel  
caso  di  C  dispari  possiamo  comprendere  tutte  le  possibili   x ,  se  C  è  pari  ciò  non  è  vero.  Questo  
si   ha   perché,   siccome   l’intervallo   è   fatto   da   un   numero   complessivamente   pari   di   punti   e   lo  
zero   è   un   punto   che   conta   nel   numero   di   valori   spendibili   nella   nostra   rappresentazione,  
quando  cerchiamo  di  mettere  a  metà  strada  l’intervallo  di  C  valori,  se  C  è  dispari  prendiamo  il  
numero   di   valori   negativi   uguale   al   numero   di   valori   positivi,   tanto   in   qualunque   modo   ci  
mettiamo   –C/2   e   +C/2   non   sono   comunque   valori   assumibili,   se   C   è   pari   dobbiamo   fare   una  
scelta   perché   se   appoggiamo   l’intervallo   su   –C/2,   +C/2   non   è   rappresentabile   e   viceversa.  
Solitamente  la  scelta  che  si  fa  in  questo  caso,  legata  alla  possibilità  di  fare  alcune  operazioni  
come   ad   esempio   il   riconoscimento   del   segno,   è   quella   di   fare   in   modo   che   il   valore  
rappresentabile  sia  –C/2.  
A  questo  punto  possiamo  dire  che:  
⎧ x se x ≥ 0
xR = ⎨
⎩C + x = C − x se x < 0  
E  se  vogliamo  tornare  indietro:  
⎧ C
⎪⎪ xR se xR <
2
x = ⎨
⎪ x − C se x > C
⎪⎩ R R
2  
ricordandoci  che   xR  è  in  ogni  caso  un  numero  positivo.  
 
Supponiamo  di  lavorare  in  base  10  e  di  avere  tre  cifre  a  disposizione  per  una  mappatura  del  
numero   che   vogliamo   rappresentare,   che   chiamiamo   X 2 X1 X 0 .   Allora   xR  può   assumere  
valori  tali  che:  
0 ≤ xR ≤ 999  
La  rappresentazione  dipende  dalla  scelta  di  C:  se  scegliamo  C  troppo  piccolo  abbiamo  la  non  
distinguibilità  di  un  codice  rispetto  all’altro,  mentre  se  scegliamo  C  troppo  grande  c’è  il  rischio  
di   sprecare   codice.   La   cosa   che   solitamente   si   fa   è   quella   di   scegliere   C   vicino   al   valore  
dell’intervallo   della   rappresentazione.   Ad   esempio   una   scelta   possibile   nel   nostro   caso  
potrebbe   essere   quella   di   scegliere   C = 1000 .   Questa   è   solo   una   scelte   possibili,   non   è   detto  
che   sia   l’unica,   infatti   potrei   anche   scegliere   C = 1001, o 1002, o 1003  o   anche   C = 999 ,   come  
vedremo   non   posso   però   usare   C = 998 .   Diciamo   che   con   C = 1000  riesco   a   rappresentare  
1000  valori  distinti  con  la  corrispondenza  uno  ad  uno,  posso  ancora  scegliere   C = 999  e  devo  
rinunciarne  ad  uno,  con   C = 998  devo  rinunciarne  a  due  e  così  via…  .    
Facciamo  qualche  conto:  
Con   C = 1000  
x → xR → X
13 13 013
−27 1000 + ( −27 ) 973
 
Un  altro  esempio:  
⎧ C
⎪⎪se xR < 2 → x = xR C
xR = 561 → ⎨ ⇒ 561 > ⇒ x = 561 − 1000 = −439
⎪se x > C → x = x − C 2
R R
⎪⎩ 2  
Ricordiamo   che   anche   se   x  e   y ,   che   sono   due   numeri   relativi,   sono   rappresentabili,   non   è  
detto  che  anche  la  loro  somma  lo  sia.  Può  succedere  infatti  che  in  qualche  caso  il  risultato  esca  
fuori  dall’intervallo  di  rappresentazione.  
13 + ( −27 ) = −14
Nel  caso  di  prima  avevamo   .  
Siccome   sappiamo   già   quali   sono   i   numeri   e   conosciamo   anche   la   loro   somma,   (13   è  
rappresentabile,   -­‐27   è   rappresentabile   e   -­‐14   è   anch’esso   rappresentabile),   possiamo   fare   la  
somma   senza   problemi.   Allora   facciamo   l’operazione   di   somma   fra   questi   due   numeri   e  
vediamo  cosa  otteniamo:  
0 1 3 +
9 7 3 =
9 8 6  
C
986 > ⇒ x = 986 − 1000 = −14
2  
Che  è  il  risultato  corretto.  
 
400 + ( −200 )
Ora  vediamo  qualche  caso  particolare,  ad  esempio  la  somma  di   :  
x = 400 x = 400
→ R
y = −200 yR = 800  
Facciamo  attenzione  che  la  somma  non  sta  nel  dominio  di   xR  (che  è  1000).    
 
Proviamo  a  fare  somma  lo  stesso:  
1
4 0 0 +
8 0 0 =
(1) 2 0 0  
Notiamo  che  se  non  consideriamo  il  riporto  abbiamo  comunque  ottenuto  il  risultato  corretto.  
 
Facciamo  gli  stessi  esempi  con   C = 999 :  
x = 13 xR = 13 X = 013
→ →
y = −27 yR = 999 − 27 = 972 Y = 972  
In   questo   caso   C 2 = 499,5  l’intero   rappresentabile   più   vicino   è   499,   ma   così   nell’intervallo  
compreso   tra   -­‐499   e   499   ci   stanno   998   numeri   e   quindi   ci   sarà   una   rappresentazione   non  
utilizzata  o  quantomeno  qualcosa  di  strano.  Notiamo  che  sia  il  13  e  il  -­‐27,  che  la  loro  somma  
sono  rappresentabili  nell’intervallo  dei  valori  possibili.  Facciamo  la  somma:  
0 1 3 +
9 7 2 =
9 8 5  
C
985 > ⇒ x = 985 − 999 = −14
2  
Il  risultato  è  corretto.  
Una   cosa   che   si   nota   subito   è   che   utilizzando   C = 999 ,   la   rappresentazione   negativa   dei  
numeri  si  ottiene  semplicemente  facendone  il  complemento  a  9.  
Vediamo  un  altro  esempio:  
x = 400 x = 400
→ R
y = −200 yR = 799  
Facciamo  la  somma:  
1
4 0 0 +
7 9 9 =
1 9 9  
Ma  il  199  rappresenta  199  e  non  rappresenta  200!!!  
Vedremo   che   il   risultato   corretto   si   ottiene   facendo   la   somma   e,   tutte   le   volte   che   il   risultato   è  
rappresentabile,  sommando  il  riporto  al  risultato  ottenuto:  
1 9 9 +
1 =
2 0 0  
Se   ci   facciamo   caso   secondo   la   rappresentazione   usata   con   C = 999 ,   xR = 0  rappresenta   il  
numero   0,   ma   anche   xR = 999  rappresenta   il   numero   0.   Con   999   cifre   in   generale   possiamo  
rappresentare  998  valori  perché  uno  lo  dobbiamo  spendere  per  rappresentare  lo  zero.  
Questo  è  uno  zero  “buono”  nel  senso  che  ai  fini  delle  operazioni  che  facciamo,  ad  esempio  la  
somma,  sommare  000  o  999  è  esattamente  la  stessa  cosa:  
0 1
0 1 3 + 0 1 3 +
0 0 0 = 9 9 9 =
0 1 3 + 0 1 2 +
0 = 1 =
0 1 3 0 1 3  
Finora  abbiamo  fatto  degli  esempi  su  alcuni  complementi  usati.  
Uno  è  il  complemento  alla  radice  [RC]  (o  complemento  all’intervallo)  per  cui:  
 
C = r n  
 
Dove   r   è   la   base   (o   radice)   ed   n   è   il   numero   di   cifre   a   disposizione   per   la   rappresentazione.   In  
tal  modo  C  rappresenta  il  numero  più  piccolo  non  rappresentabile  su  n  cifre.  
L’altro  è  il  complemento  alla  cifra  [DC]:  
 
C = r n − 1  
 
dove  questa  volta  C  è  rappresentabile.  
 
Sorge   un   problema:   dati   xR  ed   y R  rappresentabili,   non   è   detto   che   la   loro   somma   Z   sia   R
rappresentabile.    
 
Esempio:  
 
x  =  30000   y  =  30000    
x+y   =   60000   non   appartiene   all’intervallo   (possiamo   rappresentare   valori   massimo   fino   a  
49999)  
 
La  soluzione  consiste  nell’eseguire  comunque  l’algoritmo,  dopodiché  si  verifica  se  il  risultato  è  
corretto.    
In  riferimento  all’esempio  appena  visto,  possiamo  prendere  le  rappresentazioni  di  x  e  di  y  su  
5   cifre   e   lavorare   ora   su   6   cifre.   Dopodiché   devo   verificare   se   mi   servono   veramente   6   cifre  
per  rappresentare  il  numero  o  me  ne  bastano  5.      
 
 
 
 
Esempio:    
 
 
1)  Considero  i  numeri  su  5  cifre:  
 
x  =  43472       y  =  37444  
 
2)  Passo  alla  rappresentazione  su  6  cifre:  
 
  XR  =  043472       YR  =  037444  
 
3)  ZR  =  XR  +  YR  =  080916  
 
Questo  numero,  in  una  notazione  a  6  cifre,  rappresenta  un  numero  positivo.  Se  tolgo  lo  zero  
iniziale,   ovvero   lo   considero   quindi   in   una   notazione   a   5   cifre,   rappresenta   un   numero  
negativo,   quindi   cambia   completamente   il   significato!   Da   quanto   visto   nell’esempio   sopra  
considerato,   si   deduce   che   si   può   passare   da   una   rappresentazione   a   n   cifre   ad   una  
rappresentazione   a   n-­‐1   cifre   solo   se   non   cambia   il   significato   del   numero.   Discorso   analogo  
vale  per  i  numeri  negativi.      
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Se  decidiamo  di  utilizzare  la  codifica  di   xR  in  binario  naturale,  su  tre  cifre:  
il  dominio  di   xR  sarà:  
0 ≤ xR < 8  
 
COMPLEMENTO  A  2  
3
La  costante  di  complementazione  in  questo  caso  è   C = 2 = 8 → C 2 = 4  
X xR x
000 0 0
001 1 1
010 2 2
011 3 3
100 4 −4
101 5 −3
110 6 −2
111 7 −1  
Quando   xR = 4  siamo  arrivati  al  punto  in  cui   xR  non  è  più  minore  di  C/2  ma  al  più  è   xR ≥ C 2  
per  cui   x = xR − C = −4 .  
Si  nota  che  in  questo  caso  l’intervallo  è  asimmetrico,  e  la  scelta  che  si  è  fatta  è  stata  quella  di  
rappresentare  il  -­‐4  (e  quindi  di  non  rappresentare  il  4).  Con  questa  scelta  si  vede  che  tutte  le  
stringhe   che   rappresentano   numeri   negativi   iniziano   con   1.   In   questo   modo   possiamo  
riconoscere  quando  un  numero  è  negativo  o  non  negativo  (non  si  può  dire  che  un  numero  è  
positivo  perché  c’è  lo  zero).  
 
 
 
 
 
 
 
COMPLEMENTO  AD  1  
3
  C = 2 − 1 = 7 → C 2 = 3,5  
X xR x
000 0 0
001 1 1
010 2 2
011 3 3
100 4 −3
101 5 −2
110 6 −1
111 7 0  
Questa   volta   ci   sono   due   rappresentazioni   diverse   dello   zero,   in   un   certo   senso   è   come   se  
avessimo  uno  0  negativo  e  uno  0  positivo.  In  tal  modo  l’unica  cosa  che  possiamo  dire  è  se  un  
numero  è  non  negativo  o  non  positivo.  
Per  evitare  di  fare  questi  discorsi  molto  spesso  si  definisce  una  funzione  segno:  
⎧1 se x ≤ 0 cioè se il numero è non positivo
sign ( x ) = ⎨
⎩0 se x ≥ 0 cioè se il numero è non negativo  
che   porta   a   dire   che   in   corrispondenza   dello   0   ci   sono   due   valori   possibili   che   la   funzione   può  
assumere,  il  che  non  ha  molto  senso  ma  a  volte  può  essere  utile.  
 
Ovviamente   se   usiamo   l’una   o   l’altra   rappresentazione   (complemento   a   1   o   a   2)   avremo  
algoritmi  differenti.  
Soffermiamoci   nel   dominio   delle   rappresentazioni   xR  in   binario,   cioè   stiamo   parlando   di  
sistemi  per  cui   r = 2 .  Ci  manteniamo  nelle  due  forme  di  rappresentazione  che  abbiamo  visto  
cioè  complemento  all’intervallo  (in  questo  caso  complemento  a  2   → C 2 )  e  complemento  alla  
radice  (complemento  ad  1   → C1 ),  per  cui:  
C 2 ⇒ C = 2n
C1 ⇒ C = 2n − 1  
Dove   n   è   il   numero   di   cifre   con   cui   intendiamo   rappresentare   il   numero   intero   xR  in   una  
corrispondenza  uno  a  uno  con  quella  che  si  chiama  rappresentazione  in  binario  naturale.  
In   base   a   questa   definizione   vediamo   come   si   fa   a   capire   il   segno   di   x   a   partire   dalla   sua  
rappresentazione   xR .  
Sappiamo  che:  
⎧ C
⎪⎪ xR se xR <
2
x = ⎨
⎪ x − C se x > C
⎪⎩ R R
2  
Ricordiamoci  che  nel  caso  di  rappresentazione  in  binario  naturale  abbiamo  a  disposizione  n  
cifre   che   chiamiamo   X n−1 , X n−2 ,K , X 0 ,   per   cui   il   peso   di   ogni   cifra   è   tale   per   cui   queste   cifre  
siano  uguali  a  K , 4, 2,1  ad  esempio  il  peso  della  cifra   X n−1  è  esattamente  pari  a   2 .  
n−1

n −1
Nella   rappresentazione   in   complemento   a   2   C/2   è   esattamente   pari   a   C 2 = 2 ,   quindi  
n −1
possiamo   dire   che   xR  rappresenta   un   numero   positivo   tutte   la   volte   che   xR < 2 .   E’   ovvio  
allora  che  tutti  i  numeri  interi  minori  di   2  hanno   X n −1 = 0 .  
n−1

⎧ C
⎪⎪ xR se xR < ≡ X n −1 = 0
2
x = ⎨
⎪ x − C se x > C ≡ X = 1
⎪⎩ R R
2
n −1
 
x
Il  numero   R  si  interpreta  secondo  la  regola:  
n−2
xR = X n −1 ⋅ 2n −1 + ∑ 2i ⋅ X i
i =0  
dove  i  termini  sono  stati  separati  per  distinguere  il  bit  più  significativo.  
n −1
Vediamo  come  sono  fatte  le  codifiche  degli   xR < 2 :  
n−1
Sappiamo  che  la  somma  a  meno  della  cifra  più  significativa  fa  al  massimo   2 − 1,  per  cui:  
(
xR = X n−1 ⋅ 2n−1 + 2n−1 − 1 )  
n −1
Quindi   i   numeri   xR < 2  sono   tutti   e   solo   quelli   che   hanno   X n −1 = 0  perché   altrimenti   si  
avrebbe  la  quantità  tra  parentesi  più  una  quantità  positiva.  Il  fatto  che   X n −1 = 0  significa  che  
abbiamo   a   che   fare   con   numeri   positivi   o   anche   con   lo   zero.   Per   tornare   indietro   dobbiamo  
usare  la  relazione:  
n−2
n −1
X n −1 = 0 ⇒ x = xR = 0 ⋅ 2 + ∑ 2i ⋅ X i
 
i =0
n −1
x
Se   R ≥ 2  stiamo   rappresentando   numeri   negativi.   Sappiamo   che   la   somma   di   tutti   i   bit   a  
n−1
parte   l’n-­‐1-­‐esimo   può   arrivare   al   massimo   a   2 − 1,   quindi   l’unica   possibilità   di   avere   numeri  
maggiori  o  uguali  a   2  è  quella  che   X n −1 = 1.  In  tal  caso  la  regola  è:  
n−1

n−2 n−2 n−2


X n −1 = 1 ⇒ x = xR − C = 1⋅ 2n −1 + ∑ 2i ⋅ X i − 2n = 2n −1 − 2n −1+1 + ∑ 2i ⋅ X i = − 2n −1 + ∑ 2i ⋅ X i
i =0 i =0 i =0  
Per  cui  in  generale  per  passare  dalla  rappresentazione  al  numero  si  può  scrivere:  
n−2
x = − X n −1 ⋅ 2n −1 + ∑ 2i ⋅ X i
i =0  
Per  fare  un  esempio:  
(
0101 → −0 ⋅ 23 + 1⋅ 22 + 0 ⋅ 21 + 1⋅ 20 = 5
  )
Mentre:  
(
1101 → −1⋅ 23 + 1⋅ 22 + 0 ⋅ 21 + 1⋅ 20 = −8 + 5 = −3 )  
Così   abbiamo   ricavato   una   regola   d’interpretazione   che   deriva   dall’aver   scelto   di   contemplare  
tra  i  casi  negativi  anche  quello  per  cui   xR = C 2 .  
Questa   vale   per   quanto   riguarda   il   complemento   a   2.   Troviamo   quella   per   il   caso   di  
complemento  ad  1.  
n −1
In  questo  caso   C = 2 − 1  da  cui  segue  che  C/2  non  è  un  intero,   C 2 = 2 − 0,5 .  
n

Come  abbiamo  fatto  precedentemente  passiamo  dalla  rappresentazione   xR  a  quella  in  binario  
naturale   X n−1 , X n−2 ,K , X1 , X 0  e  cerchiamo  l’algoritmo  per  passare  ad   x .  
Sappiamo  che   x = xR  in  tutti  i  casi  per  cui   xR < C 2 ,  allora  a  maggior  ragione  rispetto  a  prima  
se   X n −1 = 1  il  numero  sarebbe  maggiore  di   2  e  quindi  non  cadremmo  in  quella  condizione.  
n−1

Dobbiamo   però   prestare   attenzione   nel   controllare   il   fatto   che   l’insieme   delle   cifre   fino  
all’indice   n-­‐2   possano   non   far   valere   la   condizione   xR < C 2 ;   per   cui   se   vogliamo   usare   questa  
cifra   come   discriminante   come   prima   dobbiamo   vedere   se   è   condizione   necessaria   e  
sufficiente  affinché  si  possa  fare  questa  discriminazione.  
n−1
La   somma   delle   cifre   a   meno   di   quella   significativa   è   al   massimo   2 − 1  che   fortunatamente   è  
2n−1 − 1 < 2n−1 − 0,5 .  
Quindi  se  
n−2
X n −1 = 0 ⇒ x = xR = 0 ⋅ 2n −1 + ∑ 2i ⋅ X i
i =0  
E   siamo   certi   di   ricadere   in   questo   caso.   Ci   stiamo   un   po’   più   stretti   di   prima   perché   prima  
avevamo   un   margine   di   1,   ora   abbiamo   un   margine   di   ½.   Quindi   di   nuovo   il   valore   assunto   da  
X n−1  discrimina  se  siamo  in  una  condizione  o  nell’altra.  
Nell’alto  caso  si  procede  allo  stesso  modo:  
n−2 n−2 n−2
( )
X n −1 = 1 ⇒ x = xR − C = 1⋅ 2n −1 + ∑ 2i ⋅ X i − 2n − 1 = 2n −1 − 2n −1+1 + 1 + ∑ 2i ⋅ X i = − 2n −1 + 1 + ∑ 2i ⋅ X i
i =0 i =0 i =0
 
Come  prima  possiamo  riunire  le  due  cose  semplicemente  scrivendo:  
n−2
( )
x = − X n −1 2n −1 − 1 + ∑ 2i ⋅ X i
i =0  
Un  esempio:  
( ) (
1101 → −1 23 −1 + 1⋅ 22 + 0 ⋅ 21 + 1⋅ 20 = −7 + 5 = −2 )  
 
Adesso  vogliamo  vedere  se  c’è  un  modo  per  eseguire  operazioni  sui  numeri  relativi  facendo  le  
operazioni  sui  numeri  interi  naturali  (e  quindi  tutti  positivi).  
Siamo  nel  dominio  di   xR , yR ,  che  sono  numeri  naturali  (non  stanno  rappresentando  niente).  Se  
abbiamo  a  disposizione  n  cifre,  la  somma  
zR = xR + yR  
in  generale  non  è  rappresentabile  su  n  cifre,  perché  siccome  
X R ≤ 2n − 1
n
YR ≤ 2 − 1
( ) (
⇒ Z R ≤ 2n − 1 + 2n − 1 = 2 n + 2 n − 2 ) ( )
 
Questo  vale  un  po’  per  qualunque  radice,  infatti:  
X R ≤ r n −1
YR ≤ r n − 1
⇒ ZR ≤ r n + r n − 2 ( )
 
Cioè  nella  rappresentazione  di   z R  in  quella  base  il  numero  deve  essere  composto  dalla  somma  
(r
di  
n
−2 )
,   che   per   i   fatti   suoi   è   rappresentabile   su   n   cifre,   più   eventualmente   un   valore   r ,  
n

per  cui  tutta  la  somma  può  essere  rappresentabile  su  n+1  cifre  in  generale.  Attenzione  che  la  
n
n+1-­‐esima   cifra,   cioè   quella   che   corrisponde   ad   r ,   se   c’è   è   1,   perché   rappresenta   il   riporto  
della  somma  delle  cifre  più  significative.  
Se  scriviamo  allora  la  somma  in  questo  modo:  
zR = xR + yR + Cin  
dove   Cin  è  un  numero  che  vale  1  o  0,  a  questo  punto  la  somma  sarà  ancora  tale  da  rispettare  la  
condizione:  
(
Z R ≤ r n + r n − 2 + Cin )  
Vale   ancora   il   discorso   che,   siccome   (
r − 2 + Cin n
)
 al   più   vale  
r −1 ( n
)
,   metterci   Cin  o   non  
metterlo   non   cambia   dal   punto   di   vista   del   numero   di   cifre,   tanto   al   più   questa   cifra   vale   1.  
Quello   che   può   succedere   è   che   in   sistemi   diversi   Cin  possa   essere   più   di   1   e   che   per  
rappresentarlo   in   binario   invece   di   una   cifra   me   ne   servono   2.   Quindi,   mantenendoci   nella  
condizione   che   la   somma   continui   a   stare   sul   numero   di   cifre   precedenti   più   una,   ci   sta   non  
solo  la  somma  degli  interi   xR  e   y R  rappresentabili  su  n  cifre,  ma  ci  sta  anche  la  possibilità  di  
aggiungere  o  levare  un  1.  
Immaginiamo   di   avere   un   dispositivo   a   cui   arrivano   due   ingressi   su   n   cifre,   x  e   y ,   e   gli   arriva  
anche  quella  cifra  unica   Cin  che  vuol  dire  o  0  o  1.  Dato  che  alla  fine  i  registri  sono  sempre  su  
un  numero  fissato  di  bit  (ad  esempio  su  8  bit),  immaginiamo  che  il  dispositivo  faccia  la  scelta  
di  mantenersi  su  n  cifre  nel  risultato,  e  oltre  a   z  faccia  uscire  un  altro  bit  che  chiamiamo   Cout .  
Graficamente:  
 
x y
n n

Cout Cin
ADD

z
n

 
 
In  termini  di  rappresentazione  il  dispositivo  che  fa  la  somma  è  un  oggetto  che  tira  fuori  
(Cout , Z ) = ADD ( X , Y , Cin )  
E   cioè   a   partire   dalle   rappresentazioni   in   binario   naturale   calcola   la   rappresentazione   di   un  
numero  intero  che  costituisce  la  somma  immaginando  che  la  rappresentazione  sia  fatta  da   Z ,  
sempre   su   n   bit,   e   da   un’ultima   cifra   che   chiamo   Cout .   Cioè   partendo   dalle   cifre  
X n−1 , X n−2 ,K , X1 , X 0 ,   Yn−1 , Yn−2 ,K , Y1 , Y0  e   Cin  (inteso  come  cifra),  se  faccio  la  somma  il  risultato  

si   può   rappresentare   con   una   stringa   di   cifre   Z n , Z n−1 , Z n−2 ,K , Z1 , Z0 ,   che   è   la   rappresentazione  
corretta  del  risultato  di  tutte  le  possibili  somme  che  posso  fare.  A  questo  punto  tutta  la  parte  
Z n−1 , Z n−2 ,K , Z1 , Z0  la   chiamo   Z  e   rimane   su   n   bit,   la   cifra   in   più   ( Z n )   la   chiamo   Cout .   Cioè   il  
dispositivo  funziona  in  questo  modo:  fa  la  somma  di  due  numeri  in  binario  naturale  attraverso  
la  loro  rappresentazione  in  stringhe  di  bit  in  binario  naturale,  se  si  somma  il  bit   Cin  il  risultato  
sarà  composto  da  un  numero   Z  che  in  questa  situazione  è  la  rappresentazione  di  un  numero  
intero  naturale   z R  che  evidentemente  non  è  altro  che  il  risultato  dell’operazione:  
zR = ( xR + yR + Cin ) mod 2n
 
C
e  un  altro  bit  che  chiamo   out  che  è:  
⎧1 se xR + yR + Cin ≥ 2n
Cout = ⎨
⎩0 altrimenti  
Le   stringhe   di   bit   sono   tali   che   interpretate   in   maniera   opportuna   obbediscono   a   queste  
relazioni,  cioè  se  torniamo  alla  rappresentazione  e  mettiamo  insieme   Cout , Z ,  queste  danno  il  
risultato  corretto  della  somma.  
Sfruttando   un   oggetto   che   lavora   in   questo   modo   riusciamo   a   fare   la   somma   tra   numeri   interi  
naturali  che  rappresentano  numeri  relativi  in  complemento  ad  1  o  in  complemento  a  2,  e  con  
qualche  cambiamento  riusciamo  anche  a  fare  la  differenza;  e  siccome  la  somma  e  la  differenza  
sono  alla  base  di  altre  operazioni,  vedremo  che  possiamo  sfruttare  questo  oggetto  ad  esempio  
per  fare  le  operazioni  di  moltiplicazione  e  divisione.  
   
MOLTIPLICAZIONE  
 
Consideriamo   sempre   X   e   Y   ristretti   ad   un   intervallo   di   ampiezza   C.   Per   passare   da   interi  
relativi  ad  interi  naturali,  andiamo  ad  esplicitare  XR  e  YR  .    
 
! !
− ! ≤  𝑋   ≤   !             𝑋! = 𝑋𝑚𝑜𝑑𝐶  
 
! !
− ! ≤  𝑌   ≤   !             𝑌! = 𝑌𝑚𝑜𝑑𝐶  
 
Introduciamo   una   nuova   notazione   utilizzata   per   la   rappresentazione   di   un   numero:  
chiameremo  la  rappresentazione  di  un  numero,  cioè  quello  che  era  X! ,  come  (X)Rn  ,  dove:  
 
X  è  il  valore  che  vogliamo  rappresentare  
R  è  per  ricordarci  che  stiamo  parlando  della  funzione  di  rappresentazione  
n  indica  il  numero  di  cifre  che  stiamo  usando  per  la  rappresentazione  
 
Quindi   data   una   certa   costante   di   complementazione   abbiamo   una   diversa   espressione   del  
numero  di  cifre  su  cui  avviene  la  rappresentazione,  cioè:  
 
C  =  2n     -­‐>   X!  =  (X)Rn     dove  (X)Rn  =  XmodC  
 
C’  =  2m     -­‐>   X!  =  (X)Rm     dove  (X)Rm  =  XmodC’  
 
 
Dato  X  che  si  rappresenta  su  n  cifre,  dato  Y  che  si  rappresenta  su  n  cifre,  ci  chiediamo  se  esiste  
qualche  modo  per  rappresentare  il  prodotto  XY;  se  ciò  è  possibile,  c’è  la  speranza  di  riuscire  a  
ricavare   la   rappresentazione   del   prodotto   a   partire   dalla   rappresentazione   delle  
trasformazioni.  
 
 
X   -­‐>   (X)Rn  
 

Y   -­‐>   (Y)Rn  
 

X*Y   -­‐>   (XY)Rm  


 

 
Vediamo   ora   se   è   possibile   rappresentare   (XY)Rm   e   di   quante   cifre   necessita   la  
rappresentazione  del  prodotto.  
Moltiplicare  un  numero  naturale  X  per  un  numero  naturale  Y,  vuol  dire  sommare  Y  volte  X.  La  
moltiplicazione   tra   numeri   interi   viene   effettuata   con   l’algoritmo   noto   sin   dalle   scuole  
elementari.    
Quindi  se  abbiamo:  
 
0 ≤   𝑋! < 𝐶         0 ≤   𝑋! ≤ 𝐶 − 1  
 
0 ≤   𝑌! < 𝐶         0 ≤   𝑌! ≤ 𝐶 − 1  
 
in   generale   nella   moltiplicazione   tra   due   numeri   interi   il   valore   massimo   e   il   valore   minimo  
della  molitplicazione  𝑋! 𝑌!  sono:  
 
0 ≤   𝑋! 𝑌! ≤ (𝐶 − 1)(𝐶 − 1)  
 
Visto  che  stiamo  parlando  di  rappresentazione  in  binario  naturale,  si  avrà:  
 
C  =  2n    
 
quindi  avremo:    
 
0 ≤   𝑋! 𝑌! ≤  22n  -­‐2n+1  +1  
 
Supponiamo  di  voler  rappresentare  il  prodotto  su  m  cifre.  Ipotizzando  di  scegliere  m=2,  potrò  
rappresentare   i   numeri   da   0   (incluso)   a   2m   escluso,   ovvero   da   0   a   2m-­‐1  incluso.   Quanto   detto   si  
esplicita  nella  seguente  relazione:  
 
   
0 ≤   𝑋! 𝑌! ≤  22n  -­‐2n+1  +1≤  2m  -­‐1    
 
Per   m   =   2n   e   per   n   ≥   1,   la   relazione   sopra   scritta   sarà   sempre   valida.   In   futuro   bisognerà  
verificare  se  la  suddetta  relazione  sarà  verificata  anche  per  valori  di  m  più  piccoli  di  2n.  
Abbiamo   trovato   quindi   il   seguente   risultato   fondamentale:   dati   due   numeri   X   e   Y  
rappresentabili   su   n   cifre,   il   prodotto   XY   sarà   sicuramente   rappresentabile   su   2n   cifre.  
Vedremo   successivamente   se   sarà   possibile   rappresentare   questo   prodotto   su   un   numero  
inferiore  di  cifre.    
Quando   abbiamo   analizzato   la   somma   abbiamo   visto   che   se   prendevamo   X   e   lo  
trasformavamo  in  𝑋! ,  prendevamo  Y  e  lo  trasformavamo  in  𝑌! ,  sapevamo  che,  nel  caso  in  cui  
(X+Y)   fosse   stato   rappresentabile,   per   ottenere   il   valore   corretto   della   somma   bastava   fare  
(𝑋! +𝑌! )modC.  
Ci  occuperemo  ora  di  determinare  la  relazione  esistente  tra  𝑍! ,  inteso  come  rappresentazione  
di  Z,  e  il  prodotto  tra  𝑋!  e  𝑌! .  
Distingueremo   ora   un   certo   numero   di   casi.   Quando   facciamo   il   prodotto   XY,   i   casi   notevoli  
sono:  
 
  Z=XY     -­‐>     (XY)Rm  =  ZR  
 
1)  X  =  0         XY=0  
2)  X>0;  Y>0         XY>0  
3)  X<0;  Y>0         XY<0  
4)  X<0;  Y<0         XY>0  
 
Ciò  è  importante,  in  quanto  rispetto  ad  una  costante  di  complementazione  si  avrà:  
 
1) (XY)Rm  =  0  
2) (XY)Rm  =  xy       (sempre  che  C  sia  sufficientemente  grande)  
3) (XY)R  =  2  +  xy  
m m

4) (XY)Rm  =  xy  
 
Ora,   invece   di   andare   a   fare   i   prodotti   (xy)   e   poi   trasformarli,   consideriamo   direttamente   il  
prodotto  tra  XR  e  YR.  
 
 
1)  XR  YR=0   X=0,  quindi  la  trasformazione  XR=0  e  conseguentemente  anche  il  prodotto  XRYR  
2)  XR  =  X;  YR=  Y   -­‐>   XRYR=XY   in   questo   caso   abbiamo   X>0   e   Y>0,   quindi   XR   e   YR  
sono  proprio  rispettivamente  X  ed  Y  
3)   Se   n   è   il   numero   di   cifre   su   cui   possiamo   rappresentare   sia   X   che   Y,   XR   sarà   dato   dalla  
costante   di   complementazione   (2n)   e   da   X;   YR   sarà   invece   uguale   ad   Y.   Quando   faremo   il  
prodotto  delle  rappresentazioni  otterremo:  
 
XR  =  2n  +  X;   YR  =  Y     -­‐>   XR  YR=  xy  +  Y*2n  
 
4)  XR  =  2n  +  X;     YR  =  2n  +  Y;       -­‐>   XR  YR=  xy  +  22n  +  2n(x+y)  
 
In  generale  abbiamo  visto  che  non  è  vero,  ma  supponiamo  comunque  che:    
 
-­‐  2n-­‐1  ≤ 𝑋 ≤  2n-­‐1  -­‐  1  
 
-­‐  2n-­‐1  ≤ 𝑌 ≤  2n-­‐1  -­‐  1  
 
Supponiamo  di  avere  ad  esempio:  
 
-­‐  3  ≤ 𝑋 ≤  2         -­‐  3≤  𝑌 ≤  2  
 
il  prodotto  XY  sarà  compreso  tra:  
 
-­‐6  ≤ 𝑋𝑌 ≤  9  
 
Tornando  al  nostro  caso,  il  prodotto  XY  sarà  quindi  compreso  tra:  
 
-­‐  2n-­‐1(2n-­‐1  –  1)  ≤ 𝑋𝑌 ≤  22(n-­‐1)  
 
Questo  significa  che  in  generale  il  prodotto  non  è  rappresentabile  su  n  cifre  (lo  si  può  vedere  
numericamente  per  n  ≥  1).  
Tralasciamo  per  il  momento  questa  considerazione,  e  supponiamo  che  X  e  Y  siano  tali  che  il  
prodotto  sia  rappresentabile  su  n  cifre,  ovvero  che  -­‐  2n-­‐1  ≤ 𝑋𝑌 ≤  2n-­‐1  –  1.  
Applicando  l’operazione  di  modulo  alle  4  condizioni  prima  analizzate  avremo:  
 
1) xy  =  0  
2) xy  >  0     In  questo  caso,  l’operazione  di  modulo  restituisce  proprio  XY  (proprietà  
dell’operazione  modulo  
3) xy<0     Poiché   XY   è   negativo,   l’operazione   di   modulo   applicata   ad   un   numero  
negativo  restituirà  2n+XY  
4) XY>0     Siamo  ancora  nel  caso  in  cui  XY  è  positivo,  per  cui,  l’operazioni  di  modulo  
applicata  al  prodotto  XY  restituirà  proprio  XY  
 
Abbiamo  quindi  dimostrato  che,  partendo  da  X  rappresentato  da  XR  e  da  Y  rappresentato  da  
YR,   avendo   Z=XY   e   supponendo   che   Z=XY   sia   rappresentabile   su   n   bit,   come   del   resto   X   e   Y,  
allora  possiamo  ottenere  ZR  come:    
 
ZR  =  (XR  +  YR)mod2n  
 
 
Finora   abbiamo   ipotizzato,   prima   di   effettuare   l’operazione   modulo,   che   il   prodotto   fosse  
rappresentabile.   Vogliamo   ora   estendere   il   ragionamento,   facendo   si   che   il   prodotto   tra   due  
numeri  X  e  Y  rappresentati  su  n  cifre  sia  sempre  correttamente  rappresentabile  su  m  cifre.  Per  
fare  ciò,  dobbiamo  vedere  quanto  vale  m.  
Consideriamo  il  seguente  esempio,  in  binario:  
 
X  =  72     rappresentabile  su  n=  8  bit     C  =  28  
Y  =  -­‐100   rappresentabile  su  n  =  8  bit     C  =  28  
 
Quando  andiamo  a  fare  il  prodotto  (-­‐100)*(72)  otteniamo  -­‐7200,  che  non  è  rappresentabile  su  
n=8  bit,  in  quanto  con  C  =  2  8  possiamo  rappresentare  solamente  valori  compresi  tra  -­‐128  e  
127.    
“-­‐7200”   è   rappresentabile   nell’ipotesi   in   cui   andiamo   ad   utilizzare   come   costante   di  
complementazione  C’=  2m,  dove  m  deve  essere  tale  che:    
 
7200  ≤  2m-­‐1  -­‐1  
 
Dovrò   quindi   ricorrere,   nell’esempio   in   questione,   ad   una   rappresentazione   su   14   bit.   Avrò  
quindi  C’  =  214  .    
Estendo   la   rappresentazione   di   X   e   di   Y   su   14   bit,   replicando   la   cifra   più   significativa.  
Considerando   il   prodotto   XY,   sarà   rappresentato   su   28   bit;   ricordando   che   le   cifre  
“significative”  di  X  e  di  Y  sono  sui  primi  8  bit,  e  che  quindi  le  cifre  “significative”  del  prodotto  
XY  saranno  sui  primi  14  bit,  dovrò  togliere,  nella  rappresentazione  finale,  le  cifre  da  Z14  a  Z27.  
Numericamente   parlando,   raccogliendo   le   cifre   da   Z14   a   Z27,   avrò   una   costante   sommata   a   214,  
mentre   le   cifre   da   Z13   a   Z0   rappresenteranno   una   costante   numerica   inferiore   a   214,   per   cui  
quando  farò  l’operazione  di  modulo  otterrò  soltanto  il  numero  rappresentato  dalle  cifre  meno  
significative  (Z13,  Z12,  …  ,  Z1,  Z0).    
Riepiloghiamo:   supponendo   quindi   di   sapere   che   il   prodotto   tra   due   numeri   X   e   Y   è  
rappresentabile  su  14  bit,  i  passi  da  seguire  per  effettuare  il  prodotto  saranno:  
-­‐ parto  dalla  rappresentazione  di  X  in  binario  (es.:  72)  
-­‐ estendo  la  rappresentazione  su  14  cifre  
-­‐ parto  dalla  rappresentazione  di  Y  in  binario  (es.:  -­‐100)  
-­‐ estendo  la  rappresentazione  su  14  cifre  
-­‐ effettuo  l’operazione  mod  2n  ottenendo  così  il  risultato  
 
Vediamo  la  questione  in  maniera  alternativa;  se  io  voglio  essere  sicuro  di  poter  rappresentare  
(XY),   qualunque   sia   il   valore   di   X   e   qualunque   sia   il   valore   di   Y,   su   quante   cifre   dovrò  
estendere  la  rappresentazione  di  X  e  di  Y  per  essere  sicuri  che  una  volta  fatto  l’algoritmo  di  
moltiplicazione  e  fatto  mod2n  sarò  posso  sicuro  di  ottenere  il  risultato  corretto?  
Ipotizziamo  di  poter  rappresentare  xy  su  m   cifre,  ovvero  stiamo  scegliendo  come  costante  di  
complementazione  C  =  2m.  Si  avrà:  
 
-­‐  2m-­‐1≤ 𝑋𝑌 ≤  2m-­‐1-­‐1  
 
Avevamo   precedentemente   detto   che,   dati   due   numeri   X   e   Y   rappresentati   su   n   cifre,   il  
prodotto  XY  sarà  compreso  tra:  
 
-­‐  2n-­‐1(2n-­‐1  –  1)  ≤ 𝑋𝑌 ≤  22(n-­‐1)  
 
Per  essere  sicuri  che  XY  sia  rappresentabile  su  m  cifre  è  necessario  che:  
 
a) 22(n-­‐1)  ≤  2m-­‐1-­‐1  
b) -­‐  2n-­‐1(2n-­‐1  –  1)  ≥  -­‐  2m-­‐1  
 
Risolviamo  le  disequazioni:  
 
a) 22(n-­‐1)  ≤  2m-­‐1-­‐1  
 
!!
!
≥ 2!!!! + 1       -­‐>     2! ≥ 2!!!! + 2     -­‐>   𝑚 ≥  𝐥𝐨𝐠 𝟐 (𝟐𝟐𝒏!𝟏 + 𝟐)  -­‐>  
 
Andiamo  ad  analizzare  la  quantità  sopra  sottolineata:  
 
!
𝐥𝐨𝐠 𝟐 (𝟐𝟐𝒏!𝟏 + 𝟐)    =  log ! (2!! − 2!! + 2!!!! + 2)  =  log ! (2!! −   2!! (1 − !)  +2)  =  
log ! (2!! −   2!!!!  +2)  
 
Ma  se  𝐥𝐨𝐠 𝟐 (𝟐𝟐𝒏!𝟏 + 𝟐)  <  2n  -­‐>  m  ≥  2n  
 
 
b)  -­‐2n-­‐1(2n-­‐1  –  1)  ≥  -­‐  2m-­‐1  
 
Invertiamo  i  segni  della  disuguaglianza:  
 
2n-­‐1(2n-­‐1  –  1)  ≤  2m-­‐1   -­‐>   2m-­‐1≥22(n-­‐1)-­‐2n-­‐1  
 

Se  scegliamo  m  =  2n  la  relazione  è  verificata  (non  importa  se  la  disuguaglianza  è  verificata  
anche  per  un  m  più  piccolo  in  quanto  la  soluzione  dovrà  soddisfare  entrambe  le  relazioni).  
 
 
Particolarità  
 
Per  essere  sicuri  di  ottenere  la  rappresentazione  corretta,  dalla  rappresentazione  su  n  cifre  di  
X  e  di  Y  dobbiamo  passare  alla  rappresentazione  su  2n  cifre,  ovvero:  
 
X     (X)Rn     -­‐>     (X)R2n  
Y     (Y)R n     -­‐>     (Y)R2n  
 
segue  quindi  che:    
 
(XY)R2n  =  [(X)R2n  +  (Y)R2n]mod22n  
 
 
Esiste   un   unico   caso   in   cui   sono   necessarie   2n   cifre   per   rappresentare   il   prodotto   XY,   in  
quanto  per  i  restanti  casi  le  cifre  possono  benissimo  essere  2n-­‐1;    
Supponiamo   di   voler   rappresentare   X   e   Y   su   n   cifre,   ovvero   di   avere   una   costante   di  
complementazione  C=2n  
! !
− ! ≤  𝑋 < !          
 
! !
− ! ≤  𝑌 < !          
 
Considerando   il   prodotto   XY,   avrò   che   l’estremo   superiore   dell’intervallo   dei   possibili   valori  
! ! !!
rappresentabili  sarà  pari  a  (− !)(− !) = !
,  con  C=2n.  Quindi  si  avrà  che:  
 
!!
2!! 2 1
𝑋𝑌 ≤ ( ) = (   )  
4 2 2
 
Considerando  ora  una  rappresentazione  del  prodotto  XY  su  m  cifre,  che  porta  ad  una  costante  
di  complementazione  𝐶 ! = 2! ,  sono  sicuro  che  il  massimo  valore  rappresentabile  sarà:  
 
2!
𝑋𝑌 < ( )  
2
 
Confrontando   quest’ultimo   caso   con   il   primo,   si   evince   che   con   m=(2n-­‐1)   non   è   possibile  
!!! !!!
rappresentare   un   unico   valore:  𝑋𝑌 = (! ).   Infatti  𝑋𝑌 = (! )  non   può   essere   rappresentato  
da   (2n-­‐1)   cifre   in   complemento   a   2,   quindi,   sostanzialmente,   per   un’unità   non   riusciamo   a  
rappresentare   il   prodotto.   Se   io   so   a   priori   che   i   numeri   che   voglio   rappresentare   sono  
!
simmetrici  e  quindi  rinuncio  al  valore  − !  ,  si  ha:  
 
2! 2!
− − 1 ≤  𝑋 ≤ − 1  
2 2
 
2! 2!
− − 1 ≤  𝑌 ≤ − 1  
2 2
 
!!
Si   dimostra   quindi   che,   rinunciando   a   -­‐ !  ,   si   possono   benissimo   usare,   per   rappresentare   il  
prodotto,  (2n-­‐1)  cifre  piuttosto  che  2n.  Infatti  si  ha:  
 
2! 2! 2! 2!
− −1 − 1 ≤  𝑋𝑌 ≤ −1 − 1  
2 2 2 2
 
2! 2!
− − 1 ≤  𝑋𝑌 ≤ − 1  
2 2
 
Stiamo  ora  cercando  il  valore  di  m  per  cui  siamo  certi  che  sia  verificata  la  condizione  per  cui  
l’intervallo:  
 
!! !! !! !!
[− ! − 1 ! − 1 ; ! − 1 ! − 1 ]  
 
sia  contenuto  all’interno  dell’intervallo:  
 
!! !!
[− !
−1 ; !
− 1 ]  
 
Bisogna  quindi  verificare  che:  
 
2!
− 1 ≥ 2!!!! + 1 − 2!  
2
 
2!
≥ 2!!!! + 2 − 2!  
2
 
2! ≥ 2!!!! + 4 1 − 2!!!  
 
 
𝑛 = 1                 1 − 2!!! = 0  
Per  la  quantità  evidenziata  in  giallo  si  ha:   1 − 2!!!  =    
𝑛 > 1                 1 − 2!!! < 0
 
Per   cui,   ∀n   questa   condizione   è   sicuramente   verificata   se   m=2n-­‐1.   A   noi   non   interessa  
dimostrare  che  m  deve  essere  pari  almeno  a  2n-­‐1,  ma  voglio  solo  dimostrare  che  con  m=2n-­‐1,  
pur  rinunciando  ad  utilizzare  l’estremo  inferiore  nelle  singole  rappresentazioni  di  X  e  di  Y  su  
n  cifre,  posso  rappresentare  il  prodotto  con  m=2n-­‐1  cifre.  
Vediamo   ora   com’è   possibile   rappresentare   numeri   reali.   Quando   facciamo   i   conti   con  
quest’ultimi,  a  mano  o  con  la  calcolatrice,  ci  accontentiamo  di  fare  i  conti  in  virgola  fissa  con  i  
numeri   decimali   o   frazionari,   cioè   quando   dobbiamo   moltiplicare   delle   grandezze,   ad  
esempio:  
 
3.74  V  *  0.283  mA  
 
facciamo  il  conto  e  viene  fuori  1.058mW;  in  fondo,  abbiamo  applicato  l’algoritmo  del  prodotto  
noto  sin  dalle  scuole  elementari.  Ma  se  ci  pensiamo  meglio,  3.74  è  la  rappresentazione  di  una  
quantità  che  è:  
 
!"#
3.74  =  !""  
 
e  0.283  è  la  rappresentazione  di:  
 
!"#
0.283  =  !"""  
 
Quando  facciamo  l’operazione  con  la  calcolatrice  o  a  mano,  in  realtà  quello  che  stiamo  facendo  
è:  
 
374 ∗ 283
 
100000
 
Si  evince  quindi  che  quando  lavoriamo  con  i  numeri  decimali,  in  realtà  non  facciamo  altro  che  
sfruttare   ancora   una   volta   i   numeri   interi;   la   virgola   ci   dice   semplicemente   questo   numero  
intero  per  quanto  deve  essere  diviso.    
Il  suddetto  discorso  vale  anche  per  le  rappresentazioni  in  complemento,  nel  senso  che  quando  
andiamo  a  scrivere:  
 
127.43  
 
in  realtà  quello  che  stiamo  scrivendo,  interpretato  come  numero  intero,  è  semplicemente:  
 
Xn-­‐1   X
              0  

 |            |     𝑋 = !!! ! !
!!! 𝑟 𝑋   -­‐>per  rappresentare                              𝑋 = !!! !!! 𝑟 𝑋 𝑟  
! ! !!

1  2  7  .  4  3             127.43                        -­‐>    
               1  2  7  4  3      dobbiamo  aggiungere                                          1  2  7  .  4  3    
 
 
La   virgola   è   un   qualcosa   in   più   che   serve   a   noi   per   ricordarci   che,   rispetto   alla   regola   di  
rappresentazione  scelta,  si  deve  andare  inoltre  a  moltiplicare  per  𝑟 !! .  
L’operazione   di   somma   tra   numeri   decimali,   richiede   che   le   frazioni   vengano   ridotte   allo  
stesso  denominatore.  Supponiamo  di  voler  effettuare  la  somma  (3.74  +  0.283)  
 
!"# !"#$
3.74  =  !""     -­‐>     3.74  =  !"""  
 
 
in  quanto:  
 
!"#
0.283  =  !"""  
 
Ci   domandiamo   ora   se   è   possibile   utilizzare   la   notazione   in   complemento   per   rappresentare   i  
numeri   razionali.   Considerando   quest’ultimi   come   dei   numeri   interi   divisi   per   una   potenza  
della  radice  nota  a  priori,  le  regole  saranno  le  stesse  di  quelle  utilizzate  per  i  numeri  interi.    
Immaginiamo   di   voler   rappresentare   numeri   che   sono   frazioni   rispetto   ad   un   valore   massimo  
(lavoriamo   in   binario   per   semplicità);   ricordiamo   che   se   un   numero   X   è   rappresentabile   in  
complemento   a   2,   una   volta   scelto   il   numero   di   cifre   utilizzate   per   la   rappresentazione   sarà  
pari  a:  
 
 
2! 2!
− ≤  𝑋 <    
2 2
 
 
Volendo   esprimere   delle   frazioni   rispetto   ad   1,   dobbiamo   dividere   X   per   il   minimo   valore  
rappresentabile   cambiato   di   segno,   ovvero   2n-­‐1   .   Chiameremo   il   nuovo   numero   Xn,   che   sarà  
pari  a:  
 
𝑋
𝑋! = !  
2
2
 
Dobbiamo   fare   ancora   attenzione   al   fatto   che   il   numero   “-­‐1”   è   rappresentabile,   in   quanto  
!!
corrisponde  ad  avere  valore  -­‐    !    ,  mentre  il  numero  “+1”  non  è  rappresentabile;  se  rinunciamo  
al   valore   “-­‐1”,   abbiamo   un   intervallo   di   rappresentazione   simmetrico,   con   numeri   compresi  
nell’intervallo   ]-­‐1   ,   +1[   .   Questa   è   una   situazione   che   ci   consentirà   di   effettuare   alcune  
semplificazioni  importanti.  Infatti,  dati  due  numeri  -­‐1<  X  <1    (1.n)  e  -­‐1<  Y  <1    (1.n)  ,  il  prodotto  
-­‐1<   XY   <   1   (2.2n).   In   queste   ipotesi,   si   vede   che   la   cifra   più   significativa   a   sinistra   della   virgola  
sarà  inutile  in  quanto  assumerà  sempre  un  valore  pari  a  zero,  e  come  tale  potrà  essere  omessa  
nel  conto.  Sarà  quindi  necessaria  una  rappresentazione  del  prodotto  in  notazione  1.2n  .  
Per  rappresentare  i  numeri  razionali,  utilizzeremo  una  notazione  del  tipo:  
 
a.b  
 
dove   a   rappresenta   il   numero   di   cifre   prima   della   virgola,   b   il   numero   di   cifre   dopo   la   virgola.  
Quindi,   se   moltiplichiamo   un   numero   1.15   per   un   numero   1.15,   otterremo   un   numero   2.30.  
Dimentichiamo   per   il   momento   che   abbiamo   a   che   fare   con   numeri   con   la   virgola.   Avremo  
quindi  che  X  e  Y  saranno  rappresentati  su  n=16  bit  (ricordiamo  che  X  e  Y  inizialmente  sono  
rappresentati  su  n=8  bit;  abbiamo  esteso  la  rappresentazione  su  n=16  bit  per  poter  effettuare  
il   prodotto;   il   seguente   problema   è   stato   trattato   precedentemente).   Abbiamo   visto   che,  
rinunciando  all’estremo  inferiore  dell’intervallo  di  rappresentazione,  il  risultato  del  prodotto  
poteva   essere   rappresentato   su   (2n-­‐1)   cifre   piuttosto   che   su   2n   cifre.   Quindi,   tornando  
all’esempio   appena   visto,   il   prodotto   sarà   rappresentato   su   n=31   bit.   In   pratica   sto   andando   a  
scartare  la  cifra  più  significativa.  Ritornando  ai  numeri  razionali,  ovvero  X  (1.15)  e  Y  (1.15),  
effettuando  il  prodotto  XY  (1.30).  Considerando  che  come  ipotesi  iniziale  avevo  che  sia  X  che  Y  
erano   rappresentati   su   n   =   8   bit,   le   cifre   significative   del   prodotto   XY   saranno   quelle   che  
vanno   da   0   a   15,   posso   quindi   passare   ad   una   rappresentazione   1.15.   Sulla   base   di   quanto  
visto   in   precedenza,   possiamo   quindi   effettuare   il   prodotto,   che   nell’esempio   visto   sarà  
rappresentato   su   1.30   cifre,   effettuare   il   troncamento,   ed   ottenere   quindi   un   risultato  
approssimato  su  1.15  cifre.      
I  linguaggi  HDL  ed  in  particolare  il  VHDL  consentono  di  descrivere  tramite  modi  codificati  
sistemi  digitali  anche  complessi  ma  hanno  anche  funzioni  che  li  rendono  a  tutti  gli  effetti  dei  
linguaggi   di   programmazione.   In   generale   servono   per   ottenere   un   modello   comportamentale  
di   un   dato   sistema   per   permetterne   lo   studio   inviando   un   segnale   di   prova   e   vedendo   cosa  
succede   in   uscita.   La   descrizione   fornita   da   un   file   VHDL   è   una   descrizione   puramente  
comportamentale.   Lo   scopo   principale   del   VHDL   è   quindi   quello   di   provare   il   sistema  
progettato   senza   realizzarlo   praticamente.   Per   motivi   di   tempo,   le   simulazioni   verranno  
eseguite   considerando   un   numero   limitato   di   possibili   combinazioni   in   ingresso.   Un   sistema  
digitale   è   composto   da   un   insieme   di   sottosistemi   interconnessi   tra   loro   a   loro   volta   composti  
da   un   insieme   di   elementi   base   (es.:   porta   AND).   Un   programma   VHDL   si   divide  
fondamentalmente  in  tre  parti:  
 
 

 
 
 
In  generale  ad  un’entità  possono  corrispondere  più  architetture.    
Consideriamo  un  inverter  con  in  =  ingresso  e  out  =  uscita  
 

 
 
 
Rappresentiamo  l’ingresso  come  una  tensione.  
 
 
 
 
Le  grandezze  in  ingresso  al  sistema  variano  con  discontinuità,  cioè  ad  un  istante  di  tempo  
assumono   un   valore   e   all’istante   di   tempo   immediatamente   successivo   ne   assumono   un   altro;  
ci  sono  cioè  delle  transizioni  istantanee  in  ingresso.  
In  uscita  le  transizioni  in  risposta  a  questo  sistema  sono  istantanee,  con  la  possibilità  (che  è  
essenziale,   come   vedremo,   in   VHDL)   di   definire   il   ritardo   τ1   e   τ2   con   cui   l’uscita   cambia   in  
risposta  ad  un  cambiamento  dell’ingresso.  
τ1   e   τ2   sono   equivalenti   a   quelli   che   abbiamo   chiamato   tPHL   e   tPLH   in   questa  
schematizzazione  con  fronti  di  salita  e  discesa  a  pendenza  infinita.  
Il  tipo  di  comportamento  che  possiamo  descrivere  si  limita  a  stabilire  come  cambia  l’uscita,  
con  un  ritardo  che  è  specificabile,  in  funzione  di  un  cambiamento  istantaneo  dell’ingresso.  In  
VHDL  anche  quando  si  vuole  indicare  che  la  transizione,  ovvero  il  cambiamento  dell’uscita  in  
risposta  al  cambiamento  dell’ingresso  avviene  in  un  tempo  nullo,  si  deve  tener  presente  che  in  
ogni   caso   viene   inserito   un   ritardo   (piccolo   ma   definito   internamente   al   particolare  
simulatore   VHDL   impiegato)   tra   l’istante   in   cui   cambia   l’ingresso   e   l’istante   in   cui   cambia  
l’uscita  (vedi  Δτmin).    
In   una   situazione   in   cui   le   ampiezze   dei   segnali   sono   o   ‘1’   oppure   ‘0’,   l’unico   parametro   che  
conta  per  definire  l’andamento  dell’ingresso  è  l’istante  in  cui  cambia  il  segnale  di  ingresso.  In  
uscita,  ci  interessa  sapere  dopo  quanto  cambia  il  segnale  di  uscita  in  seguito  al  cambiamento  
del  segnale  di  ingresso.    
Si   deduce   quindi   che   un   sistema   che   descrive   il   comportamento   di   un   circuito   digitale   deve  
quindi  verificare  se  ad  un  certo  istante  di  tempo  c’è  stato  un  cambiamento  dell’ingresso.  Per  
descrivere  l’evoluzione  del  sistema  si  deve  stilare  la  tabella  degli  eventi.  Supponiamo  che  a  t0  
l’ingresso  passi  da  ‘1’  a  ‘0’  ;  di  conseguenza  l’uscita,    a  t0+  t1  passerà  da  ‘0’  a  ‘1’  .  Vediamo  com’è  
fatta  la  tabella  degli  eventi  per  il  seguente  esempio:  
 
 
  t0  
in    1  -­‐>  0  
 
 
 
out    0  -­‐>  1     t0  +  t1  
 
 
 
 
 
 
Affinché  l’uscita  cambi,  ci  dobbiamo  spostare  a  t0  +  t1.  Diremo  che  all’istante  di  tempo  t0  si  è  
verificato   un   evento.   Un   evento   causato   da   un   altro   evento   dista   temporalmente   un   tempo  
finito  dal  primo.  t0  +  t1  >  t0  per  mantenere  la  causalità  degli  eventi.  Se  ci  troviamo  ad  un  istante  
di   tempo   in   cui   non   stanno   accadendo   cambiamenti,   ovvero   se   non   ci   sono   eventi,   è   inutile  
guardare  cosa  c’è  in  ingresso.  Allora  invece  di  scorrere  il  tempo  e  guardare  in  che  istante  c’è  
un   cambiamento,   possiamo   fissare   gli   ingressi   all’inizio   dei   tempi,   in   maniera   tale   che,   ad  
esempio,  sappiamo  che  a  10  ns  ci  sarà  un  cambiamento.  Infatti,  se  questo  è  il  primo  evento,  
sappiamo   che   prima   non   può   succedere   nulla,   e   quindi   non   ci   mettiamo   ad   osservare   se,   ad  
esempio,  a  1  ns  è  successo  qualcosa.  In  parole  povere,  l’evoluzione  del  sistema  si  sposta  dal  
dominio  del  tempo  al  dominio  degli  eventi.  L’analisi  del  sistema  consiste  quindi  nella  ricerca  
del  primo  evento  e  nel  calcolo  di  tutti  i  potenziali  molteplici  eventi  che  sono  conseguenza  di  
quest’ultimo.   Tutti   gli   eventi   verranno   inseriti,   in   maniera   ordinata   secondo   un   tempo  
crescente,  in  una  tabella.  
Consideriamo   come   esempio   la   descrizione   data-­‐flow   di   Fig.   3.1.   Dal   listato   si   ricava  
immediatamente  una  relazione  di  dipendenza  fra  i  segnali.  I  segnali  che  si  trovano  a  sinistra  
dell’operatore   di   assegnazione   <=   dipendono   dai   segnali   che   appaiono   a   secondo   membro.   Ad  
esempio,   dalla   linea   12   del   listato   di   Fig.   6.1   otteniamo   che   y(0)   dipende   da   s0   e   da   a(3),  
mentre  dalla  linea  13  si  ricava  che  y(1)  dipende  da  x0  e  da  c0.  
 

 
Figura  3.1:  Descrizione  VHDL  data  flow  

 
Figura  3.2:  Simulazione  del  listato  di  Fig.  3.1  

 
 
 
 
 
Per  descrivere  un  sistema,  è  necessario  fissare  una  convenzione  per  stabilire  cosa  succede  
all’inizio,  ovvero  bisogna  fissare  lo  stato  iniziale  del  sistema.  
Nel   nostro   caso,   supponiamo   che   inizialmente   sia:   a=”0000”,   s0=c0=x0=’0’.   Al   tempo   t0   il  
valore  del  segnale  a(2)  cambia  e  si  ha:  a(2)=’1’.  Quando  il  valore  di  un  segnale  cambia,  diremo  
che  per  quel  segnale  si  è  verificato  un  evento.  Poichè  s0  e  c0  dipendono  da  a(2),  le  due  linee  
15  e  16  del  listato  vengono  valutate.  Il  risultato  della  valutazione  della  riga  15  è  ‘1’;  poichè  il  
valore   attuale   di   s0   è   pari   a   ‘0’,   il   simulatore   inserirà   in   una   lista   degli   eventi   pendenti,  
l’evento:   s0=’1’.   Questo   evento   viene   previsto   (schedulato)   per   il   tempo:   t=t0+d.   Possiamo  
pensare  a  d  (il  delta  delay)  come  ad  un  ritardo  infinitesimale,  dopo  il  quale  s0  potrà  assumere  
il  valore  ‘1’.  La  valutazione  della  linea  16  fornisce  ‘0’;  dato  che  c0  è  già  pari  a  ‘0’  nessun  evento  
viene  schedulato  per  questo  segnale.  
Il   simulatore   passa   ora   al   tempo   t=t0+d,   quando   si   ha   l’evento:   s0=’1’.   Poichè   x0   ed   y(0)  
dipendono  da  s0  vengono  valutate  le  due  linee  17  e  12.  La  valutazione  della  linea  12  consente  
di  inserire  l’evento  y(0)=’1’  al  tempo  t=t0+2d  nella  lista  degli  eventi;  la  valutazione  delle  linea  
17  non  comporta  la  schedulazione  di  nessun  evento  per  il  segnale  x0.  
Il   simulatore   passa   ora   al   tempo   t0+2d   quando   si   ha   l’evento:   y(0)=’1’.   Poichè   nessun  
segnale  dipende  da  y(0)  il  ciclo  di  simulazione  è  completato.  Il  risultato  complessivo  di  questo  
ciclo  di  simulazione  è  stato  quello  di  portare  s0  ed  y(0)  ad  ‘1’.  
Supponiamo  ora  che  al  tempo  t1  si  abbia  l’evento:  a(0)  =’0’.  Poiché  s0  e  c0  dipendono  da  
a(0)  vengono  valutate  le  linee  15  e  16:  come  risultato  vengono  schedulati  gli  eventi:  s0=’0’  e  
c0=’1’   per   t=t1+d.   Si   passa   così   al   tempo   t=t1+d   e   si   valutano   le   linee   12,13,14   e   17,  
schedulando  gli  eventi  y(0)=’0’  ed  y(1)=’1’  per  t=t1+2d.  Al  tempo  t=t1+2d  si  completa  il  ciclo  
di  simulazione.    
La  Fig.  6.4  mostra  il  susseguirsi  degli  eventi  durante  la  simulazione.  
Un   sistema   di   elaborazione   del   genere   si   chiama   simulatore   ad   eventi   discreti,   cioè   un  
sistema   in   cui   solo   a   certi   istanti   possono   accadere   degli   eventi   dai   quali   scaturiscono   altri  
eventi  ad  istanti  di  tempo  predeterminati.  Il  tempo  serve  solo  come  sistema  per  ordinare  gli  
eventi.    
Un   sistema   hardware   si   può   sempre   rappresentare   in   questi   termini:   riceve   degli   input,  
esegue   delle   operazioni,   produce   degli   output.   In   generale   sarà   costituito   da   uno   o   più  
sottosistemi   rappresentabili   in   questi   termini.   Allora   per   descrivere   un   sottosistema   di  
devono  definire:  
-­‐ la   sua   interfaccia   esterna,   ovvero   gli   ingressi   e   le   uscite   che   rappresentano   le   sue  
relazioni  con  gli  altri  sottosistemi  o  con  l’esterno  
-­‐ il  suo  funzionamento  interno,  ovvero  che  cosa  fa  il  sistema  e/o  come  lo  fa  
In  VHDL  il  modello  di  un  sottosistema  è  detto  design  entity  ed  è  costituito  da  due  parti:  una  
entity  (che  descrive  l’interfaccia)  e  una  architecture  (che  descrive  il  funzionamento).    
Nella  descrizione  hanno  un  ruolo  fondamentale  i  segnali  che  spostano  i  dati  tra  diverse  parti  
del   sistema.   Le   porte   dell’interfaccia   esterna   sono  segnali   particolari   in   quanto   spostano   i   dati  
da  e  verso  l’esterno.    
Un   sistema   descritto   in   VHDL   è   un   sistema   concorrente,   in   quanto   ciascuna   delle   entità,  
ovvero  degli  elementi  di  elaborazione,  funziona  contemporaneamente  a  tutte  le  altre.  Quindi  
diamo  una  descrizione  di  com’è  fatto  il  sistema,  di  come  si  comporta,  ma  non  descriviamo  la  
sua   evoluzione.   Il   risultato   è   che   non   conta   l’ordine   con   cui   descriviamo   il   collegamento   tra  
dispositivi.   Attenzione   dunque   a   non   confondere   ciò   che   si   scrive   in   quanto   descrizione   del  
comportamento  con  ciò  che  succede  nel  tempo  nella  fase  di  simulazione.    
Consideriamo  l’oscillatore  ad  anello.  
 
 
 
Potremmo   pensare   a   quest’oggetto   come   un   unico   dispositivo   che   ha   un   ingresso   x   e  
un’uscita  y.  Gli  ingressi  e  le  uscite  in  VHDL  sono  enti  che  possono  essere  di  diversa  natura.    
 
In  VHDL  esiste  un  tipo  predefinito  che  è  il  tipo  bit,  definito  per  enumerazione,  i  cui  elementi  
sono  i  caratteri  ‘0’  e  ‘1’.  
Se   in   VHDL   vogliamo   dichiarare   che   esiste   un   oggetto   che   ha   un   ingresso   e   un’uscita  
(immaginiamo   di   associare   all’ingresso   e   all’uscita   rispettivamente   un   segnale   d’ingresso   e   un  
segnale  d’uscita)  appartenenti  al  tipo  bit,  dobbiamo  scrivere:  
 
entity  osc_anello  is    
  port  (x:  in  bit;  y:  out  bit),  
end  entity  osc_anello  
 
 

 
 

 
 
Specificare   la   direction   è   fondamentale   nella   descrizione   di   un   sistema   caratterizzato  
dall’interconnessione   di   altre   entità.   Infatti   è   consentito   collegare   insieme   due   ingressi,   ma  
non  è  possibile  fare  la  stessa  cosa  con  le  uscite.    
Passiamo  a  descrivere  il  sistema  (  #  )  .  Esistono  due  possibilità:  o  studiamo  il  sistema  da  un  
punto   di   vista   “esterno”   o   lo   studiamo   da   un   punto   di   vista   “interno”.   Nel   primo   caso   ci  
interessa  solamente  la  descrizione  del  legame  tra  ingresso  e  uscita,  ovvero:  
 
-­‐  se  x  =  0     y  =  0  oppure  y  =  1  ad  intervalli  regolari  
-­‐  se  x  =  1     y  =  1  (l’uscita  rimane  a  1)  
 
Nel   secondo   caso,   vogliamo   analizzare   l’interazione   tra   i   diversi   blocchi.   Un   modello   di  
comportamento  è  quello  che  si  chiama  architettura.    
Abbiamo  visto  che  una  entity  declaration  definisce  l’interfaccia  di  un  modulo  ma  non  dice  né  
sulla   funzionalità   svolta   dal   modulo,   né,   tantomeno,   sul   modo   in   cui   il   modulo   realizza   tale  
funzionalità.   La   funzionalità   di   un   modulo   è   descritta   in   VHDL   mediante   una   architecture  
declaration,  secondo  la  sintazzi  seguente:  
 
architecture  architecture_name  of  entity_name  is  
[declarations]  
begin  
[implementation]  
end  architecture_name;  
 
La   prima   cosa   che   risulta   evidente   è   che   ogni   architecture   è   associata   ad   una   ed   una   sola  
entity.  
Non  è  invece  vero  il  vice  versa:  è  infatti  possibile  specificare  più  architecture  alternative  per  
una   stessa   entity   e   selezionarne   una   specifica   prima   di   procedere   alla   sintesi   oppure   alla  
simulazione.  
L’associazione   di   una   architecture   specifica   ad   una   entity   prende   il   nome   di   configuration  
declaration.  Nei  progetti  di  complessità  media  o  bassa,  l’utilità  di  avere  diverse  architecture  
alternative  per  un  modulo  risulta  di  scarsa  utilità  e  quindi  è  una  possibilità  usata  solo  molto  
raramente.  
La   prima   sezione   di   una   architecture,   opzionale   ed   indicata   nella   sintassi   come   declarations,   è  
costituita  da  un  elenco  di  dichiarazioni.  Le  dichiarazioni  possono  essere  di  quattro  tipi:  
 
1.     Dichiarazioni  di  costanti:  Nomi,  tipi  e  valori  delle  costanti  simboliche  utilizzate  nella  
specifica.  
2.    Dichiarazioni  di  segnali:  Nomi  e  tipi  dei  segnali  che  saranno  usati  nella  specifica  della  
funzionalità  del  modulo.  
3.     Dichiarazioni  di  tipi:  Nomi  e  definizioni  di  tipi  definiti  dall’utente  ed  utilizzati  nella  
specifica.  
4.     Dichiarazioni  di  componenti:  Nomi  ed  interfacce  dei  moduli  utilizzati  nella  specifica  
della  architecture  in  esame.  
 
Senza   entrare   ora   nei   dettagli   delle   varie   tipologie   di   dichiarazioni,   si   tenga   presente   che   le  
dichiarazioni   presenti   nella   sezione   di   una   data   architecture   hanno   visibilità   soltanto   per  
quella   architecture.   Questa   circostanza   può   comportare   alcune   complicazioni  
prevalentemente  nella  definizione  di  tipi  aggiuntivi.  
Tra   le   parole   chiave   begin   ed   end   compare   la   sezione   implementation,   destinata   a   raccogliere  
la   descrizione   della   funzionalità   che   il   modulo   deve   svolgere.   La   funzionalità   di   un   intero  
circuito,   quindi,   si   trova   quindi   distribuita   nelle   architecture   declaration   delle   diverse   entity  
che  costituiscono  i  moduli  del  sistema.  
 
 
Il   fatto   che   le   istruzioni   sono   concorrenti   vuol   dire   che   il   loro   effetto   non   dipende  
dall’ordine  in  cui  sono  scritte.    
Le  istruzioni  dichiarative  servono  a  definire  gli  oggetti,  che  sono  tipicamente  dei  segnali  e  
posso  essere  utilizzati  (scope)  solo  all’interno  della  architecture  description.    
Le  istruzioni  funzionali  contengono  la  logica  della  rete.  

 
 
 
 
Nel  sistema  #  abbiamo  5  oggetti  da  descrivere,  ciascuno  dei  quali  si  comporta  in  maniera  
indipendente   o   concorrente   agli   altri   (concorrente   significa   che   nello   stesso   istante   dobbiamo  
tener  conto  di  quello  che  succede  su  tutti  questi  sistemi).    
Descriveremo  ciascuno  di  questi  oggetti  come  un  process  separato  mettendo  in  relazione  
ognuno   dei   5   blocchi   (ognuno   dei   quali   è   un   process)   attraverso   degli   elementi   di  
interconnessione.    
La  struttura  di  un  process  riproduce  il  modo  in  cui  da  un  ingresso  si  calcolano  le  uscite.  Ai  
process  possiamo  dare  dei  nomi  per  distinguerli  dagli  altri.    
In   VHDL   il   collegamento   tra   oggetti   si   descrive   sulla   base   di   elementi,   di   enti   (che   per   il  
momento   possiamo   far   finta   che   siano   l’equivalente   dei   fili)   che   si   chiamano   segnali   con  
parola  chiave  signal.  Questi  elementi  vengono  specificati  nella  parte  di  istruzioni  dichiarative.  
Scriveremo  quindi:  
 
  signal     A,  B,  C,  D,  E,  F:  bit;  
 
Attenzione  a  non  confondere  i  signal  con  un  altro  elemento  del  linguaggio  VHDL  che  sono  
le  variabili  che  all’interno  di  un  process  si  usano  per  arrivare  più  facilmente  alla  descrizione  di  
un  sistema.  Proprio  per  evitare  questa  confusione,  l’assegnazione  di  variabili  in  VHDL  avviene  
sfruttando  “:=”,    mentre  l’assegnazione  di  segnali  avviene  sfruttando  “<=”.  
Supponiamo  di  avere  le  seguenti  istruzioni:  
 
a  <=  b  
b  <=  a  
 
Le  istruzioni  vengono  viste  una  dopo  l’altra,  ma  in  entrambi  i  casi  stiamo  assumendo  che  
all’istante  di  tempo  in  cui  viene  attivato  il  process,  cioè  è  stata  bloccato,  a  assumerà  il  valore  di  
b  dopo  un  tempo  brevissimo  e  b  assumerà  il  valore  di  a  dopo  un  tempo  brevissimo.  Il  risultato  
finale  sarà  quello  di  aver  programmato  due  “TRANSIZIONI”,  una  per  a  e  una  per  b,  in  istanti  di  
tempo   immediatamente   successivi   a   quello   attuale,   per   cui   i   valori   dei   segnali   si  
scambieranno.  Terminata  l’esecuzione  delle  istruzioni  interne  ad  un  process,  il  suo  stato  viene  
bloccato,  in  attesa  di  un  nuovo  “evento”.  Quindi  le  variabili  all’interno  di  un  process  sono  degli  
eventi   che   assumono   valori   che   tengono   sempre   memoria   di   quello   che   è   successo.   Le  
variaibli  hanno  senso  di  esistere  soltanto  all’interno  di  un  process.    Il  calcolo  che  viene  
effettuato   all’interno   di   un   process   consiste   semplicemente   nella   programmazione   di   una  
nuova  transizione  per  i  segnali.  
All’interno   di   un   Process   si   possono   usare   un   certo   numero   di   statement   o   costrutti  
sequenziali,  l’esempio  più  semplice  è:  
 
Process  
  x:=3  
  y<=3  
  ……  
 
x  è  una  variabile  intera  che  da  ora  in  poi  varrà  3.  y  è  un  segnale  intero  e  con  il  simbolo  “<=”  
di   fatto   si   programma,   ad   un   dato   istante   di   tempo,   la   transizione   che   avverrà   una   volta   finito  
il  process.  
 
Descriviamo   il   comportamento   di   uno   degli   inverter   del   sistema   #,   ad   esempio   Process(  C  ).  
Noi  non  dobbiamo  riprodurre  l’inverter,  ma  dire  che  se  il  segnale  C  assume  determinati  valori  
allora   come   conseguenza   D   ne   assume   altri.   L’inverter   è   una   relazione   che   sussiste   tra   l’uscita  
e  l’ingresso.    
Process   (   C   )   significa   che   tutte   le   volte   che   C   cambia   dobbiamo   guardare   cosa   c’è   scritto   di  
seguito  per  programmare,  prevedere  o  verificare  se  devo  programmare  nella  lista  degli  eventi  
un  evento  che  corrisponde  al  cambiamento  di  D.    

 
 
Process  (  C  )  viene  attivato  in  corrispondenza  di  un  evento  su  C,  solo  se  C  cambia  valore;  in  
pratica  Process  (  C  )  significa  semplicemente  “se  C  cambia”.    
Con   il   Process   stiamo   riproducendo   il   comportamento   di   un   inverter,   specificando   cosa  
succede  al  segnale  d’uscita  in  corrispondenza  al  segnale  d’ingresso.  Process  (  C  )  descrive  però  
solo   una   parte   del   sistema.   Concorrentemente   con   questo   dovremo   scrivere   il   legame   che  
sussiste  tra  D  ed  E  e  così  via,  in  un  ordine  qualunque.    
All’interno  dell’architecture  avremo  i  seguenti  Process,  con  i  relativi  corpi:  
 
Process  (D)  
Process  (X,  B)  
Process  (  C  )  
Process  (E)  
Process  (F)  
Process  (B)  
 
C’è  anche  bisogno  di  un  Process  sensibile  a  B  che  permette  di  fissare  il  valore  di  Y:  
 
Process  (B)  
  Y  <=  B;  
end  Process  
 
Tutte   le   volte   che   cambia   B,   Y   lo   segue   immediatamente.   Attenzione   al   fatto   che,   mentre   in   un  
sistema   elettrico   i   punti   Y   e   B   che   sono   collegati   insieme   cambiano   veramente   nello   stesso  
istante,   in   VHDL   Y   cambia   sempre   e   comunque   in   un   tempo   successivo   a   t   (istante   di  
cambiamento   di   B),   ovvero   l’uscita   Y   seguirà   l’ingresso   B   in   un   tempo   t+Δt;   questo   discorso  
serve  a  mantenere  la  coerenza  tra  segnali.  In  VHDL,  Processi  come  “Process  (B)”  sono  molto  
frequenti,   tant’è   che   in   VHDL   c’è   la   possibilità   che   uno   degli   statement   concorrenti   sia  
semplicemente  la  riga:  
 
Y<=B;                      sono       Process  (B)  
            equivalenti                Y  <=  B;  
end  Process  
   
La   riga   sottolineata   significa   che   tutte   le   volte   che   cambia   B   viene   programma   nella   tabella  
degli  eventi  una  transizione  anche  per  Y.    
Supponiamo  di  avere  un  segnale  s  e  una  variabile  v.  Per  copiare  il  valore  di  v  su  s  dobbiamo  
scrivere:    
 
  v:=s  
 
Visto   che   ci   si   immagina   che   gli   inverter   siano   tutti   uguali   è   un   po’   seccante   ripetere   la  
struttura  del  process  (abbiamo  diversi  process  in  quanto  diversi  sono  i  segnali  coinvolti)  che  
svolgono   sostanzialmente   la   stessa   funzione.   Possiamo   invece   definire   un’entità   inverte   che  
sfrutteremo   all’interno   dell’entità   osc_anello:   descriviamo   il   comportamento   di   un’entità   in  
termini  dell’interconnessione  tra  altre  entità.    
 
 
 
Proviamo  a  descrivere  una  porta  NOR  
 

 
 
Forniremo   una   descrizione   della   porta   NOR   errata   per   riflettere   meglio   sul   concetto   di  
Process.  Una  porta  NOR  da  in  uscita  1  quando  entrambi  gli  ingressi  sono  0.    
 

 
Con   questo   tipo   di   descrizione,   il   comportamento   della   porta   NOR   non   è   completamente  
specificato,   in   quanto   non   abbiamo   specificato   cosa   succede   quando   non   si   verifica   la  
condizione   “A   =   ‘0’   AND   B   =   ‘0’   “.   Volevamo   descrivere   una   porta   NOR   e   invece   abbiamo  
sbagliato,   descrivendo   un   sistema   sequenziale.   Un   sistema   combinatoriale   è   un   sistema   in  
cui  le  uscite  dipendono  dal  valore  degli  ingressi  in  quell’istante.  Un  sistema  sequenziale  è  un  
sistema  in  cui  il  valore  delle  uscite  dipende  da  tutta  la  storia  precedente  degli  ingressi,  ovvero  
sia   dagli   ingressi   all’istante   attuale   che   dagli   ingressi   ad   esso   antecedenti.   La   memoria  
dell’evoluzione   degli   ingressi   agli   stati   precedenti   può   essere   immagazzinata   in   delle   variabili  
interne   che   si   chiamano   variabili   di   stato   per   cui   molto   spesso   un   sistema   sequenziale   è   un  
sistema   in   cui   le   uscite   dipendono   dal   valore   degli   ingressi   e   dalle   variabili   di   stato   interne.   In  
questa   situazione,   stessi   ingressi   in   condizioni   diverse   danno   uscite   differenti   (macchina   a  
stati  finiti).  
Tornando  al  nostro,  perché  stiamo  sbagliando?  Consideriamo  la  seguente  situazione:  
 
t1       A  =  0       B  =  0     -­‐>     y  =  1  
t2  >  t1       A  =  1     B  =  0     -­‐>     y  =  1  
 
All’istante   di   tempo   t1,   per   com’è   definita   la   porta   NOR,   l’uscita   assumerà   il   valore   1.  
All’istante  di  tempo  t2,  in   funzione   del   valore   degli   ingressi,   la   variabile   y   dovrebbe  assumere  
il   valore   0.   Invece,   poiché   non   abbiamo   specificato   cosa   succede   nei   casi   in   cui   A   !=   0   e   B   !=   0,  
avremo   un   valore   dell’uscita   y   errato.   Abbiamo   infatti   descritto   un   sistema   sequenziale   in  
quanto   siamo   in   una   condizione   in   cui   l’uscita   dipende   da   quella   che   è   stata   la   storia  
precedente  degli  ingressi.  Quando  vogliamo  descrivere  un  sistema  sequenziale  in  VHDL,  se  un  
ingresso   non   fa   cambiare   l’uscita,   non   la   specifichiamo.   Quando   però   dobbiamo   descrivere   un  
sistema   combinatoriale,   proprio   per   definizione,   dal   momento   che   l’uscita   deve   dipendere   dal  
valore   degli   ingressi   in   quell’istante,   deve   essere   possibile   fissare   l’uscita   in   base   al   valore  
degli   ingressi,   dunque   è   indispensabile   che   venga   specificato   esattamente   e   completamente  
qual’è  l’uscita  per  ogni  ingresso.    
 

 
Consideriamo  una  porta  NOR  in  CMOS:    
 
 
 
 
In  una  NOR  CMOS  esiste  una  sola  condizione  in  cui  si  osserva  la  transizione  dell’uscita  dal  
livello   basso   al   livello   alto   e   cioè   quando   da   qualunque   altra   condizione   si   passi   ad   una  
condizione  in  cui  sia  A  sia  B  sono  0.  Il  condensatore  si  carica  in  questo  caso  attraverso  i  due  
PMOS   che   hanno   i   canali   in   serie   e   si   viene   a   stimare,   facendo   in   conti,   un   unico   tempo   di  
propagazione  basso  alto.    
Quando   invece   osserviamo   la   transizione   alto-­‐basso,   le   situazioni   possono   essere   diverse  
perché  la  transizione  alto  basso  potrebbe  essere  dovuta  al  fatto  che:  
-­‐ B   è   rimasto   a   0   e   A   è   diventato   1,   cosicché   il   condensatore   si   scarica   solo   attraverso   un  
transistore  
-­‐ A   e   B   sono   entrambi   diventati   1   cosicché   osserviamo   un   tempo   di   scarica   un   po’   più  
veloce  
Quindi  se  vogliamo  differenziare  i  tempi  di  salita  dai  tempi  di  discesa,  pensando  davvero  a  
come  sono  fatti  i  sistemi,  bisognerebbe  trovare  un  modo  per  specificare  cosa  dovrà  succedere  
non  solo  in  base  ai  valori  attuali,  ma  anche  a  quelli  precedenti.    
Volendo   esaminare,   verificare   da   un   punto   di   vista   sintattico   e   simulare   un   file   VHDL,  
possiamo   fare   riferimento   al   programma   GHDL,   un   simulatore   con   un’interfaccia   stile   linea   di  
comando   che   produce   un   file   degli   eventi.   Per   visualizzare   le   forme   d’onda   useremo  
GTKWAVE.  
In  VHDL  gli  eventi  sono  sempre  causati  da  altri  eventi.  C’è  il  problema  di  chi  genera  il  primo  
evento.   In   VHDL   c’è   una   convenzione   riguardo   lo   stato   iniziale:   nella   fase   di   simulazione   si  
stabilisce   che   tutti   i   process,   oltre   ad   essere   attivati   quando   cambia   il   segnale   al   quale   sono  
sensibili,   vengono   anche   attivati   contemporaneamente   al   tempo   0   indipendentemente   dalla  
sensitivity  list.  Tutti  i  processi  verranno  dunque  attivati  almeno  una  volta.    
 
 
 
 
 
 
 
 
 
 
Consideriamo  un  inverter:  
 
 
 

 
 
Abbiamo  visto  la  volta  scorsa  che  in  VHDL  si  definiscono  le  entità,  le  linee  di  ingresso  e  di  
uscita,   dopodiché,   facendo   uso   di   uno   o   più   process,   si   scrive   all’interno   dell’architettura   un  
modello  comportamentale  dei  nostri  dispositivi.  Lo  scopo  del  processo  è  quello  di  elaborare  e  
mettere   nella   coda   degli   eventi   del   simulatore   eventuali   transizioni   delle   porte   di   uscita   che  
sono  conseguenza  della  transizione  sulla  porta  di  ingresso.  
In   VHDL   le   transizioni   sono   tutte   istantanee.   Ad   una   transizione   in   ingresso   non  
corrisponde  nello  stesso  istante  la  transizione  in  uscita,  ma  c’è  un  ritardo.  Attenzione  al  fatto  
che  in  VHDL  tale  ritardo  viene  specificato  come  una  proprietà  dell’entità  (cioè  dell’inverter)  
per   come   l’abbiamo   scritto   ma   sappiamo   che   ciò   non   corrisponde   alla   realtà   in   quanto,   in  
generale,  il  ritardo  di  propagazione  di  una  porta  dipende  da  quante  altre  porte  sono  collegate.  
Questo  non  è  facilmente  includibile  in  un  file  VHDL.  
Negli  spazi  che  abbiamo  lasciato  (prima  di  after  nel  codice  sopra)  andrebbe  posta  la  parola  
chiave  INERTIAL  che  è  il  modello  di  ritardo  di  default  in  VHDL.  
In  VHDL  esistono  due  modelli  di  ritardo.  Uno  è  il  modello  INERTIAL  ed  è  quello  che  viene  
preso  di  default  se  non  compare  la  parola  chiave  ,  l’altro  si  chiama  TRANSPORT.  Esistono  due  
modelli  diversi  perché  sono  previsti  due  comportamenti  distinti  in  riferimento  ad  un  ritardo  
di  tipo  inerziale  ed  un  ritardo  di  tipo  transport.  
Nota:   in   VHDL   gli   identificatori   scritti   in   maiuscolo   o   in   minuscolo   sono   la   stessa   cosa.  
(Attenzione:  dire  che  il  condensatore  è  inerziale  non  significa  niente  di  per  sé.)  
La   parola   inerziale   si   riferisce   al   tentativo   di   modellare   un   sistema   logico   immaginando  
quello  che  mostriamo  nel  seguente  esempio.  
 
 
 
 
 
 
 
 
 
Consideriamo  il  seguente  esempio  nel  caso  di  un  inverter  CMOS:  
 
 
 
C  rappresenta  il  carico  capacitivo  dovuto  alla  presenza  di  altri  ingressi.  
In  un  sistema  del  genere,  quando  proviamo  a  studiare  quello  che  succede  in  funzione  del  
tempo,  si  possono  presentare  situazioni  come  quella  descritta  nella  seguente  figura:  
 

 
 
Ci   aspettiamo   che   la   tensione   di   uscita   cambi   con   un   certo   andamento,   con   un   ritardo  
rispetto  alla  tensione  di  ingresso.  
È  chiaro  che  il  modello  di  ritardo  in  VHDL  è  una  cosa  semplificata.  Se  dallo  studio  di  questo  
sistema   (secondo   grafico)   ad   esempio   con   SPICE   vogliamo   ricavare   un   modellino   in   VHDL,  
possiamo  ad  esempio  prendere  come  tempo  di  ritardo  tipico  per  il  nostro  modellino  il  tempo  
di   propagazione,   cioè   il   tempo   impiegato   perché   l’uscita   VOUT   arrivi   a   metà   strada   tra   la  
tensione  nominale  alta  e  la  tensione  nominale  bassa.  È  come  se  mi  immaginassi  che  nel  mio  
modellino  la  tensione  d’uscita  V*OUT  (che  è  la  rappresentazione  di  quello  che  succede  in  VHDL)  
abbia  l’andamento  in  figura,  cioè  quando  arrivo  a  metà  strada  è  il  momento  in  cui  decido  di  
cambiare  l’uscita.  
Nel   caso   cerchiato,   l’uscita   non   avrebbe   il   tempo   di   arrivare   a   metà   strada   prima   che  
l’ingresso   cambi   ancora   e   riporti   l’uscita   al   livello   alto.   Questa   situazione   si   riferisce   al   fatto  
che,   visto   che   la   corrente   erogata   sia   in   fase   di   carico   che   in   fase   di   scarico   è   limitata,   il  
condensatore   inerziale,   con   una   corrente   finita,   impiega   un   tempo   finito   per   avere   una  
variazione   di   tensione   alla   sua   uscita.   In   una   situazione   come   quella   descritta,   i   sistemi   a   valle  
interpretano   questo   comportamento   come   “l’uscita   è   rimasta   ad   1”.   Il   modello   inerziale   tende  
a   riprodurre   questo   comportamento   (ovvero   l’evento   cerchiato),   nel   senso   che   il   simulatore  
VHDL   (ricordiamo   che   il   VHDL   serve   per   descrivere   il   comportamento   secondo   certe   regole  
che  sono  evidenziate  dal  simulatore,  il  quale  si  comporta  secondo  queste  regole)  fa  in  modo  
che,  ogni  volta  che  viene  attivato  il  processo,  il  risultato  dell’attivazione  di  tale  processo  è  la  
programmazione   di   un   evento   (cioè   l’inserimento   di   un   evento   in   una   coda   di   eventi),   cioè  
quando   ‘a’   cambia   (supponiamo   che   vada   da   0   a   1),   viene   messo   nella   coda   degli   eventi   (che   è  
ciò   che   viene   elaborato   dal   simulatore)   l’evento   “b   diventa   0”   10   ns   a   partire   dal   tempo   in   cui  
il  processo  è  stato  attivato.  
Abbiamo  la  seguente  scala  dei  tempi:  
 

 
 
Osserviamo  l’istante  in  cui  viene  attivato  il  process.  Esso  viene  attivato  perché  ‘a’  passa  da  
0   a   1.   Questo   cambiamento   fa   attivare   tutti   i   process   che   sono   sensibili   al   segnale   collegato   ad  
‘a’.  Dopodichè  il  tempo  viene  congelato  e  si  calcola  ciò  che  deve  succedere  nel  futuro.  A  10  ns  
di  distanza  viene  inserito  l’evento  “b  passa  da  1  a  0”.  Eventualmente  in  questa  coda  possono  
essere   inseriti   altri   eventi   che   dipendono   dal   fatto   che   ‘a’   è   cambiato,   o   che   comunque   il  
segnale  che  ha  causato  il  cambiamento  di  ‘a’  è  cambiato.  Adesso  si  va  alla  ricerca  del  prossimo  
evento.  Supponiamo  che  prima  ancora  che  ‘b’  possa  cambiare,  ci  sia  l’evento  “a  passa  da  1  a  0”  
a  3  ns.  Quando  ‘a’  passa  da  1  a  0  il  processo  viene  di  nuovo  attivato  e  viene  programmato  un  
evento  a  15  ns  dall’istante  in  cui  ‘a’  cambia  (cioè  a  18  ns).  Tale  evento  è  “b  passa  da  0  a  1”.  
Vediamo   di   seguito   quello   che   sta   succedendo   indicando   con   livello   logico   0   un   segnale  
basso  e  con  livello  logico  1  un  segnale  alto.  
 
 
 
 
 
 
 
‘a’  passa  da  0  ad  1  ad  un  istante  che  prendiamo  come  riferimento.  Dopo  3  ns  ‘a’  passa  da  1  a  
0.  Come  risultato  di  questa  operazione  abbiamo  che,  quando  stavo  elaborando  la  transizione  
di  ‘a’  da  0  ad  1,  veniva  programmata  la  transizione  di  ‘b’  da  1  a  0  dopo  10  ns.  Indichiamo  con  
una   freccia   verso   il   basso   tale   transizione.   Prima   di   arrivare   a   questa   transizione,   però,   ‘a’  
passa  da  1  a  0,  quindi  verrebbe  programmato  un  evento  a  15  ns  di  distanza.  Tale  evento  è  “b  
passa   da   0   a   1”.   Cioè   viene   programmato   un   nuovo   evento   per   ‘b’   prima   ancora   di   essere  
giunti   all’istante   di   tempo   in   cui   ‘b’   cambiava   per   colpa   di   un   evento   precedente.   Secondo   il  
modello  INERTIAL,  se  viene  programmata  una  nuova  transizione  per  un  segnale  prima  ancora  
che  venga  eseguita  la  transizione  che  era  stata  programmata  in  un  tempo  precedente,  le  due  
transizioni  in  figura  vengono  cancellate.  Quindi,  se  viene  programmata  una  transizione  in  un  
tempo   futuro   rispetto   ad   una   transizione   che   non   è   ancora   stata   eseguita,   le   due   transizioni  
vengono   cancellate.   Si   riproduce   sostanzialmente   l’effetto   in   cui   l’impulso   era   troppo   breve  
perché  con  i  tempi  di  ritardo  che  avevamo  previsto  l’uscita  potesse  cambiare.  
Quella   dell’inverter   che   deve   caricare   una   capacità   non   è   l’unica   origine   di   ritardo   di   un  
sistema   digitale.   Il   problema   esiste   nella   situazione   in   cui   più   unità   digitali   sono   collegate  
mediante  cavi  di  collegamento.  Da  quando  è  stato  introdotto  il  VHDL,  il  problema  del  ritardo  
di   propagazione   dei   segnali   attraverso   bus   o   linee   di   collegamento   diventa   significativo   anche  
all’interno   di   uno   stesso   circuito   integrato,   tanto   da   diventare,   almeno   attualmente,   uno   dei  
limiti   alla   velocità   di   clock   di   un   sistema   digitale   (l’altro   limite   è   la   potenza   dissipata).   Il  
ritardo   a   cui   ci   riferiamo   è   il   ritardo   tipico   di   una   linea   di   trasmissione   che   supponiamo  
termini  con  la  sua  impedenza  caratteristica  R0  per  fare  un  esempio.  
 

 
 
Supponiamo   la   linea   ideale   e   quindi   non   dispersiva,   priva   di   perdite.   Se   andiamo   ad  
osservare   quello   che   succede   in   qualunque   punto   della   linea,   troviamo   sostanzialmente   V/2  
nell’esempio   considerato,   ma   ritardato   di   un   tempo   che   dipende   dalla   distanza   rispetto   al  
carico   e   dalla   velocità   di   propagazione   su   questa   linea.   In   una   situazione   come   questa,   se  
siamo   in   una   condizione   ideale,   qualunque   sia   la   durata   di   un   impulso   in   ingresso   all’inizio  
della   linea   di   trasmissione,   questo   lo   ritroviamo   uguale   uguale,   senza   alcuna   modifica   dei  
fronti  e  ritardato  di  una  quantità  che  può  essere  anche  grandissima.  Questo  è  anche  quello  che  
succede   in   qualunque   sistema   di   comunicazione.   In   una   trasmissione   via   aria   o   via   vuoto   la  
situazione   è   la   stessa:   ho   dei   segnali   che   vengono   inviati   e,   supponendo   che   il   canale   abbia  
banda   sufficientemente   grande   da   immaginarcela   pressoché   infinita,   non   c’è   un   limite   alla  
velocità  di  variazione  dell’impulso,  ma  necessariamente  riceverò  tutti  i  segnali  con  un  ritardo,  
visto   che   mi   trovo   ad   una   distanza   finita.   Questo   tipo   di   comportamento   non   può   essere  
riprodotto  dall’inertial  perché  abbiamo  un  impulso  breve  e  vogliamo  modellare  il  fatto  che  in  
realtà  non  guardiamo  l’uscita  b  di  questo  inverter,  ma  b’:  
 

 
 
Stiamo   rappresentando   in   qualche   modo   una   linea   di   trasmissione.   Non   posso   dire   che,  
siccome   c’è   un   ritardo,   b’   cambia   100   ns   dopo   rispetto   alla   transizione   di   b   perché   se   così  
facessi,   transizioni   dell’ordine   di   20   ns   che   sono   tali   da   rendere   possibile   il   cambiamento  
dell’uscita   verrebbero   automaticamente   cancellate   ma   non   succederebbe   così   nella   realtà,  
cioè   la   presenza   di   un   ritardo   di   propagazione   non   incide   sulla   forma   del   segnale,   ma  
semplicemente   sull’istante   di   tempo   in   cui   questo   segnale   si   presenta   all’uscita   del   sistema.  
Quando   si   vuole   modellare   o   rappresentare   una   situazione   come   questa,   si   usa   la   parola  
chiave  “transport”.  La  parola  chiave  transport,  indipendentemente  dalla  velocità  di  variazione  
di  ‘a’  (intendendo  con  velocità  di  variazione  la  distanza  di  tempo  tra  un  fronte  di  salita  ed  un  
fronte   di   discesa),   fa   si   che   questo   comportamento   venga   riprodotto   in   uscita.   Se   vogliamo  
rappresentare   il   ritardo   di   propagazione,   generalmente   non   c’è   di   ritardo   di   propagazione   sul  
fronte  di  salita  e  sul  fronte  di  discesa,  quindi  la  situazione  tipica  in  cui  ci  si  trova  quando  si  usa  
un   ritardo   transport   è   che   i   ritardi   sul   fronte   di   salita   e   sul   fronte   di   discesa   sono   gli   stessi.   Se  
quello   che   stiamo   esprimendo   vuole   rappresentare   il   comportamento   di   una   linea   di  
trasmissione  questo  è  inevitabile.  
 

 
 
Se   usiamo   il   transport   non   facciamo   altro   che   dire   che   un   impulso   presente   su   ‘a’,  
comunque   breve,   semplicemente   viene   riprodotto   a   10   ns   come   se   questo   ritardo   fosse  
causato  da  una  linea  di  trasmissione.  Se  usassimo  il  modello  inerziale,  il  risultato  di  avere  un  
impulso  in  ingresso  su  ‘a’  sarebbe  avere  ‘b’  tutto  quanto  ad  1,  non  avremmo  la  transizione  #.  
 

 
 
Una   cosa   è   l’intenzione   di   modellare   in   VHDL   un   sistema   fisico,   un’altra   cosa   è   scrivere  
delle   cose   sintatticamente   corrette   ma   che   non   necessariamente   corrispondono   al   modello  
fisico   reale.   Ad   esempio   potremmo   voler   scrivere,   utilizzando   il   modello   transport,   che   b  
diventa  0  dopo  10  ns,  ma  quando  a,  invece  di  passare  da  0  a  1  passa  da  1  a  0,  b  diventa  1  dopo  
per  esempio  20  ns.  
 

 
 
 
La  differenza  di  tempo  delle  transizioni  nel  modello  di  un  inverter  che  pilota  una  capacità  
non   produce   mai   delle   inconsistenze:   “se   vuoi   fare   cambiare   l’uscita   prima   ancora   che   sia  
cambiata   per   colpa   di   prima,   cancella   tutto   e   non   cambia   niente”.   Questo   non   produce   mai  
delle   inconsistenze,   cioè   delle   situazioni   in   cui,   per   esempio,   a   regime,   se   abbiamo   0   in   un  
inverter,   per   colpa   di   queste   cancellazioni,   alla   fine   non   abbiamo   1   a   regime   ma   un   valore  
diverso.   Per   come   funziona   il   modello   inertial   questo   non   può   succedere.   Col   modello  
transport  invece  le  cose  sono  diverse.  Cerchiamo  di  capire  perché.  
Limitiamoci   ad   eseguire   quello   che   abbiamo   detto,   cioè   quando   viene   predetta   una  
transizione  questa  viene  eseguita  e  non  importa  se  ne  viene  prevista  un’altra  in  un  tempo  in  
cui   questa   ancora   non   è   stata   eseguita.   Vediamo   che   conseguenze   può   avere   questo   fatto.  
Immaginiamo  la  seguente  situazione,  utilizzando  un  modello  transport:  
 

 
 
Ricordiamo   che   tutte   le   volte   che   c’è   un   evento   noi   fermiamo   il   tempo,   ci   spostiamo   nel  
dominio   degli   eventi,   facciamo   i   nostri   conti   che   richiederanno   un   certo   tempo   (che   non   è  
però   quello   di   simulazione),   per   arrivare   a   programmare   dal   punto   di   vista   di   questo   dominio  
in  cui  si  evolvono  le  cose,  in  un  tempo  infinitesimo,  le  nuove  transizioni.    
Nell’esempio   sopra,   quando   a   diventa   1   (cioè   a   t*),   viene   programmata   la   transizione   per   b  
dopo  
10   ns   (cioè   a   t*+10ns).   Abbiamo   una   lista   di   eventi:   andiamo   al   prossimo   evento,   dove   a  
passa  da  1  ad  0,  cioè  a  5  ns.  In  questo  istante  di  tempo  a  è  diventato  0,  quindi  b  deve  diventare  
1   dopo   20   ns   rispetto   a   t*,   quindi   a   t*+25ns.   Continuiamo   nel   tempo   e   immaginiamo   che   dove  
c’è   il   taglio   si   abbia   la   situazione   riportata   sopra.   Dove   c’è   il   taglio   stiamo   supponendo   che  
siamo   in   un   tempo   lontano   rispetto   a   quello   in   cui   è   già   successo   tutto,   dove   abbiamo   a   ad   1   e  
b   a   0.   Supponiamo   di   avere   le   due   transizioni   di   a   mostrate   in   figura   ai   tempi   t’   e   t’+5ns.  
Quando  siamo  a  t’,  viene  programmato  un  b  che  va  alto  a  20  ns  di  distanza  (cioè  a  t’+20ns).  
Quando   si   arriva   a   t’+5ns   viene   programmato   un   b   che   va   basso   a   10   ns   di   distanza   (cioè   a  
t’+15ns).  Finito  questo  evento  si  passa  al  prossimo.  Il  prossimo  evento  che  si  incontra,  per  la  
differenza  di  ritardo  che  abbiamo  specificato  nelle  transizioni  è  b  che  va  basso,  ma  visto  che  
era  già  basso  rimane  basso,  poi  però  c’è  b  che  va  alto  e  quindi  diventa  alto.  Quindi  alla  fine  ci  
troviamo  nella  situazione  in  cui  quello  che  doveva  essere  un  inverter  è  in  realtà  un’altra  cosa  
(perché  abbiamo  sia  a  che  b  ad  1).  Quindi  col  modello  transport  abbiamo  una  inconsistenza  
che   dipende   dal   fatto   che   abbiamo   programmato   una   cosa   (a   t’)   che   avverrà   in   un   tempo  
successivo  (cioè  a  t’+20ns)  rispetto  a  ciò  che  abbiamo  programmato  in  un  istante  successivo  
(a  t’+5ns)  ma  che  avverrà  in  un  istante  di  tempo  precedente  (t’+15ns).  Col  modello  di  ritardo  
inerziale,   le   transizione   di   b   a   t’+15ns   e   a   t’+20ns   verrebbero   cancellate   e   il   problema  
verrebbe  quindi  risolto  perché  b  resterebbe  basso.  Col  modello  transport  si  presenta  questa  
situazione   quando   il   modello   transport   lo   usiamo   in   una   situazione   che   non   è   quella   reale  
perché  in  una  linea  di  trasmissione  non  ci  può  essere  differenza.  Nella  realtà  questa  situazione  
non  si  può  trovare  mai  perché  se  i  tempi  sono  uguali,  l’ordine  degli  eventi  viene  mantenuto,  
cioè   l’evento   che   viene   programmato   prima   avverrà   prima   dell’evento   che   abbiamo  
programmato  dopo.  Bisogna  tener  presente  che  questa  specifica  di  transport,  se  non  ci  fossero  
dei  meccanismi  automatici  all’interno  del  VHDL  che  aggiustino  questa  situazione,  può  portare  
a  delle  inconsistenze  rispetto  al  nostro  modello.  La  nostra  intenzione  è  che  se  abbiamo  ‘a’  ad  1  
stabile   in   ingresso   l’uscita   è   0   e   se   abbiamo   ‘a’   a   0   stabile   in   ingresso   l’uscita   è   1.   Nella  
descrizione,  questo  è  un  modello  combinatoriale  visto  che  le  variabili  appartengono  al  tipo  bit  
(l’abbiamo   dato   per   scontato)   e   ci   sono   solo   due   possibilità.   Invece   l’altro   è   un   sistea  
sequenziale,  un  sistema  che  ha  una  memoria.  Come  fa  il  VHDL  a  difendersi  da  situazioni  del  
genere?  La  regola  è  abbastanza  semplice:  quando  si  usa  il  modello  transport,  se  in  un  tempo  
(ad   esempio   t’+5ns)   viene   programmata   una   transizione   per   il   segnale   che   deve   avvenire   in  
un   tempo   precedente   (a   t’+15ns)   rispetto   ad   altre   transizioni   già   programmate   (a   t’+20ns),   le  
transizioni   programmate   per   il   futuro   (a   t’+20ns)   rispetto   all’istante   di   tempo   più   vicino  
(t’+15ns)   vengono   semplicemente   cancellate.   In   questo   caso   il   risultato   somiglia   a   quello  
dell’inertial.  In  questo  modo  si  mantiene  la  coerenza  sui  valori  di  regime.    
Vediamo  un  po’  di  sintassi.  La  scrittura  corretta  è  la  seguente:    
 

 
 
Ogni  statement  in  VHDL,  un  po’  come  in  C,  termina  con  punto  e  virgola.  Il  process  termina  con  
end  process,  l’if  termina  con  end  if.  Se  non  scriviamo  né  TRANSPORT  né  INERTIAL,  di  default  
è  come  se  ci  fosse  scritto  INERTIAL.  Dobbiamo  vedere  quali  sono  le  possibilità  che  ci  vengono  
offerte   per   poter   descrivere   il   comportamento.   Descrivere   il   comportamento   significa,   a  
partire  da  transizioni  che  avvengono  in  un  certo  istante,  programmare  nuove  transizioni  nel  
futuro.   Ricordiamo   che   tutti   i   process   presenti   nel   sistema,   all’inizio   dei   tempi,   vengono  
attivati.   È   come   se   la   nascita   dell’evento   di   simulazione   venisse   ritenuto   un   evento   di   tutti   i  
process.   Questo   è   anche   il   meccanismo   con   il   quale   un   process   senza   lista   di   sensibilità   può  
essere   utilizzato   per   generare   con   i   wait   for   una   serie   di   eventi   che   possiamo   considerare  
come   l’ingresso   dei   nostri   sistemi.   Il   VHDL   prevede   la   possibilità   che   si   possa   parlare   a   chi   sta  
osservando  l’esecuzione  di  una  simulazione,  inviando  volta  per  volta  dei  messaggi  di  output  
con   riferimento   a   dei   controlli   che   noi   stessi   abbiamo   messo   in   certi   punti.   Ad   esempio  
costruiamo   un   sistema   complicato   e   siamo   nella   situazione   in   cui   vogliamo   vedere   se   i   segnali  
di   ingresso   all’inverter,   che   vengono   dal   sistema   complicato,   sono   sufficientemente   distanti  
l’uno  dall’altro  per  assicurare  che  l’uscita  abbia  effettivamente  una  transizione.  Nel  nostro  file  
VHDL   possiamo   dire,   per   esempio,   di   controllare   se   in   corrispondenza   di   una   transizione  
dell’ingresso   si   verifichi   effettivamente   una   transizione   in   uscita   e,   nel   caso   in   cui   ciò   non   si  
verifichi,   di   dare   un   messaggio,   una   segnalazione.   Questo   meccanismo   si   chiama   ASSERT,   lo  
vedremo.  Se  abbiamo  4,5  segnali,  possiamo  vederne  l’andamento  nel  tempo  e  studiare  quello  
che   succede.   Nel   caso   di   sistemi   più   complessi   è   però   impossibile   studiarne   il   comportamento  
semplicemente   osservando   l’andamento   dei   segnali   nel   tempo.   Diventa   quindi   importante  
inserire   in   alcuni   punti   strategici   degli   statement   VHDL   che   permettano   di   inviare   dei  
messaggi  a  chi  sta  guardando  lo  schermo  buio  per  dire  “sta  andando  tutto  bene”,  “sta  andando  
male”,   “è   successa   questa   cosa”,   e   così   via.   In   VHDL   ci   sono   costanti,   variabili   e   segnali,   invece  
negli  altri  linguaggi  di  programmazione  in  genere  ci  sono  costanti  e  variabili.  Essi  sono  oggetti  
che  generalmente  assumono  valori  all’interno  di  quello  che  si  chiama  TIPO.  In  tutti  i  linguaggi  
esiste  un  certo  numero  di  tipi  predefiniti  e  in  molti  linguaggi  esistono  anche  dei  meccanismi  
che   permettono   di   creare   dei   tipi   definiti   dall’utente.   Il   VHDL   permette   di   definire   tipi   di  
qualunque  natura.  I  tipi  sono  generalmente  definiti  per  enumerazione,  o  fanno  riferimento  ai  
numeri  interi,  o  fanno  riferimento  ai  numeri  reali.  Esistono  anche  tipi  composti  come  array  (o  
vettori),   record,   puntatori.   Le   stringhe   di   bit   vengono   viste   in   VHDL   come   un   array   di   bit.   Il  
tipo  tempo,  che  permette  di  specificare  i  ritardi  di  propagazione,  ha  a  che  fare  con  i  numeri  
reali.  Quando  lavoriamo  in  VHDL  siamo  in  una  directory  del  nostro  sistema  (schermo  del  PC,  
prompt).  La  directory  è  un  contenitore  dei  diversi  elementi  che  servono  per  procedere  nella  
scrittura  del  modello  del  nostro  sistema.  La  scrittura  può  avvenire  in  un  unico  file  o  in  più  file  
spezzettati,   purchè   siano   nella   stessa   directory.   Tutto   quello   che   viene   esaminato,   ad   esempio  
dal   GHDL,   viene   automaticamente   memorizzato   come   parte   delle   cose   disponibili   nella  
directory  in  questione.  Se  ad  esempio  abbiamo  due  file  distinti  che  fanno  riferimento  ad  entità  
che   in   qualche   maniera   sono   fra   di   loro   interconnesse,   il   fatto   di   averle   messe   nella   stessa  
directory  le  rende  perfettamente  equivalenti  (dal  punto  di  vista  del  GHDL)  alla  situazione  in  
cui   le   avessimo   scritte   tutte   in   un   unico   file.   Ogni   directory   è   cioè   un   ambiente   di   lavoro  
separato.   GHDL   è   un   insieme   di   compilatore,   simulatore,   controllore   sintattico,   costruttore…   I  
file   VHDL   sono   file   di   testo.   Le   estensioni   dei   file   hanno   generalmente   estensione   .vhd   o   .vhdl.  
In   alcuni   dei   sistemi   che   vedremo,   è   richiesto   che   il   nome   dell’entità   principale   (cioè   quella  
che  contiene  il  sistema  nel  suo  complesso,  quella  che  si  chiama  TEST  BENCH)  coincida  con  il  
nome  del  file  prima  dell’estensione.  
Un  commento  in  VHDL  si  scrive  con  due  trattini.  Un  modello  VHDL  si  compone  di  linee  di  
testo.  
 
Commenti  
Si   può   aggiungere   un   commento   dopo   una   linea   di   codice   valido   VHDL   usando   il   doppio  
trattino  “-­‐-­‐”.  Tutto  quello  che  viene  scritto  dopo  il  doppio  trattino  fino  alla  fine  della  riga  viene  
ignorato.  Una  riga  che  comincia  con  il  doppio  trattino  è  un  commento.  
 
 
entity  andport  is  -­‐-­‐  Questa  è  l'inizio  della  dichiarazione  
dell'entita'  andport  
-­‐-­‐  ora  dobbiamo  definire  le  porte  
port  is  (a,  b:  in  bit;  y:  out  bit);  
-­‐-­‐  non  c'è  altro  da  fare  ora  si  chiude  le  parte  dichiarativa  
end  entity  andport;  -­‐-­‐  Fine  della  dichirazione  
 
Figura  1:  Esempio  di  commento  

 
Identificatori  
 
Sono   identificatori   i   nomi   degli   “oggetti”   che   costituiscono   un   modello   VHDL   (segnali,  
entità,  nomi  di  architettura  ecc).  Gli  identificatori  devono  rispettare  le  regole  seguenti:  
 
•  Gli  identificatori  possono  contenere  solo  lettere,  cifre  decimali  e  il  carattere  “_”;  
•  un  identificatore  deve  cominciare  con  un  carattere  alfabetico;  
•  un  identificatore  non  può  terminare  con  il  carattere  “_”;  
•  non  possono  essere  presenti  due  o  più  caratteri  “_”  consecutivi;  
 
Non   c'è   distinzione   tra   lettere   maiuscole   e   minuscole:   “Paolo”   e   “paolo”   sono   lo   stesso  
identificatore.  
 
Cane,  cane_e_gatto,  gatto_e_2_cani       (Esempio  di  identificatori  validi)  
_Cane,  cane_,  1_cane_e_due_gatti,  33trentini,     (Esempio  di  identificatori  non  validi)    
 
Sono  possibili  i  così  detti  identificatori  estesi.  Gli  identificatori  estesi  iniziano  e  finiscono  il  
carattere   “\”.   Tutti   i   caratteri   (inclusi   gli   spazi)   possono   essere   usati   all'interno   di   un  
identificatore   esteso.   Per   esempio   \1  cane  e  2  gatti\   è   un   identificatore   valido.   Attenzione:   nel  
caso   degli   identificatori   estesi,   c'è   distinzione   fra   maiuscole   e   minuscole:   \Cane\,   \cane\,  
\CANE\  sono  3  identificatori  distinti.  Non  possono  essere  usate  come  identificatori  le  parole  
chiave   che   fanno   parte   del   linguaggio.   Per   esempio   non   si   può   usare   la   parola   entity   come  
identificatore.  
 
Numeri  
 
I  numeri  possono  essere  relativi  a  quantità  intere  o  quantità  reali.  
Un  numero  intero  si  rappresenta  come  una  sequenza  di  cifre  senza  punto  decimale.  La  base  
di  default  è  la  base  10,  ma  possono  essere  specificate  basi  diverse  come  negli  esempi  seguenti:  
123         numero  123  rappresentato  in  base  10  
2#10010#       numero  18  rappresentato  in  base  2  
16#12#   numero   18   rappresentato   in   esadecimale   (in   esadecimale,   oltre  
alle  cifre  da  0  a  9  si  usano  le  lettere  a,  b,  c,  d,  e,  f  per  rappresentare  
le  cifre  relaive  ai  numeri  10,11,12,13,14,15)  
 
Come  aiuto  per  la  lettura,  specialmente  nel  caso  della  base  2,  si  può  usare  il  carattere  “_”  
per  separare  le  cifre.  Per  esempio  le  due  scritture  seguenti  sono  equivalenti:  
 
2#100111001#  
2#1_0011_1001#  
 
Per   rappresentare   i   numeri   reali   sono   possibili   le   consuete   notazioni   in   virgola   fissa   e  
esponenziali.  
 
Caratteri  
 
Il  singolo  carattere  (una  costante  di  tipo  carattere)  viene  indicato  racchiudendo  il  carattere  
fra  apici:  
'A',  'b',  '?'.  
 
Stringhe  
 
Una   costante   di   tipo   stringa   si   indica   racchiudendo   i   caratteri   che   la   compongono   fra  
virgolette.  
Esempio:  “Questa  è  una  stringa”  
Il  carattere  &  viene  usato  per  concatenare  più  costanti  di  tipo  stringa.  
 
 
 
 
Stringhe  di  bit  
 
Se  vogliamo  parlare  della   stringa   di  bit   che   corrisponde   al   numero  decimale   123   è   faticoso  
fare   la   conversione   e   c’è   il   rischio   che   facciamo   errori.   In   VHDL,   come   in   altri   linguaggi,   si   può  
dire  al  VHDL  “pensaci  tu,  io  intendo  scrivere  in  questo  posto  una  stringa  di  bit,  ma  siccome  mi  
secco  a  fare  il  conto,  ti  dico  che  la  stringa  di  bit  è  quella  che  corrisponde  alla  interpretazione  in  
binario,  ottale  o  esadecimale  del  numero  che  ti  scrivo  accanto”.  Per  esprimere  una  stringa  di  
bit,   generalmente,   dovremmo   mettere   la   parola   B   prima   delle   virgolette,   se   non   la   mettiamo   è  
automaticamente  una  stringa  di  bit.  
 
Esempio:  B“111_0110_1010_1010”  
 
In  VHDL  si  ha  spesso  a  che  fare  con  sequenze  di  bit.  Si  può  rappresentare  una  sequenza  di  
bit   con   riferimento   alla   notazione   binaria,   ottale   o   esadecimale.   Esempi   di   stringhe   di   bit  
secondo  le  tre  possibili  notazioni  sono:  
B”10001101”   oppure  B”1000_1101”  stringa  di  bit  in  notazione  binaria  
O”342”       stringa  di  bit  in  notazione  ottale  (equivalente  a  B”011_100_010”)  
X”AF”       stringa  di  bit  in  notazione  esadecimale  (equivalente  a  B”1010_1111”)  
 
Nel   seguito   si   farà   riferimento   alla   notazione   EBNF   per   la   descrizione   degli   elementi   del  
linguaggio.  Le  parole  chiave  del  linguaggio  sono  indicate  in  grassetto.    
 
Costanti  e  variabili  
 
Costanti  e  variabili  devono  essere  dichiarate  prima  di  poter  essere  usate  in  un  modello.  Una  
dichiarazione   introduce   il   nome   di   un   oggetto   (costante   o   variabile),   ne   definisca   il   tipo   e   può  
fissare  un  valore  iniziale.  
 
Dichiarazione  di  costante  
 
dichiarazione_di_costante<=constant  identificator  {...}:indicatore_di_sottotipo[:=espressione];  
La   parte   di   assegnazione   (:=espressione)   è   ovviamente   obbligatoria   nel   caso   della   definizione  
di   costanti,   ma   è   indicata   come   opzionale   perché   ci   sono   alcuni   casi   particolari   in   cui   si   può  
definire  una  costante  senza  assegnare  il  valore.    
 
constant  numero_di_byte:integer:=4;  
constant  numero_di_bit:integer:=8*numero_di_bytes;  
constant  e:real:=2.718281;  
constant  ritardo_di_propagazione:  time:=3  ns;  
 
Dichiarazione  di  variabile  
 
dichiarazione_di_variabile<=variable   identificatore{...}:   identificatore_di_sottotipo  
[:=espressione];  
Il  valore  iniziale,  nel  caso  in  cui  non  sia  esplicitamente  specificato,  dipende  dal  tipo.  Per  i  tipi  
scalari,  il  valore  iniziale  è  il  valore  “più  a  sinistra”  del  tipo.  L'espressione  “più  a  sinistra”  sarà  
più  chiara  in  seguito.  Le  variabili  possono  essere  usate  esclusivamente  all'interno  di  process.  
 
Assegnazione  di  variabile  
 
assegnazione_di_variabile<=[label:]nome:=espressione;  
Per  esempio:  
se  var_a  è  una  variabile  di  tipo  bit,  allora  per  assegnare  il  valore  '1'  si  scrive  var_a:='1';  
se  var_b  è  una  variabile  di  tipo  intero  si  può  scrivere  var_b:=3+7;  
 
 
 
TIPI  SCALARI  
 
 
Dichiarazione  di  tipo  
 
dichirazione_di_tipo<=type  identificatore  is  definizione_del_tipo;  
 
Esempio  di  definizione  di  tipo:  
 
type  mele  is  range  0  to  100;  
type  arance  is  range  0  to  100;  
 
Si  noti  che,  nonostante  mele  e  arance  prevedano  gli  stessi  insiemi  di  valori,  si  tratta  di  due  tipi  
diversi!  
Pertanto,  se  definissimo  le  seguenti  variabili:  
 
mela:mele:=0;  
arancia:arance:=1;  
frutto:arance;  
 
l'assegnazione  seguente  sarebbe  scorretta:  
 
frutto:=mela+arancia;  
 
Si  ponga  attenzione  al  fatto  che  se  si  vogliono  usare  tipi  definiti  dall'utente  nella  dichiarazione  
delle   porte   di   una   entità,   i   tipi   devono   essere   in   qualche   modo   già   definiti   prima   della  
dichiarazione   dell'entità   stessa.   Questo   si   ottine   definendo   i   tipi   che   si   intendono   sfruttare  
all'interno   di   un   “package”.   Per   il   momento   un   package   può   essere   visto   come   in   insieme   di  
definizioni  di  tipo.  Un  package  può  essere  contenuto  in  un  file  di  testo  separato.  
Esempio  di  definizione  di  package:  
 
package  tipi_interi  is  
type  small_int  is  range  0  to  255;  
type  med_int  is  range  0  to  65535;  
end  package  tipi_interi;  
 
Una   volta   che   il   file   che   contiene   il   package   viene   elaborato,   i   tipi   definiti   sono   “visibili”   se  
immediatamente  prima  della  definizione  dell'entità  si  scrive:  
 
use  work.tipi_interi.all;  
entity  prova  is  
port  (a,b:  in  small_int;  c:  out  med_int);  
end  entity  small_order;  

Nota bene: quando si elabora un package nella stessa directory di lavoro in cui si elaborano le
entità, tutte le definizioni (tipi, entità ecc. entrano a far parte di una libreria di default che ha il nome
convenzionale work).
 
 
 
Tipi  interi  
 
I   tipi   interi   in   VHDL   hanno   valori   nell'insieme   dei   numeri   interi.   Lo   standard   del   linguaggio  
prevede  che  l'intervallo  dei  valori  possibili  si  estenda  da  un  minimo  di  -­‐231+1  a  un  massimo  
di   231-­‐1.   Si   può   definire   un   nuovo   tipo   intero   come   un   sottoinsieme   del   tipo   intero  
predefinito.  
 
definizione_di_tipo_intero<=  range  espressione_semplice  {to  |  downto}  espressione_semplice  
 
Si   noti   che   le   parole   chiave   to   e   downto   servono   a   definire   un   ordinamento   interno   di   tipo  
crescente   o   decrescente   all'interno   del   tipo.   Il   valore   “più   a   sinistra”   del   tipo   è   il   valore   più  
piccolo   nel   caso   di   ordinamento   crescente;   è   il   valore   più   grande   nel   caso   di   ordinamento  
decrescente.  
Esempi  di  definizione  di  tipi  interi:  
 
type  giorno_del_mese  is  range  0  to  31;  
type  anno  is  range  0  to  2100;  
 
A  questo  punto  si  possono  definire  delle  variabili  che  appartengono  ai  tipi  definiti:  
 
oggi:giorno_del_mese:=9;  
anno_di_nascita:anno:=1965;  
 
Ricordiamo  che  non  è  permesso  scrivere  espressioni  come:  
 
anno_di_nascita:=oggi;  
 
Sono  possibili  definizioni  del  tipo:  
 
constant  numero_di_bit:integer:=32;  
type  bit_index  is  range  0  to  numero_di_bit-­‐1;  
 
Sui  valori  del  tipo  intero  sono  definite  alcune  operazioni:  
•  +  :  addizione  o  identità;  
• =  :  operazione  di  confronto  (l’operazione  di  assegnazione  si  esegue  con  “:=”)  
•  -­‐  :  sottrazione  o  negazione  
•  *  :  moltiplicazione  
•  /  :  divisione  
•  mod  :  modulo  
•  rem  :  resto  
•  **  :  elevamento  a  potenza  (solo  esponenti  interi  non  negativi)  
 
The  remainder  (rem)  and  modulus  (mod)  are  defined  as  follows:  
   
                       A  rem  B  =  A  –(A/B)*B                                                (in  which  A/B  in  an  integer)  
                       A  mod  B  =  A  –  B  *  N                                        (in  which  N  is  an  integer)  
 
Il  risultato  dell’operatore  rem  ha  il  segno  del  primo  operando  della  divisione  e  inoltre  |A  rem  
B|  <  |B|.  Il  risultato  dell’operatore  mod  ha  invece  il  segno  del  secondo  operando.  
Some  examples  of  these  operators  are  given  below.  
   
11  rem  4                                            results  in  3  
(-­‐11)  rem  4                                  results  in  -­‐3  
9  mod  4                                                results  in  1  
7  mod  (-­‐4)                                      results  in  –1    (7  –  4*2  =  -­‐1)  
 
Tipo  Floating  Point  
 
Lo  standard  prevede  che  l'intervallo  dei  valori  rappresentabili  deve  essere  almeno  pari  a            
(-­‐1.8E+308+1.8E308)  
 
Floating_point_definition  <=  range  simple_expression  {to  |  downto}  simple_expression  
 
Esempio  di  definizione  di  un  tipo  floating  point  
 
type  real_value  is  range  0.0  to  1e6;  
 
Tipi  “fisici”  
 
physical_type_definition<=  range  simple_expression  {to  |  downto  }  simple_expression  
units  
identifier;  
{identifier=physical_literal;}  
end  units  [identifier]  
physical_literal<=[decimal_literal  |  based_literal]  unit_name  
 
Esempio:  
 
type  resistance  is  range  0  to  1e9  
units  
ohm;  
end  units  resistance;  
 
Assegnazione  di  una  variabile  del  tipo  resistance  
 
r23:=  330  ohm;  
 
Esempi  di  definizione  con  unità  principale  e  secondarie  
 
type  resistance  is  range  0  to  1e9  
units  
ohm;  
kohm=  1000  ohm;  
Mohm=1000  kohm;  
end  units;  
 
type  lunghezze  is  range  0  to  1e9  
units  
um;  
mm=1000  um;  
m=1000  mm;  
pollice=  25.4  mm;  
piede=  12  pollici;  
km=  1000  m;  
end  units;  
 
Il  tipo  time  è  un  tipo  fisico  predefinito  nel  seguente  modo:  
 
type  time  is  range  (definizione  dipendente  dall'implementazione)  
units  
fs;  
ps=  1000  fs;  
ns=  1000  ps;  
us=  1000  ns;  
ms=1000  us;  
sec=1000  ms;  
min=  60  sec;  
hr=  60  min;  
end  units;  
 
Si  noti  che  è  obbligatoria  la  presenza  di  uno  spazio  fra  il  valore  numerico  e  l'unità  di  misura  
tutte  le  volte  che  si  assegna  un  valore  a  una  variabile  di  tipo  fisico.  
 
Tipi  definiti  per  enumerazione  
 
Vengono  esplicitamente  elencati  gli  elementi  del  tipo  mediante  l'elencazione  di  identificatori  o  
di  caratteri  alfanumerici.  
 
enumeration_type<=(  (identificatore  |  carattere  ),  {,...})  
 
Esempi:  
 
type  colori_semaforo  is  (rosso,  giallo,  verde);  
 
type  colori  is  ('b','w','g','y','r');  
 
Il  tipo  character  è  un  tipo  predefinito  definito  per  enumerazione:  
type  character  is  (nul,  soh,  …....'a','b',.......);  
 
Un  importante  tipo  predefinito  definito  per  enumerazione  è  il  tipo  boolean  
type  boolean  is  (false,true);  
 
Questo  tipo  è  usato  per  rappresentare  il  valore  delle  “condizioni”  che  controllano  l'esecuzione  
di   modelli   comportamentali.   Ci   sono   un   certo   numero   di   operatori   logici   e   relazionali   che  
producono  risultati  del  tipo  boolean.  
 
Le  espressioni  123=123,  'A'='A',  7  ns=7  ns  producono  il  valore  true;  
Le  espressioni  123=443,  'A'='z',  7  ns=17  us  producono  il  valore  false  
Gli  operatori  logici  and,  or,  nand,  nor,  xor,  xnor  e  not  si  applicano  a  espressioni  o  variabili  di  
tipo  boolean  e  producono  risultati  di  tipo  boolean.  
Gli   operatori   and,   or,   nand,   e   nor   sono   chiamati   operatori   di   tipo   “short.circuit”.   Questa  
denominazione   deriva   dal   fatto   che   nella   valutazione   dell'espressione   logica   viene   valutata  
prima  l'espressione  a  sinistra  dell'operatore.  Se  il  risultato  è  tale  che  il  valore  dell'espressione  
logica   è   determinato   (per   esempio   nel   caso   dell'   and,   se   l'espressione   a   sinistra   vale   false,   il  
risultato  dell'operazione  and  è  comunque  false)  l'espressione  a  destra  non  viene  valutata.  Un  
altro  importante  tipo  definito  per  enumerazione  è  il  tipo  bit  
type  bit  is  ('0','1');  
 
Sul  tipo  bit  sono  definite  operazioni  di  tipo  logico  corrispondenti  alle  operazioni  dell'algebra  
booleana.  Si  faccia  attenzione  al  fatto  che  l'operatore  and,  quando  applicato  al  tipo  bit  produce  
come  risultato  un  valore  del  tipo  bit.  ('1'  and  '1')  produce  come  risultato  il  valore  '1'  del  tipo  
bit.  Non  hanno  significato  espressioni  del  tipo  ('1'  and  true).  
 
type  severety_level  is  (note,  warning,  error,  failure);    
 
questo   tipo   predefinito   è   utilizzato   nella   comunicazione   tra   il   simulatore   e   l’operatore,   nella  
fase  di  simulazione.  Mediante  l’istruzione  “assert”,  è  possibile,  in  punti  e  in  momenti  strategici  
dell’evoluzione   della   fase   di   simulazione,   inviare   dei   messaggi   e   interferire   con   l’esecuzione  
del   programma.   Note,   warning,   error   e   failure   sono   4   diversi   livelli   di   gravità.   Assert   note  
significa  che  non  c’è  nessun  problema  (ad  esempio  “a  è  diventato  finalmente  1”,  tanto  per  non  
avere   tutto   lo   schermo   buio).   Warning,   error   e   failure   sono   diversi   livelli   di   gravità   che  
possiamo   scegliere   noi   nella   fase   in   cui   si   ha   l’esecuzione   e   che   possono   essere  
automaticamente   utilizzati   dal   simulatore   VHDL   per   decidere   cosa   fare.   Nel   momento   in   cui  
cominciamo   a   simulare   un   sistema   potremmo   ad   esempio   dire   “quando   c’è   failure   bloccati,  
quando  c’è  error  non  ti  fermare  e  continua  ad  andare  avanti”  e  così  via.  Questo  nella  fase  di  
debug,   in   cui   si   eseguono   delle   simulazioni,   si   ricevono   dei   messaggi   e   si   interviene   a   bloccare  
o  meno  l’esecuzione  del  programma.  
 
Tipo  standard  logic  
 
Tipo   standar_ulogic   è   un   tipo   definito   per   enumerazione   in   una   package   standardizzato  
(std_logic_1164).  
 
type  std_ulogic  is  (     'U',  -­‐-­‐Unitialized  
'X',  –  Forcing  Unknown  
'0',  –  Forcing  zero  
'1',  –  Forcing  one  
'Z',-­‐-­‐  high  impedence  
'W',-­‐-­‐  Weak  Unknown  
'L',  –  Weak  zero  
'H',-­‐-­‐  weak  one  
'-­‐');  –  don't  care  
 
Come  vedremo  in  seguito,  questo  tipo  è  alla  base  del  tipo  std_logic  per  descrivere  in  modo  più  
realistico  il  comportamento  dei  sistemi  logici.  
 
Sottotipi  
 
In   VHDL   il   motivo   per   cui   si   ricorre   all’utilizzo   di   sottotipi   è   collegato   fatto   che   se   servirà  
dover  realizzare  in  hardware  qualcosa  che  sia  un  contatore  da  0  a  7,  questo  necessariamente  
si   farà   utilizzando   un   sistema   binario   (un   registro   a   3   bit).   Se   siamo   cioè   sicuri   che   i   valori  
possibili  sono  compresi  tra  0  e  7,  li  chiamiamo  esplicitamente  onde  evitare  che  chi  costruisce  
il   sistema   possa   pensare   che,   siccome   voglio   utilizzare   come   segnale   o   variabile   una   cosa  
intera,  devo  utilizzare  necessariamente  un  registro  a  32  bit.  
 
subtype_declaration<=subtype  identifier  is  subtype_indication;  
subtype_indication<=  type_mark  [range  simple_expression  {to  |  downto}  simple  expression]  
 
 
Come   suggerisce   il   nome,   un   sottotipo   è   un   tipo   ottenuto   a   partire   da   un   tipo   già   definito.  
L'insieme   dei   valori   di   un   sottotipo   è   generalmente   un   sottoinsieme   dei   valori   del   tipo.  
Variabili   di   un   sottotipo   e   del   tipo   di   partenza   possono   essere   combinati   in   operazioni   di  
assegnamento  o  di  calcolo.  
Per  esempio:  
 
subtype  small_int  is  integer  range  0  to  255;  
subtype  med_int  is  integer  range  0  to  65535;  
 
Supponiamo  ora  che  sia:  
 
var1:small_int:=37;  
var2:med_int:=528;  
 
 
L'espressione:  
 
var2:=var2+var1;  
 
ha  perfettamente  senso.  Si  noti  che,  invece,  l'espressione:  
 
var1:=var1+var2;  
 
è  lecita,  ma  produce  un  errore  perché  il  risultato  dell'operazione  non  appartiene  al  sottotipo  
della  variabile  a  sinistra  dell'operazione  di  assegnazione.  
 
Qualificatori  di  tipo  
 
Supponiamo  di  aver  definito  i  seguenti  tipi:  
 
type  colore  is  (rosso,  verde,giallo);  
type  semaforo  is  (rosso,verde,giallo);  
 
I   tipi   sono   distinti   e   pertanto   il   valore   “rosso”   del   tipo   colore   è   cosa   diversa   dal   valore   “rosso”  
del   tipo   semaforo.   Per   distinguere   chiaramente   i   due  valori   si   può   fare   esplicito   riferimento   al  
tipo  di  appartenenza  mediante  le  espressioni:  
 
colorè(rosso)  
semaforo'(rosso)  
 
Attributi applicabili ai tipi scalari

Sia TIPO l'identificatore di un tipo o sottotipo scalare.


Allora
TIPO'left indica l'elemento più a sinistra del tipo
TIPO'right indica l'elemento più a destra del tipo
TIPO'low indica il valore più piccolo del tipo
TIPO'high indica il valore più grande del tipo
TIPO'ascending è un valore boolean che vale true se il tipo è ordinato in modo crescente, altrimenti
vale false

Attribute   Type  of  T   Result  type   Result  


T’image(x)   Any  scalar  type   string   A  textual  representation  of  the  
or  subtype   value  x  of  type  T  
T’value(s)   Any  scalar  type   Base  type  of  T   Value   in   T   represented   by   the  
or  subtype   string  s  
 
 
 
 
 
Attributi  applicabili  a  tipi  discreti  e  fisici.  
 
Sia  ancora  TIPO  l'identificatore  di  un  tipo  discreto  o  fisico  
TIPO'pos(x)  produce  un  intero  che  indica  la  posizione  di  x  all'interno  dell'insieme  dei  valori  
del  tipo  (il  primo  valore  del  tipo  ha  indice  di  posizione  0)  
TIPO'val(n)  produce  il  valore  x  che  ha  indice  di  posizione  n  
TIPO'succ(x)   produce   il   valore   del   tipo   di   indice   di   posizione   immediantamente   superiore   a  
quello  di  x  
TIPO'pred(x)   produce   il   valore   del   tipo   di   indice   di   posizione   immediantamente   inferiore   a  
quello  di  x  
TIPO'leftof(x)  produce  il  valore  del  tipo  con  posizione  immediantamente  a  sinistra  di  x  
TIPO'rightof(x)  produce  il  valore  del  tipo  con  posizione  immediantamente  a  destra  di  x  
 
Tipi  composti  
 
Array  
 
Un  array  è  una  collezione  ordinata  di  valori  tutti  appartenenti  allo  stesso  tipo.  
La  definizione  di  un  tipo  array  segue  la  sintassi:  
 
array_type_definition<=array  (discrete_range  {,....}  of  element_subtype_indication)  
discrete_range<=discrete_subtype_indication   |   simple_expression   (   to   |   downto)  
simple_expression  
subtype_indication<=type_mark  [range  simple_expression  (to|downto)  simple_expression]  
 
Esempi:  
 
type  word  is  array  (0  to  31)  of  bit;  
type  word  is  arry  (31  downto  0)  of  bit;  
 
Supponiamo  sia  
 
type  colori  is  (nero,rosso,verde,blu,giallo,bianco);  
 
possiamo  ora  definire  un  array  di  numeri  naturali  (sottotipo  predefinito  del  tipo  intero)  come:    
 
type  quantita  is  array  (rosso  to  giallo)  of  natural;  
 
In  questa  definizione  ci  si  affida  al  fatto  che  il  tipo  del  range  è  chiaro  dal  contesto.  Se  non  fosse  
cosi'  (per  esempio  se  rosso  e  giallo  sono  elementi  di  più  tipi  definiti  per  enumerazione)  allora  
si  può  scrivere:  
 
type  quantita  is  array  (colori  range  rosso  to  giallo)  of  natural;  
 
Altro  modo:  
 
subtype  alcuni_colori  is  colori  range  rosso  to  giallo;  
type  quantita  is  array  (alcuni_colori)  of  natural;  
 
 
Se  si  definisce  una  variabile  di  tipo  array:  
 
variable  parola:word;  
 
allora  parola(3)  indica  il  bit  di  indice  3  all'inteno  della  parola.  Se  si  ha:  
 
variable  parola1,parola2:word;  
allora  l'espressione  
parola1:=parola2;  
 
copia  tutti  gli  elementi  di  parola2  in  parola1.  È  possibile  definire  array  multidimensionali.  
 
Assegnazione  di  valori  ad  un  array.  
 
L'inizializzazione  di  costanti  o  variabili  di  tipo  array  può  essere  ottenuta  come  segue:  
 
type  point  is  array  (1  to  3)  of  real;  
constant  origin:point:=(0.0,0.0,0.0);  
variable  view_point:point:=(10.0,20.0,0.0);  
 
In   alternativa   alla   “associazione   posizionale”,   si   può   specificare   il   valore   da   assegnare   alle  
variabili  corrispondenti  a  specifici  indici.  
 
variable  view_point:point:=(1=>10.0,  2=>20.0,3=>0.0);  
 
Altra  interessante  possibilità:  
 
type  coeff_array  is  array  (coeff_ram_address)  of  real;  
variable  coeff:coeff_array:=(0=>1.6,  1  =>2.3,  2  to  63  =>0.0);  
 
o  ancora:  
 
variable  coeff:coeff_array:=(0|3|5|9  =>1.2,  1=>2.3,  others=>0.0);  
 
Il  simbolo  “|”  serve  per  separare  indici  a  cui  corrisponde  lo  stesso  valore.  “Others”  deve  essere  
sempre  messo  in  fondo).  
 
Assegnamento  di  segnali  
 
signal_assignment_statement  <=  [label:]  name  <=[delay_mechanism]  waveform:  
waveform<=(value_expression  [after  time_expression]){,  ….}  
delay_mechanism<=  transport  |  [reject  time_expression]  inertial  
 
per  esempio  
 
y<=not  or_a_b  after  5  ns;  
 
clk<='1'  after  10  ns,  '0'  after  20  ns;  
 
Se  non  compare  la  specifica  del  meccanismo  di  ritardo  si  assume  che  esso  sia  inerziale.  
 
 
 
Attributi  dei  segnali  
 
Sia  S  il  nome  di  un  segnale  e  T  una  costante  che  specifica  un  tempo.  Allora:  
 
S'delayed(T)  è  un  segnale  che  assume  gli  stessi  valori  di  S  ma  con  un  ritardo  pari  a  T  
 
S'stable(T)  è  un  valore  Boolean  che  è  “true”  se  non  c'è  stato  alcun  evento  su  S  nell'intervallo  di  
tempo  di  durata  T  immediatamente  precedente  al  tempo  attuale.  
 
S'quiet(T)   è   un   valore   Boolean   che   è   “true”   se   non   c'è   stato   alcuna   transazione   su   S  
nell'intervallo  di  tempo  di  durata  T  immediatamente  precedente  al  tempo  attuale.  
S'transaction  è  un  segnale  di  tipo  bit  cha  cambia  valore  da  '0  '  a  '1'  o  viceversa  tutte  le  volte  
che  c'è  una  transazione  di  S  
S'event   è   un   valore   Boolean   che   è   “true”   se   c'è   un   evento   su   S   nel   circolo   di   simulazione  
corrente.  
S'active  è  un  valore  Boolean  che  è  “true”  se  c'è  una  transazione  su  S  nel  circolo  di  simulazione  
corrente.  
S'last_event  indica  il  tempo  dall'ultimo  evento  
S'last_active  indica  il  tempo  dall'ultima  transazione  
S'last_value  indica  il  valore  di  S  prima  dell'ultimo  evento  su  S  
Questi   attributi   sono   particolarmente   utili   nella   fase   di   simulazione   per   il   controllo   del  
rispetto  delle  specifiche  di  sistema  mediante  “assert”.  
 

 
 
 

 
 
 
 
INFINITE  LOOP  
 

 
 
 
CASE  STATEMENT  
 
 

 
 
 
 
 
L’istruzione   next   interrompe   l’esecuzione   di   un   ciclo   del   loop   e   ritorna   a   valutare   la  
condizione   principale   del   loop.   L’istruzione   exit   serve   per   uscire   fuori   dal   loop   e   proseguire  
con   l’espressione   successiva   e   poi   con   end   loop.   Con   exit   si   può   uscire   anche   da   un   loop  
infinito.    
Un   altro   importante   statement   sequenziale   (specie   in   fase   di   analisi   del   sistemi)   è   lo  
statement  assert  che  si  usa  così:  
 
assert  condizione  report  “  ….  “  severity  severitycode  
 
Se  la  condizione  da  FALSE  viene  comunicato  su  uno  standard  output  oppure  su  un  file  ciò  
che   vi   è   fra   virgolette   e   viene   comunicato   al   simulatore   un   codice   di   severity   fra:   note,  
warning,   error,   failure.   Se   la   condizione   è   TRUE,   lo   statement   assert   non   viene   considerato.  
Dunque   assert   serve   per   gestire   e   monitorare   situazioni   che   potrebbero   creare   problemi  
durante  la  simulazione.    
Se  il  severitycode  è  note  viene  semplicemente  comunicato  al  simulatore  senza  arrestare  la  
simulazione,   per   gli   altri   codici   di   severity   si   sceglie   a   priori   se   interrompere   o   meno   la  
simulazione.   La   funzione   assert   risulta   utile   nel   caso   di   un   flip   flop   SR   per   il   quale   se   i   due  
ingressi  (S  e  R)  sono  entrambi  a  ‘1’  l’uscita  non  è  definita.    
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

                  
     


    
 
  
  
      !
 
       !
     !
"  
#
     $$$$$$$$!
  
#
      !
  
#
  %!

& ""     ' (         
     ' 

   

   "     


   )%$!*
   *
  )%$!*
" 
!*
  *

'     

+'   "                    
  "              

 

  ! 


      )%$!*
      )%$!*
  " *

,           -   


  !    ""  '   
   !   ""            )!.     

 
  $) 
 /$!'
  $!/  $! $!*
   $!/   $!!   $!!  $! $!!*
 
  !/   0#! ! !*
   !/    0#! !!    0#! !!  ! !!*
 *
  *

/   )!*


/  )!*
     
    $$$$$$$$   "
   /1$$$$$$$$1!'
"/
#
*
 
"/
$
*
 *

/  )! )!   )!!!  )!!  )!!  )!!*
    "  
2/#$*
2/  3*
"2/"#4*
2/#5*
 *
'   *

'   

   "      '       ' 
  "    6  '         

      )%$!*


     )%$!*

 

2/ * ,"   7 ""  '


   -  
 "

     ! 

 
  1  81 *    ""'    
  '        
 

  $) 
 /$!'
 $!2/  $! $!#*
   $!2/   $!!   $!!  $! $!!4*
 
 !2/   0#! ! !#*
   !2/    0#! !!    0#! !!  ! !!4*
 *
  *

2/   )!*


2/ )!*
  /1$$$$$$$$1!'
"2/
#
#*
 
"2/
$
#*
 *

2/  )! )!  )!!!  )!!  )!! )!!*

 *

' *

(                 
' 6"            9  
 -     ' 

   "":%      "   
   "    

6  
 "      "    

"'    " 


(                                      ;+    
      "" '
' "  

+'    ' 1   1        "" ' 

      )%$!*


     )%$!*

 

 $ %:  '!


   /< $!/< $! /< /< $!/<   $!!*

 # %:  '!


   /< #!/< #! /<   $!/< #!/<   #!!*

 4 %:  '!


   /< 4!/< 4! /<   #!/< 4!/<   4!!*

 = %:  '!


   /< =!/< =! /<   4!/< =!/<   =!!*

 > %:  '!


   /< >!/< >! /<   =!/< >!/<   >!!*

 5 %:  '!


   /< 5!/< 5! /<   >!/< 5!/<   5!!*

 ? %:  '!


   /< ?!/< ?! /<   5!/< ?!/<   ?!!*

 ) %:  '!


   /< )!/< )! /<   ?!/< )!/<   )!!*

" %:" '!


  /< "/<"!*

 %: % '!


   /< )!/< )!/< )!/<!*

2/ )!*
2/   )!*

2/ *

' "*
& "          '  '''
                  '
 ' 
 '*

' ' 

   " %  *


      )%$!*

 

  %:  ! 


      
'    @  A
@A@"A
   /< /</< /< /<"/<"/< %/< !*


 

 "       "
 2/1$$$$$$$$1*
 2/1$$$$$$$$1*
% 4$*
 4$       
 2/1########1*
 2/1$$$$$$$#1*
% 5$*
   5$        
 2/1$#$#$#$#1*
 2/1#$#$#$#$1*
% 5$*
   5$     '
% *
 *

' *

(  '  7   %:%'  


 " 
        

&       


' 0 '
' 0'  ""       !
' 0'
' 0'00/

-     :%


Digitale  II  Lezione  10  del  08/04/2011  
 
Andiamo   a   vedere   qualche   dettaglio   in   più   sulla   dichiarazione   di   una   entità.   Nella  
dichiarazione   di   una   entità,   ricordiamo,   abbiamo   la   parola   chiave   entity   seguita   da   un  
identificatore  cioè  il  nome  di  questa  entità,  ad  esempio:  

entity  and_or_invert_is  

  port  (a1,  a2,  b1,  b2:  in  bit:=  ‘  1  ‘;  y:out  bit);  

end  entity  and_or_invert  


 
attenzione   alla   definizione   delle   porte,   come   si   può   notare   c’è   la   possibilità   anche   di  
fissare  uno  stato  di  default,  in  questo  esempio  abbiamo  messo  bit  a  1.  Questo  significa  
che   se   nessun   segnale   esterno   risulta   collegato   alle   porte,   il   valore   assunto   da  
quest’ultime   è   proprio     quello   specificato   nella   definizione   delle   porte,   in   questo   caso  
sarà  1.  
All’interno   di   una   entità,   in   realtà,   possono   essere   anche   dichiarati   dei   segnali,   delle  
costanti   e   anche   dei   tipi.   Un’altra   cosa   importante   è   che   all’interno   di   una   entità   si  
possono  includere  dei  process  passivi.  L’unica  possibilità  che  hanno  questi  process  è  di  
esaminare  lo  stato  dei  segnali  ed  eventualmente  reagire  a  determinati  situazioni  con  la  
comunicazione   di   uno   stato.   Andiamo   a   vedere   a   cosa   possono   servire   questi   process  
passivi:   immaginiamo   di   aver   descritto   un   flip-­‐flop   sr,   allora   sappiamo   che   questo  
funziona   nel   modo   semplice   che   conosciamo   purché   non   ci   si   trovi   mai   nella   condizione  
in  cui  entrambi  gli  ingressi  passino  dallo  stato  11  allo  stato  00.  Questa  situazione  deve  
essere   evitata   perché   nessuna   descrizione   del   sistema   per   quanto   dettagliata   può  
essere,  non  può  descrivere  questo  comportamento.  Allora  per  evitare  che  ciò  avvenga,  
possiamo   mettere   al   livello   di   entità   una   specie   di   sistema   di   controllo   che   in  
conseguenza  di  una  transizione  degli  ingressi  verifichi  che  la  transizione  non  sia  quella  
da  11  a  00,  se  ciò  accade  il  process  può  dare  una  segnalazione  attraverso  una  istruzione  
di  assert,  che  ci  avvisa  sul  fatto  che  è  avvenuta  una  transizione  da  11  a  00.  
Questi  process  passivi  li  inseriamo  all’interno  delle  entità  per  evitare  di  ripeterli  in  ogni  
singola  architettura,  cosi  da  evitare  eventuali  errori  che  potrebbero  nascere  nel  ripetere  
più  volte  la  stessa  istruzione,  errori  di  cui  sarebbe  poi  difficile  accorgerne  sì.  
Ad   ogni   entità   corrispondono   una   o   più   architetture.   Ogni   architettura   si   può   definire  
nello   stesso   file   o   in   un   altro   file   differente.   L’unico   vincolo   è   che   prima   si   deve  
analizzare  l’entità  e  poi  si  analizza  l’architettura  per  verificare  se  è  stato  fatto  corretto  
riferimento  all’entità.  
 
 
La  struttura  di  una  architettura  nella  sua  formo  più  semplice  è  la  seguente:  
 
architecture  nome  of  nome_entity  is  
qui  bisogna  inserire  la  parte  dichiarativa  nella  quale  possono  essere  dichiarati:  
tipi,  funzioni,  procedure  e  i  segnali.  
Dopo  questa  parte  avremo  
 Begin  
  qui  va  inserita  la  lista  degli  statement  concorrenti  
end  architecture  nome;  
 
la   lista   degli   statement   concorrenti   è   formata   da   blocchi   di   testo   che   descrivono   il  
comportamento   dell’entità   e   l’ordine   con   cui   si   presentano   questi   blocchi   non   è  
rilevante,   perché   ognuno   è   autonomo   rispetto   all’altro.   Tipicamente   questi   statement  
concorrenti   sono   processi   (impliciti/espliciti)   oppure   possono   essere   dei   riferimenti   al  
altre  entità.  
Dunque   il   nucleo   di   questa   struttura   è   il   process.   Bisogna   ricordare   che   tutti   i   process  
vengono   attivati,   indipendentemente   dalla   stato   del   segnale,   all’inizio   della   fase   di  
simulazione.  
 
Esempio  
Supponiamo  che  ad  un  certo  punto  l’architettura  di  un  sistema  sia  questa:  
 
entity  yy  is  
  port  is  (  ck:out  bit);  
end  entity  yy;  
 
architecture  xx  of  yy  is  
  begin  
    ck<=  ‘  1  ‘  after  10ns,  ‘  0  ‘  after  20ns;  
end  architecture  xx;  
 
provare  a  vedere  quello  che  succede  prima  di  avviare  la  simulazione  sulla  base  di  quello  
che  abbiamo  detto  a  proposito  dell’assegnazione  dei  segnali.  
 
 
Quello  che  di  solito  vogliamo  fare  oltre  a  descrivere  se  dobbiamo  verificare  lo  stato  di  
un  sistema,  non  è  tanto  vedere  se  in  ingresso  ci  sia  un  uno  e  in  uscita  ci  sia  uno  zero,  ma  
dobbiamo   verificare   che   nel   corso   del   funzionamento   gli   ingressi   siano   stabili   prima  
dell’arrivo  del  fronte  di  clock  e  che  rimangano  stabili  per  un  certo  tempo  dopo  l’arrivo  
del  fronte  di  clock.    
Per  controllare  cose  del  genere,  dobbiamo  ricordare  che  ciò  costituisce  “il  motore”  della  
simulazione  VHDL  è  il  fatto  che  in  corrispondenza  di  un  evento  su  un  segnale,  cioè  un  
cambiamento   da   un   valore   ad   un   altro   del   segnale,   può   essere   attivato   un   process   se  
quest’ultimo  ha  il  suddetto  segnale  nella  sensitivity  list.  Allora,  praticamente,  se  siamo  
nell’istante   in   cui   è   avvenuta   la   transizione   del   clock,   bisogna   segnare   l’istante   di   tempo  
in  cui  ciò  è  avvenuto;  cioè  se  abbiamo  un  process  sensibile  a  questi  segnali  d’ingresso  ci  
possiamo   segnare   in   una   variabile   qual   era   il   valore   dell’istante   di   tempo   della  
simulazione   in   cui   sono   avvenute   queste   transizioni.   Dopo   di   ché   nel   process   che   è  
sensibile   al   clock   possiamo   andare   a   controllare   qual   è   il   tempo   attuale   e   vedere   se   è  
passato   abbastanza   tempo.   Siccome   in   VHDL   questa   operazione   si   fa   continuamente,  
esistono  quelli  che  si  chiamano  attributi  di  segnale  che  forniscono  informazioni  su  cosa  
è  avvenuto  nel  tempo  precedente,  su  cosa  sta  succedendo  nello  stesso  istante  di  tempo  
in   cui   un   altro   segnale,   per   esempio,   ha   subito   una   transizione.   Alcuni   di   questi   attributi  
possono  anche  essere  utilizzati  per  far  svolgere  delle  funzioni.  
Vediamo  prima  gli  attributi  di  segnale  tipicamente  utilizzati,  per  esempio,  in  un  process  
passivo  che  ha  il  compito  di  controllare  se  sta  andando  tutto  bene.  
Allora  supponiamo  per  esempio  che  “S”  sia  il  nome  di  un  segnale  che  è  stato  definito  
all’interno   di   un’architettura   e   che   ad   un   certo   punto   siamo   all’interno   di   un   process  
perché  c’è  stata  una  transizione  di  qualche  segnale,  per  esempio  il  clock.  Quindi:  
 
process  (ck)  is  
  begin  
    S’stable  (T)  -­‐-­‐     “T”  è  una  costante  che  indica  il  tempo.  
Se   siamo   entrati   in   questo   process,   il   tempo   è   stato   bloccato   nell’istante   in   cui   il   clock   è  
diventato  alto  per  esempio.  
 

In   particolare   S’stable(T)   è   un   valore   Boolean   che   è   “true”   se   non   c'è   stato   alcun   evento  
su  S  nell'intervallo  di  tempo  di  durata  T  immediatamente  precedente  al  tempo  attuale.  

S'quiet(T)   è   un   valore   Boolean   che   è   “true”   se   non   c'è   stato   alcuna   transazione   su   S  
nell'intervallo  di  tempo  di  durata  T  immediatamente  precedente  al  tempo  attuale.  

S'transaction  è  un  segnale  di  tipo  bit  cha  cambia  valore  da  '0  '  a  '1'  o  viceversa  tutte  le  
volte  che  c'è  una  transazione  su  S.  

S'event  è  un  valore  Boolean  che  è  “true”  se  c'è  un  evento  su  S  nel  ciclo  di  simulazione  
corrente.  

S'active   è   un   valore   Boolean   che   è   “true”   se   c'è   una   transazione   su   S   nel   ciclo   di  
simulazione  corrente.  

S'last_event  indica  il  tempo  dall'ultimo  evento  


S'last_active  indica  il  tempo  dall'ultima  transazione  

S'last_value  indica  il  valore  di  S  prima  dell'ultimo  evento  su  S    

S'last_event  indica  il  tempo  dall'ultimo  evento  

S'last_active  indica  il  tempo  dall'ultima  transazione  

S'last_value  indica  il  valore  di  S  prima  dell'ultimo  evento  su  S  

S’delayed(T)  è  un  process  implicito  che  serve  per  simulare  un  ritardo.  

Questi  attributi  sono  particolarmente  utili  nella  fase  di  simulazione  per  il  controllo  del  
rispetto  delle  specifiche  di  sistema  mediante  “assert”.  

Uno   di   questi   attributi   che   useremo   spesso   è   S’event   che   riguarda   proprio   il   modo   di  
descrivere  una  macchina  a  stati  sequenziale  sincronizzata  

  in   out  

  Reset   clock  

  all’inizio   dei   tempi   cioè   quando   accendiamo   questo  


Il   segnale   di   Reset   serve   perchè  
sistema  ci  si  porti  nello  stato  iniziale  del  funzionamento.  In  queste  macchine  sequenziali  
viene   elaborato   un   nuovo   stato   interno   p   esterno   solo   quando   una   tra   Reset   e   Clock  
cambia   stato.   Per   descrivere   una   cosa   del   genere,   la   descrizione   tipica   è   la   seguente:  
all’interno  dell’architettura  avremo  

process  (Reset,  ck)  is  


  begin  
    if  Reset=  ‘0’  then  
    ………………………….  
    ………………………….  
    ………………………….  
    else  
      if  (ck’event  and  ck=  ‘1’)  then  
      …………………………………………  
      …………………………………………  
      end  if;  
    end  if;  
end  process;  
 
Assert  statement  
 

 
 

 
 
 
 
 
 
 
 
 
 
 
 
 
Se   sappiamo   che   I   tempi   di   tsetup   e   thold   sono   rispettati,   allora   il   funzionamento   è  
quello  atteso.  
 
 
 
assert ((time_last_ck_rise-time_last_change)>T_setup) report
"time setup violation" severity error;
end if;
if (d'event) then
time_last_change:=now;
assert ((time_last_change-time_last_ck_rise)>T_hold) report
"time hold violation" severity error;
end if;
end if;
end process timing;
end entity ffd;

architecture behav of ffd is

begin
flip_flop:process (reset,ck,d) is
begin
if (reset='0') then
uscita<='0';
else
if (ck'event and ck='1') then
uscita <=d after 3 ns;
end if;
end if;
end process flip_flop;
end architecture behav;

Si può, nell'ipotesi semplificativa in cui si abbia che la durata del semiperiodo positivo del clock è
sempre superiore al tempo di hold, riscrivere l'entità precedente facendo ricorso agli attributi dei
segnali. In particolare ck'stable(T) produce true solo se il segnale ck è rimasto stabile nei T secondi
precedenti al tempo di simulazione in cui viene valutato. Si noti inoltre che, se si scrive la
dichiarazione di entità nel modo che segue, nel caso particolare in cui il dato cambia nello stesso
istante in cui il clock va alto, si hanno entrambe le segnalazioni di violazione del tempo di setup e
del tempo di hold.

entity ffd is
port(reset,ck, d:in bit; uscita:out bit);

constant T_setup:time:=5 ns;


constant T_hold:time:=5 ns;
begin
timing: process (reset,ck,d) is
begin
if (reset/='0') then
if (ck'event and ck='1') then
assert d'stable(T_setup) report "time setup violation"
severity error;
end if;
if (d'event) then
assert (ck='0' or (ck='1' and ck'stable(T_hold))) report "time
hold violation" severity error;
end if;
end if;
end process timing;
end entity ffd;

L'argomento dell'assert che controlla il tempo di hold merita qualche attenzione. Quando il dato
cambia, si possono distingure 4 casi distinti:
1. Il dato cambia mentre il segnale di clock è a '1';
2. Il dato cambia mentre il segnale di clock è a '0';
3. il dato cambia nello stesso istante in cui il clock diventa '1';
4. il dato cambia nello stesso istante in cui il clock diventa 0;
Immaginiamo per semplicità che il clock sia periodico.
Se siamo nella condizione 2 o nella condizione 4, l'argomento dell'assert restituisce sempre true
grazie al fatto che ck='0' sarebbe true. In ragione dell'ipotesi semplificativa assunata (semiperiodo
positivo del clock di durata superiore al tempo di hold), inquesto caso non si avrebbe certamente
una violazione del tempo di hold. Se siamo nella condizione 1 o 3, allora ck='0' sarebbe false e si
controlla effettivamente che quando il dato cambia, il clock sia rimasto alto per un tempo superiore
al tempo di hold. In particolare, se siamo nel caso 3 si ha certamente una assert violation poiché
ck'stable(T_hold) sarebbe false per qualunque valore positivo di T_hold.
Senza l'ipotesi semplificativa, nel caso in cui il dato cambi dopo (o in coincidenza con) il fronte di
discesa del clock, non possiamo risalire, mediante il risorso ai soli attributi dei segnali, al tempo
trascorso dall'ultimo fronte di salita del clock.
File assert_sr.vhd
entity sr is
port (s,r: in bit; q, qn: out bit);
end entity sr;

architecture datapath of sr is
signal ua:bit:='1';
signal ub:bit:='0';
begin
ua<=r nor ub;
ub<=s nor ua;
q<=ua;
qn<=ub;
controllo: process (s,r)
begin
assert ((s/='1')or(r/='1')) report "Errore: entrambi gli ingressi a 1!"
severity error;
end process controllo;
end architecture datapath;

entity test_bench is
end test_bench;

architecture behav of test_bench is


signal in1,in2:bit;
signal u1:bit;
signal u2:bit;
begin
flip:entity work.sr(datapath)
port map (in1,in2,u1,u2);
simula: process is
begin
in1<='0';
in2<='0';
wait for 10 ns;
in1<='1';
wait for 10 ns;
in1<='0';
wait for 10 ns;
in2<='1';
wait for 10 ns;
in1<='1';
wait for 10 ns;
in2<='0';
wait for 10 ns;
in1<='0';
wait for 20 ns;
wait;
end process simula;
end behav;

Per eseguire la simulazione in ghdl (windows):


ghdl -a aasert_sr.vhd
ghdl -e test_bench
ghdl -r test_bench –assert-level=error --vcd=out.vcd
winwave out.vcd
File assert_sr_pass.vhd
entity sr is
port (s,r: in bit; q, qn: out bit);
begin
contr: process (s,r)
begin
assert ((s/='1')or(r/='1')) report "Uso improprio del flip flop" severity
error;
end process contr;

end entity sr;

architecture datapath of sr is
signal ua:bit:='1';
signal ub:bit:='0';
begin
ua<=r nor ub;
ub<=s nor ua;
q<=ua;
qn<=ub;
end architecture datapath;

entity test_bench is
end test_bench;

architecture behav of test_bench is


signal in1,in2:bit;
signal u1:bit;
signal u2:bit;
begin
flip:entity work.sr(datapath)
port map (in1,in2,u1,u2);
simula: process is
begin
in1<='0';
in2<='0';
wait for 10 ns;
in1<='1';
wait for 10 ns;
in1<='0';
wait for 10 ns;
in2<='1';
wait for 10 ns;
in1<='1';
wait for 10 ns;
in2<='0';
wait for 10 ns;
in1<='0';
wait for 20 ns;
wait;
end process simula;
end behav;

Per eseguire la simulazione in ghdl (windows):


ghdl -a assert_sr_pass.vhd
ghdl -e test_bench
ghdl -r test_bench –assert-level=error --vcd=out.vcd
winwave out.vcd
File ffd_assert.vhd
entity ffd is
port(reset,ck, d:in bit; uscita:out bit);

constant T_setup:time:=5 ns;


constant T_hold:time:=5 ns;
begin
timing: process (reset,ck,d) is
variable time_last_change,time_last_ck_rise:time:=0 ns;
begin
if (reset/='0') then
if (ck'event and ck='1') then
time_last_ck_rise:=now;
assert ((time_last_ck_rise-time_last_change)>T_setup) report
"time setup violation" severity error;
end if;
if (d'event) then
time_last_change:=now;
assert ((time_last_change-time_last_ck_rise)>T_hold) report
"time hold violation" severity error;
end if;
end if;
end process timing;
end entity ffd;

architecture behav of ffd is

begin
flip_flop:process (reset,ck,d) is
begin
if (reset='0') then
uscita<='0';
else
if (ck'event and ck='1') then
uscita <=d after 3 ns;
end if;
end if;
end process flip_flop;
end architecture behav;

entity test_bench is
end entity test_bench;

architecture behav of test_bench is

signal test_reset,test_ck,test_d:bit:='0';
signal test_out:bit;
begin

flip_ffd:entity work.ffd(behav)
port map (test_reset,test_ck,test_d,test_out);
simula:process is
begin
test_reset<='0';
wait for 20 ns;
test_reset <='1';
wait for 26 ns;
test_d<='1';
wait for 4 ns;
test_ck<='1';
wait for 5 ns;
test_d<='0';
wait for 5 ns;
test_ck<='0';
wait for 10 ns;
test_ck<='1';
wait for 2 ns;
test_d<='1';
wait for 10 ns;
test_d<='0';
test_ck<='0';
wait for 20 ns;
test_d<='1';
test_ck<='1';
wait for 20 ns;
wait;

end process simula;


end architecture behav;

Per eseguire la simulazione in ghdl (windows):


ghdl -a ffd_assert.vhd
ghdl -e test_bench
ghdl -r test_bench –assert-level=error --vcd=out.vcd
winwave out.vcd
Procedure e funzioni

Le procedure e le funzioni vengono usare per raggruppare insiemi di statement sequenziali che
possono essere poi eseguiti in blocco all'interno di un process mediante il solo riferimento al nome
della specifica funzione o procedura. Sia le procedure sia le funzioni rientrano nella più generale
definizione di sottoprogrammi. La differenza fra una procedura e una funzione sta sostanzialmente
nel fatto che una procedura viene definita e richiamata per ottenere l'effetto derivante dagli
statement contenuti al suo interno; in una funzione, lo scopo degli statement che che costituiscono
la sua definizione è principalmente quello di calcolare un valore che viene associato al nome della
funzione, il quale può apparire come argomento di operazioni di assegnazione o di altre espressioni.
Sia le procedure, sia le funzioni, possono essere definite in termini di una lista di “parametri
formali” sui quali operare. Questa lista può comprendere costanti, variabili e segnali. Gli effettivi
segnali, variabili e constanti con riferimento ai quali si eseguono le azioni specificte dalla procedura
o dalla funzione sono volta per volta quelli specificati al momento dell'uso della procedura o della
funzione stessa all'interno di un process.

Procedure
La definizione di una procedura (procedure) segue la sintassi generale specificata di seguito:

procedure_definition<=
procedure identifier [[( parameter_interface_list ) ]] is
{subprogram_declarative_part}
begin
{sequential_statement}
end procedure identifier;

La sezione realtiva ai parametri formali della procedura (parameter_interface list) , come si vede, è
opzionale. La specifica dei parametri formali di una procedura o funzione, in vhdl, avviene
ricorrendo alle parole chiave in, out e inout come nel caso della definizione delle porte di una
entità. Nel caso della definizione di procedure, il significato delle parole chiave in, out e inout
dipende dal tipo di parametro formale che viene specificato. La sintassi per la specifica della
parameter_interface_list è la seguente:

interface_list<=
(( [[constant || variable || signal ]] identifier {,...} : [[mode ]] subtype_indication
[[:=static_expression ]] )) {,...}

mode<= in || out || inout

All'interno della parte dichiarativa (subprogram_declarative_part) possono essere definiti non solo
tipi, sottotipi, costanti e variabili, ma anche sottoprogrammi e funzioni che saranno localmente
visibili solo all'interno della procedura nella quale sono definiti.

Una procedura può ossere dichiarata all'interno della sezione dichiarativa di un process, nel qual
caso la procedura è esclusivamente visibile all'interno del process; può essere dichiarata nella
sezione dichiarativa di una architettura, nel qual caso è visibile a tutti i process all'interno di quella
architettura; può essere definita all'interno di un package e può quindi essere resa disponibile a più
entità.
Per quanto riguarda la interface_list, distinguiamo il caso in cui si voglia fare riferimento a un
segnale dagli altri casi. Discuteremo pertanto più avanti il caso dei parametri di tipo segnale.
Se ci limitiamo al caso di variabili e costanti, si deve osservare che l'uso della parola chiave
constant associata alla specifica di modo 'in' è ridondante. Infatti, quando un parametro che non sia
un segnale viene specificato di tipo “in” questo è comunque di tipo costante, nel senso che
all'interno della procedura si può solo leggere il valore del parametro ma non si può modificarlo in
ogni caso. Con l'eccezione del caso dei segnali, quindi, quando si vuole fare rigferimento a un
parametro di tipo 'in' si può omettere la specifica constant o variable. All'atto dell'uso della
procedura, infatti, non ha importanza se il parametro è stato dichiarato di tipo variable o di tipo
constant se il modo è 'in'. Il VHDL agisce in ogni caso come se si strattasse di un valore costante e
immodificabile. Se all'atto della chiamata si inserisce una variabile nella posizione di un parametro
con modo 'in', quello che si sta' facendo è equivalente a inserire una cosatnte i cui valore è pari a
quello assunto dalla variabile nell'istante della chiamata. Si faccia riferimento all'esempio
segnuente, nel quale si definisce una semplice procedura che produce sullo schermo un numero di
linee di report pari al valore del parametro passato. Nonostante che il parametro sia definito di tipo
costante, poiché il modo è 'in', solo lecite tutte e tre le chiamate introdotte all'interno del process e
tutte e tre producono lo stesso effetto perché la costante ha valore 3 e la variabile, all'atto della
chiamata, ha anch'essa il valore 3.

entity test_proc is
end entity test_proc;

architecture test of test_proc is

constant b:integer:=3;

procedure prova (constant k:in integer) is


variable i:integer:=0;
begin
for i in 1 to k loop
assert false report "stampa di prova" severity note;
end loop;
end procedure prova;

begin

process is

variable a:integer:=3;
begin
prova(3);
assert false report "----------------------------------"
severity note;
prova(a);
assert false report "__________________________________"
severity note;
prova(b);
wait;
end process;
end architecture test;

Se si specifica il modo 'out' si intende specificare che il parametro passato non può essere “letto”
all'interno della procedura, ma si può assegnare ad esso un valore. E' quindi evidente che un
parametro di tipo out non può essere una costante. Se non si specifica nulla, si assume che si tratta
di una variabile. L'unica altra possibilità, che discuteremo in seguito, è che si voglia fare riferimento
a un segnale, nel qual caso la parola chiave signal è obbligatoria. Nel caso in cui il modo è 'inout' si
intende fare riferimento a un parametro che può essere letto e/o scritto all'interno della procedura.
Anche in questo caso il parametro non può essere una costante. Se non viene esplicitamente usata la
parola chiave signal, si sottindente che si sta facendo riferimento a un parametro di tipo variabile. Si
osservi l'esempio seguente:

entity test_proc is
end entity test_proc;

architecture test of test_proc is

procedure prova (k:inout integer) is


variable i:integer:=0;
begin
for i in 1 to k loop
assert false report "stampa di prova" severity note;
end loop;
k:=k+1;
end procedure prova;

begin

process is

variable a:integer:=1;
begin
prova(a);
assert false report "----------------------------------"
severity note;
prova(a);
assert false report "__________________________________"
severity note;
prova(a);
wait;
end process;
end architecture test;

Nell'esempio precedente, la prima volta che la procedura prova(a) viene invocata, a=1; la seconda
volta il valore di a è 2 (a viene incrementato di 1 all'interno della procedura stessa), la terza volta a
vale 3.

La situazione nel caso di parametri di tipo signal è un po' differente.

Nel caso in cui si passi un segngale specificando il modo 'in', non viene passato il valore del
segnale, ma il segnale vero e proprio. Come conseguenza di questo fatto, se il processo contiene
uno statement di tipo wait (per esempio wait for 100 ns) e durante il tempo di attesa il segnale viene
modificato da un altro processo, il valore del segnale dopo lo statement wait è diverso dal valore
prima del wait!.
Nel caso in cui il parametro di modo di un segnale è 'out', allora all'interno della procedura si
possono programmare transizioni per il segnale proprio come si farebbe all'interno di un process.
Nel caso in cui il modo è inout, si può leggere il valore attuale del segnale e programmarne le
transizioni, esattmente come si farebbe in un process.
Una procedura può contenere una lista di numerosi parametri formali. La corrispondenza fra i
parametri passati e i parametri formali avviene sulla base dell'ordine in cui questi compaiono nelle
parentesi, oppure ricorrendo alla sintassi:

parametro_formale=>parametro_passato, nel qual caso l'ordine non ha importanza.


Per esempio, se una procedura è definita nel modo seguente:

procedure pippo (a,b:in integer, signal s:integer) is

All'atto della chiamata, suppoendo che x e y siano variabili intere e z un segnale di tipo intero, le
seguenti scritture sono equivalenti:

pippo (x,y,z);

pippo (s=>z, a=>x,b=>y);

Una particolarità delle procedure in VHDL è che è possibile prevedere un valore predefinito per i
parametri costanti (con modo 'in'). Per indicare che si vuole fare riferimento al valore predefinito si
può usare la parlo chiave open come nell'esempio seguente:

procedure paperino (a:in integre:=1; b:in integer:=2; c:in


integer:=3) is

la chiamata seguente

paperino (x, open,z);


equivale a
paperino (x,2,z);

Infine, se il parametro per il quale si vuole specificare il valore di default è l'ultimo della lista, si
può semplicemente ometterlo. Pertanto, le seguenti chiamate sono equivalenti:

paperino (x,2,open);
paperino (x,2);

Si può determinare la immediata fine dell'esecuzione della procedura mediante il ricorso alla parola
chiave return.

Per esempio:
procedure topolino(a:in integer) is

begin
if (a=0) then
return;
else
…..
….
end if;
end procedure topolino;
Nell'esempio precedente, se la procedura viene chiamata con un alore del parametro pari a 0, non
viene eseguita alcuna operazione giacché l'esecuzione della procedura termina all'atto della
esecuzione dell'istruzione di return.

Uso dei parametri di tipo array “uncostrained”.

Nella definizione di un tipo array si può intenzionalmente omettere la spcifica della dimensione
dell'arrei stesso. La dimensione dell'array di una variabile appartenente al tipo viene dichiarata
all'atto della dichirazione di una variabile appartenente al tipo.

Per esempio protremmo definire:

type sample is array (integer range < > ) of integer;

L'uso dei simboli “< >” indica proprio che la dimensione (ovvero del numero di elementi) delle
variabili array di questo tipo non è definito a priori ma verrà specificato all'atto della dichiarazione
delle variabili stesse.
Così si potrà avere:

variable short_sample_buf: sample (0 to 63);


variable long_sample_buf:sample(0 to 1023);

La definizione della dimensione dell'array all'atto della definizione delle variabili non è una
particolarità esclusiva del VHDL. Questo accade normalmente nel linguaggio C, per esempio.

Le procedure possono accettare come parametri dei tipi “unconstrained” e questo è molto utile per
definire delle procedure cje svolgano operazioni su array senza che sia necessario conoscere a priori
la dimensione dell'array che costituirà il parametro effettivo all'atto della chiamata.
Si noti che il tipo predefinito bit_array è un tipo unconstrained.
Supponiamo per esempio di voler definire una procedura che calcoli il numero di bit che sono a uno
in un array, indipendentemente dal numero di elementi dell'array che di volta in volta verrà passato
alla procedura. Nell'esempio che segue, frutteremo ancora, come in precedenza, lo statement assert
per stampare sullo schermo il valore calcolato.

entity test_proc is
end entity test_proc;

architecture test of test_proc is

procedure prova (k:in bit_vector) is


variable i:integer:=0;
variable count:integer:=0;
begin
for i in k'range loop
if k(i)='1' then
count:=count+1;
end if;
end loop;
assert false report "Il numero di bit
risulta"&integer'image(count) severity note;
end procedure prova;

begin
process is

variable a:bit_vector(0 to 7):="01011010";


variable b:bit_vector(15 downto 0):="1111100000011111";
begin
prova(a);
prova(b);
wait;
end process;
end architecture test;

Funzioni

Per molti versi le funzioni sono simili alle procedure. La particolarità delle funzioni è quella di
“restituire un valore” come risultato dell'elaborazione, valore che è assoziato al nome stesso della
funzione.
La sintassi generale è del tipo:

function_definition_definition<=
[[pure || impure ]]
function identifier [[( parameter_interface_list ) ]] return type_mark is
{subprogram_declarative_part}
begin
{sequential_statement}
end function identifier;

In generale, procedure e funzioni hanno accesso alle variabili definite dalle strutture “genitori”,
ovvero alle altre funzioni o procedure, processi o architetture all'interno delle quali sono definite.
Una funzione si dice pura se nella sua definizione non si fa riferimento a variabili o segnali definiti
all'esterno di essa. Chiaramente, una funzione pura chiamata con gli stessi parametri (che assumono
all'atto di due chiamate distinte gli stessi valori) produce sempre lo stesso risultato. Una funzione
impura non si comporta necessariamente in questo modo, poiché se il risultato del calcolo dipende
da altre variabili oltre a quelle formali, il risultato può essere diverso anche se i valori assunti dai
parametri all'atto di due chiamte distine sono gli stessi. Se non si specifica il tipo di funzione, questa
viene assunta di tipo “pure” e se nel codice di definizione si fa riferimento a variabili esterne, il
compilatore produce unmessaggio di errore. Nella grande maggioranza dei cai si fa esclusivo
riferimento a funzioni di tipo “pure”. Rispetto alle procedure, si deve specificare il tipo del valore
restituito dalla funzione e associato al suo nome. Una funzione puà essere usata come elemento di
una operazione compatibile con il suo tipo. Per esempio se pippo(a,b,c) è la chiamata a una
funzione di tipo intero e se x e y sono variabili di tipo intero, la scrittura:
y:=x+pippo(a,b,c);
ha perfettamente senso e si avrà che a y viene assegnato la somma algebrica fra il valore di x e il
valore restituito dalla funzione. Nella definizione della funzione, prima che l'esecuzione sia
completata, occorre specificare il valore restituito mediante l'istruzione

return valore;

Sotto ogni altro aspetto, per le funzioni vale quanto detto per le procedure. Riscriviamo l'esempio
precedente (conteggio dei bit a '1' in una array di bit) facendo ricorso a una funzione piuttosto che a
una procedura. In questo caso supponiamo che il valore restituito dalla funzione sia proprio dil
risultato del conteggio.

entity test_proc is
end entity test_proc;

architecture test of test_proc is

function prova (k:in bit_vector) return integer is


variable i:integer:=0;
variable count:integer:=0;
begin
for i in k'range loop
if k(i)='1' then
count:=count+1;
end if;
end loop;
return count;
end function prova;

begin

process is

variable a:bit_vector(0 to 7):="01011010";


variable b:bit_vector(15 downto 0):="1111100000011111";
begin
assert false report "Il numero di bit
risulta"&integer'image(prova(a)) severity note;
assert false report "Il numero di bit
risulta"&integer'image(prova(b)) severity note;
wait;
end process;
end architecture test;
Elettronica+dei+Sistemi+Digitali+2+3+LEZIONE+13+

+
Procedure"prova"(variable"a,"b:"in"integer;"signal"s:"in"integer)"is"
"
Facciamo"una"osservazione"sull’utilizzo"di"segnali"per"quanto"riguarda"le"procedure."
Con"le"variabili"e"con"le"costanti"scrivere"in"significa"che"quello"che"viene"passato"alla"procedura"è"il"
valore" della" variabile" o" costante" in" quel" momento." È" un" valore" costante," quindi" quando" si" fa"
riferimento" alla" variabile" che" è" stata" passata" si" ritroverà" sempre" e" comunque" lo" stesso" valore."
D’altra"parte"non"c’è"motivo"che"le"variabili"cambino,"nel"senso"che"le"variabili"sono"visibili"soltanto"
all’interno"di"un"process."I"segnali"invece"possono"cambiare"per"effetto"di"diversi"process."Siccome"
i"sistemi"sono"concorrenti,"un"segnale"può"essere"cambiato"da"più"process.""
Se"in"una"procedura"è"passato"un"segnale,"quello"che"si"passa"non"è"il"segnale"ma"è"un"riferimento"
al" segnale." Vediamo" che" conseguenza" ha" questo" fatto." Immaginiamo" che" nel" corpo" della"
procedura"ad"un"certo"punto"ci"sia"scritto:"
"
…….."
wait"for"20"ns;"
p"<="s"
…….."
"
L’istruzione"wait"scritta"sopra"sospende"l’esecuzione"del"process"finchè"non"siano"passati"20"ns."Se"
in"questo"tempo"un"altro"process"ha"cambiato"il"valore"del"segnale"s,"ci"ritroviamo"il"nuovo"valore"e"
non" quello" che" avevamo" all’ingresso" della" procedura." Questo" proprio" perché" viene" passato" un"
collegamento,"un"filo","che"arriva"su"s.""
Se" non" c’è" il" wait" comunque" la" procedura" viene" eseguita," dal" punto" di" vista" dello" scorrere" del"
tempo" di" simulazione," in" un" tempo" nullo." Ma" se" abbiamo" istruzioni" del" tipo" “aspetta" un" po’" di"
tempo”," se" il" valore" di" s" viene" modificato" da" un" altro" processo," esso" si" ritrova" modificato" anche"
nello" statement" sopra," cioè" a" p" viene" assegnato" il" valore" di" s" nell’istante" in" cui" si" sta" facendo" la"
simulazione,"e"non"nell’istante"di"inizio"dell’esecuzione"del"process."
Questo"dobbiamo"tenerlo"presente"perché"se"associamo"l’idea"di"passaggio"per"valore"di"in"che"è"
vera"per"variabili"e"costanti,"essa"non"è"vera"per"i"segnali."
Passaggio"di"un"segnale"all’interno"di"una"procedura"significa"estendere"il"collegamento"del"filo"che"
rappresenta"il"segnale"all’interno"della"procedura."Per"cui,"se"ad"un"certo"punto"nella"procedura"c’è"
un"ritardo"come"quello"dell’esempio"sopra"e"qualche"altro"process"cambia"il"valore"di"s,"il"valore"di"
s"si"ritrova"cambiato"nel"momento"in"cui"programmiamo"la"transizione"p"<="s."
"
"
Note"sulla"sintassi."
Vediamo"il"modo"di"passare"dei"parametri"per"una"procedura"nell’atto"in"cui"si"utilizza"all’interno"di"
un"process."
"
1"
"
Process"..."
."
."
."
Prova"(x,"y,"z)"
"
Tra" parentesi" abbiamo" i" parametri" attuali." L’associazione" tra" i" parametri" attuali" ed" i" parametri"
formali" è" dettato" dall’ordine:" a" significa" x," b" significa" y," s" significa" z." Nell’esecuzione" della"
procedura"viene"utilizzato"s"ovunque"si"trovi"z,"b"ovunque"si"trovi"y,"x"ovunque"si"trovi"a."Questo"in"
qualunque"linguaggio"di"programmazione"e"anche"in"VHDL.""
In"vhdl"si"possono"usare"delle"forme"di"associazione"tra"parametri"formali"e"parametri"attuali"che"si"
usano" anche" per" effettuare" i" collegamenti" tra" segnali" e" porte" d’ingresso." Si" può" cioè" dire"
esplicitamente"a"quale"parametro"formale"viene"associato"ciascun"parametro"attuale"in"modo"che"
non" ci" si" debba" ricordare" l’ordine." Analogamente" a" come" si" fa" l’associazione" tra" porte" e" segnali"
possiamo"quindi"scrivere:"
"
Prova"(a"=>"x,"b"=>"y,"s=>z)"
"
In"questa"maniera"non"conta"l’ordine"ma"l’associazione"diretta"che"viene"scritta"esplicitamente."
"
Parlando" di" procedure" stiamo" intendendo" anche" le" funzioni" di" cui" parleremo" meglio" dopo."
Vediamo" un’altra" cosa" che" si" può" fare" con" le" procedure." Per" i" parametri" di" tipo" in" si" può" anche"
specificare," all’atto" della" definizione" della" funzione," un" valore" di" default." Se" mantenessimo" la"
sintassi"più"comune,"cioè"quella"per"cui"l’associazione"tra"i"parametri"attuali"e"i"parametri"formali"
deve"essere"fatta"in"base"all’ordine,"questa"cosa"avrebbe"poco"senso"perché"ogni"volta"che"usiamo"
la"procedura"dovremmo"elencare"nell’ordine"i"parametri""che"sono"presenti."Abbiamo"però"visto"
che"è"possibile"utilizzare"l’altra"scrittura,"quella"con"cui"è"possibile"specificare"il"collegamento"tra"
ogni" parametro" formale" ed" ogni" parametro" attuale." In" questa" maniera" abbiamo" la" possibilità" di"
dare"dei"valori"predefiniti"ad"alcuni"degli"ingressi"della"procedura."In"questo"caso"questa"cosa"ha"
senso,"per"cui"se"non"specifichiamo"nulla"per"quel"parametro,"esso"assume"il"valore"di"default."
Possiamo"ad"esempio"avere"la"seguente"definizione"di"procedura:"
"
Procedure"prova1(a:"in"integer:=1;"b:"in"integer:=2;"c:"in"integer:=3)"is"
"
Ricordiamo"che"se"non"specifichiamo"la"parola"chiave"si"tratta"di"una"costante."
Immaginiamo" che" abbiamo" un" process" con" all’interno" degli" statement" e" poi" una" chiamata" a"
procedura:"
"
Process"
."
."
."
Prova1"(5,"7,"9);"
"

2"
"
Quello" che" abbiamo" scritto" vuol" dire" eseguire" tutti" gli" statement" che" ci" sono" all’interno" della"
procedura"con"5"al"posto"di"a,"7"al"posto"di"b"e"9"al"posto"di"c."In"maniera"alternativa"avremmo"
potuto"scrivere:"
"
Prova1"(b"=>"7,"c"=>"18,"a"=>"5);"
"
Se"per"uno"dei"parametri"vogliamo"utilizzare"il"valore"predefinito"utilizziamo"la"parola"chiave"open."
Esempio:"
"
Prova1"(5,"open,"18);"
"
Dove"si"incontra"la"parola"chiave"open"al"posto"di"un"parametro,"bisogna"prendere"come"valore"il"
valore" predefinito," cioè" quello" scritto" nella" fase" di" dichiarazione" della" procedura" (nel" nostro"
esempio"avremo"quindi"5,"2,"18)."
Queste"due"forme"sono"equivalenti:"
"
Prova1"(5,"7,"open);"
Prova1"(5,"7);"
"
Quando"cioè"si"vuole"utilizzare"come"valore"predefinito"quello"dell’ultimo"parametro"nella"lista"dei"
parametri"possiamo"non"scriverlo"neanche."Questa"cosa"può"avere"un"minimo"di"utilità"ad"esempio"
nel" caso" in" cui" si" voglia" scrivere" una" procedura" che" operando" su" un" registro" (un" array" di" 8" bit)"
faccia"lo"shift"a"sinistra"o"lo"shift"a"destra."Potremmo"scrivere"questa"procedura:"
"
Procedure"shift_l"("signal"Reg:"inout"registri,"posti:"in"integer:="1);"
" "
La"l"in"shift_l"sta"per"left."Supponiamo"che"Reg"sia"un"segnale"di"tipo"registri,"dove"registri"è"un"tipo"
che"abbiamo"definito"noi"e"che"è"un"bit_array"lungo"8"bit."
Immaginiamo" che" questa" procedura" legga" il" valore" del" registro" e" programmi" una" transizione" sul"
registro"in"cui"tutti"i"bit"saranno"spostati"di"1"verso"sinistra"e"il"bit"meno"significativo"venga"messo"a"
zero" (come" nella" funzione" shift" che" si" incontra" in" qualunque" microprocessore)." Le" operazioni" di"
shift"non"devono"essere"necessariamente"di"1,"possiamo"avere"shift"di"2,"3,"4,"etc."posti."Quando"
vogliamo"fare"lo"shift"a"sinistra"di"uno"di"questo"registro"che"chiamiamo"ad"esempio"accumulatore"
scriviamo:"
shift_l(accumulatore)"
Se"vogliamo"fare"lo"shift"di"3"posti"scriviamo:"
shift_l(accumulatore,"3)"
"
Potremmo" fare" anche" una" funzione" shift_l" che" accetti" anche" numeri" negativi" nella" nostra"
espressione"in"modo"che"i"numeri"negativi"rappresentino"uno"shift"a"destra."Nel"nostro"esempio,"
se" non" specifichiamo" altro" otteniamo" lo" shift" a" sinistra" di" 1," se" specifichiamo" anche" un" altro"
parametro"il"comportamento"della"procedura"si"modifica."
3"
"
Esistono"delle"librerie"predefinite"in"cui"si"fa"spesso"uso"di"questo"modo"di"scrivere"per"cui"a"volte"
lo"stesso"nome"di"procedura"lo"troviamo"chiamato"con"n"parametri"e"a"volte"con"n+1"parametri."
"
Rispetto"alle"procedure,"lo"scopo"principale"di"una"funzione"è"generalmente"quello"di"poter"essere"
usate," nel" momento" in" cui" vengono" chiamate," alla" destra" di" un’operazione" di" assegnazione" di"
variabile"o"di"programmazione"di"un"segnale."Le"funzioni"sono"quindi"delle"procedure"con"qualcosa"
in"più."Generalmente"si"utilizzano"le"funzioni"quando"lo"scopo"della"funzione"è"calcolare"un"valore,"
che" è" il" risultato" dell’elaborazione" della" funzione," che" viene" utilizzato" come" argomento" per" una"
operazione" di" assegnazione." Le" funzioni" però" possono" fare" pure" tutte" le" cose" che" fanno" le"
procedure" e" cioè" operare" su" segnali" o" variabili" e" modificarne" il" comportamento." In" vhdl" c’è"
distinzione"tra"le"funzioni"che"in"nessun"modo"modificano"variabili"o"segnali"che"non"siano"quelli"
passati"formalmente"e"funzioni"che"invece"agiscono"anche"su"segnali"o"parametri"che"sono"quelli"
visibili."Ricordiamo"che"all’interno"di"una"procedura"si"vedono"tutte"le"variabili"del"process"e"quindi"
si"vedono"tutte"le"variabili"o"i"segnali"dell’architecture."Nel"caso"in"cui"una"funzione"non"tocchi"altra"
cosa"che"non"siano"i"parametri"formali"che"sono"stati"passati"si"parla"di"FUNZIONE+PURA"in"vhdl."Se"
invece" una" funzione," oltre" a" operare" sui" parametri" formali" che" sono" stati" passati," fa" riferimento"
(per"riferimento"si"intende"una"operazione"di"assegnazione,"di"modifica"o"di"qualunque"altra"cosa)"
a" segnali" o" variabili" esterne" al" corpo" della" funzione" stessa" si" parla" di" FUNZIONE+ IMPURA." Quasi"
tutti"i"sistemi"di"analisi"del"file"vhdl"possono"verificare,"se"glielo"chiediamo,"che"tutte"le"funzione"
siano" pure." Questo" ad" esempio" per" verificare" di" non" aver" scritto" per" sbaglio" delle" funzioni" che"
agiscono" su" degli" elementi" esterni." Le" funzioni" pure" servono" pure" per" altre" cose" che" vedremo" a"
breve."
Per" quanto" riguarda" le" funzioni," l’unica" cosa" che" cambia" rispetto" alle" procedure" è" che," siccome"
esse"devono"restituire"un"valore,"bisogna"specificare,"nella"fase"di"definizione"della"funzione,"il"tipo"
del"valore"restituito.""
"
#"function"prova_fun"("…")"return"##"is"
."
." " " "
."
begin"
."
."
."
" " "
Return"17;""
end"function"prova_fun;"
"
Nell’esempio" sopra," tra" parentesi" abbiamo" la" lista" dei" parametri" formali" che" si" specificano" allo"
stesso"modo"delle"procedure"e"che"possono"essere"costanti,"variabili"o"segnali"di"tipo"in,"di"tipo"out"
o" di" tipo" inout" con" tutte" le" convenzioni" che" abbiamo" visto:" quando" si" tratta" di" costanti" l’unico"
parametro"che"ha"senso"è"il"parametro"in."Quando"abbiamo"un"parametro"di"tipo"in"esso"non"può"
essere" modificato," un" parametro" di" tipo" out" esso" non" può" essere" letto" ma" può" essere" soltanto"
scritto," inout" è" l’equivalente" del" passaggio" per" riferimento." Attenzione" al" fatto" che," per" quanto"
riguarda"i"segnali,"il"passaggio"di"tipo"in"significa"che"viene"passato"il"segnale"e"non"una"sua"copia"
come"saremmo"invece"portati"a"pensare"per"analogia"a"costanti"e"variabili."
4"
"
Dove" c’è" ##" dobbiamo" mettere" il" tipo" restituito" che" può" essere" bit," integer" o" un" altro" tipo" o"
sottotipo"definito"in"un"package."
Dove" c’è" #" possiamo" specificare" la" parola" chiave" pure" o" impure.* Questo" allo" scopo" di" aiutare" il"
simulatore"vhdl"a"verificare"che,"se"abbiamo"dichiarato"come"funzione"una"funzione"pura,"questa"
non" vada" ad" interferire" con" altre" variabili" o" segnali" che" non" siano" quelle" che" abbiamo" elencato"
nella"lista"dei"parametri"formali."
Prima" di" begin" abbiamo" la" parte" dichiarativa" in" cui" possiamo" dichiarare" tipi," funzioni" e" costanti"
relative"alla"funzione."
Fra"gli"statement"che"sono"disponibili"all’interno"del"corpo"della"funzione,"ce"n’è"almeno"uno"che"
deve"servire"a"poter"stabilire"il"valore"da"assegnare"all’identificatore"di"funzione,"cioè"il"valore"che"
viene"assunto"dalla"funzione"una"volta"completata"l’esecuzione."Stiamo"parlando"dello"statement"
return"17,"dove"17"è"il"valore"che"deve"essere"restituito"e"che"ovviamente"deve"appartenere"al"tipo"
della" funzione." Tale" valore" può" esserci" qualunque" espressione" che" coinvolge" costanti," variabili" o"
segnali."Una"funzione"che"restituisce"sempre"e"solo"il"valore"17"non"ha"molto"senso!"
Potremmo"avere"una"situazione"del"genere:"
"
Process"(…)"
."
."
."
a:="3"+"prova_fun(…)"
"
Facciamo"l’esempio"di"una"funzione"che"restituisce"il"numero"di"bit"a"1"o"a"0"che"sono"contenuti"in"
un"intero"o"in"un"array"di"bit"
"
entity"test_proc"is"
end"entity"test_proc;"
"
architecture"test"of"test_proc"is"
function"conta_bit_a_uno"(k:in"bit_vector)"return"integer"is"
variable"count:integer:=0;"
begin"
for"index"in"k'range"loop"
if"k(index)='1'"then"
count:=count+1;"
end"if;"
end"loop;"
Return"count;"
end"function"conta_bit_a_uno;"
begin"
process"is"
variable"a:bit_vector(0"to"7):="01011010";"
begin"

5"
"
assert"false"report""Il"numero"di"bit"in"a"risulta"&integer'image(conta_bit_a_uno(a))""
" severity"note;""
wait;"
end"process;"
end"architecture"test;"
"
Ricordiamo"che"procedure"e"funzioni"possono"essere"definite:"
all’interno"dei"package."In"questo"modo"sono"visibili"a"più"entità;"
nella"parte"dichiarativa"dell’architettura."In"questo"modo"sono"visibili"a"tutti"i"process"che"
sono"all’interno"di"questa"architettura;"
all’interno"di"un"un"process."In"questo"caso"sono"visibili"solo"all’interno"del"process"in"cui"
sono"definite."
"
Nel" nostro" esempio," la" funzione" conta_bit_a_uno" ha" come" argomento" un" vettore" di" bit" e"
restituisce"un"intero."
Attenzione"alla"distinzione"tra"process"e"funzione."Un"process"si"attiva"e"si"sospende"ma"è"sempre"
“vivo”," ogni" tanto" si" ferma" ma" poi" riprende" ad" operare," per" cui" il" valore" iniziale" dato" ad" una"
variabile" all’interno" di" un" process" è" una" cosa" che" riguarda" l’inizio" dei" tempi," dopodiché" si" ha"
l’evoluzione" delle" variabili" e" nella" parte" dichiarativa" non" ci" si" va" più." Non" è" così" nel" caso" delle"
funzioni."
"
bit_vector"fa"parte"dei"tipi"uncostrained,"e"come"per"tutti"i"tipi"unconstrained"non"viene"specificata"
la" dimensione" del" vettore." Ci" aspettiamo," all’interno" della" procedura," che" un" vettore" abbia"
dimensioni" diversi." Esistono" degli" attributi" che," a" partire" dal" nome" del" vettore," ci" dicono" diverse"
cose" che" ci" interessano." Nel" momento" in" cui" ad" esempio"dobbiamo" scrivere" un" ciclo" ci" interessa"
sapere"quanto"è"grande"il"vettore"con"cui"abbiamo"a"che"fare."Esiste"un"attributo"che"associato"al"
nome"del"vettore"ci"restituisce"il"numero"di"elementi.""
k’range"restituisce"l’intervallo"di"indici"possibili"per"il"vettore."Se"ad"esempio"bit_vector"fosse"stato"
definito"come"bit_vector"da"7"fino"a"0,"scrivere""
for"index"in"k'range"loop"
è"identico"a"scrivere"
for"index"in"7"downto"0"loop"
"
Nel"nostro"esempio"abbiamo"dichiarato"la"funzione"conta_bit_a_uno"all’interno"dell’architettura,"
nella" parte" dichiarativa." Dopo" il" secondo" begin" (il" primo" begin" precede" il" corpo" della" funzione)"
abbiamo" il" corpo" dell’architettura." Avendo" scritto" false" nella" condizione" dell’assert," quella" cosa"
vale"in"ogni"caso."
Una"stringa"può"essere"composta"da"più"stringhe."In"vhdl"il"segno"di"concatenazione"fra"stringhe"è"
il"seguente:"&."
“ciao”&“ciao”"" "è"equivalente"alla"stringa" " "“ciaociao”."
Esiste" un" attributo" che" serve" per" trasformare" un" elemento" appartenente" a" qualunque" tipo" nella"
stringa" che" esprime" il" tipo." Se" ad" esempio" abbiamo" il" numero" 13" di" tipo" intero," se" applichiamo"
6"
"
l’attributo"in"questione"ad"una"variabile"che"assume"il"numero"13,"ci"viene"restituita"una"stringa"in"
cui"ci"sono"scritti"i"caratteri"1"e"3."Per"un"segnale"di"tipo"bit"otterremo"il"carattere"0"o"1."Stiamo"
parlando" dell’attributo" image." Nell’esempio" sopra," scriviamo" integer" e" l’apice" per" dire" che"
l’argomento"della"image"è"di"tipo"intero."
"
Vedi"PROCEDURE"E"FUNZIONI"negli"appunti"del"docente."
"
In" vhdl" abbiamo" la" possibilità" di" usare" in" maniera" implicita" una" situazione" di" conflitto" fra" più"
segnali" che" si" trovano" ad" essere" collegati" tra" loro" a" simulare," dal" nostro" punto" di" vista," una"
situazione"in"cui"più"uscite"di"un"circuito"logico"sono"connesse"tra"di"loro."All’interno"di"un"process"
può"essere"programmata"una"solta"transizione"di"un"segnale."All’interno"di"un"process"non"ha"per"
esempio"senso"scrivere:"
a"<="3;"
a"<="7;"
Questo"non"ha"senso"perché"vorrebbe"dire"programmare"una"transizione"per"a"che"deve"assumere"
contemporaneamente"i"valori"3"e"7."Questo"viene"impedito"di"principio"in"vhdl"eccetto"che"non"si"
specifichi"che"a"è"un"segnale"di"tipo"resolved."Se"un"segnale"è"di"tipo"resolved,"cose"come"quelle"
viste" sono" consentite" (anche" se" non" proprio" nella" forma" che" abbiamo" scritto" sopra)" perché" un"
segnale" di" tipo" resolved" è" un" segnale" che" ha" associato" quella" che" si" chiama" una" funzione" di"
risoluzione"(da"cui"il"nome"resolved)"dei"conflitti"di"assegnazione."Una"funzione"di"risoluzione"dei"
conflitti"di"assegnazione"è"una"funzione"che"stabilisce,"nel"momento"in"cui"si"tenta"di"attribuire"più"
valori"ad"a"nello"stesso"istante"(con"“nello"stesso"istante”"intendiamo"dire"che"viene"programmata"
nello"stesso"istante"di"tempo"),"quale"valore"assumerà"effettivamente"a."
Questa" cosa" la" ritroviamo" tutte" le" volte" in" cui" progettiamo" ad" esempio" un" bus." Immaginiamo" di"
avere"le"uscite"di"più"sistemi"collegate"ad"uno"stesso"bus."
"
S"
S1" S2" S3"

entità" entità" entità"

1" 2" 3"


"
"
Perché"questo"conflitto"sia"risolto"ci"sono"diverse"possibilità"che"abbiamo"visto"ad"Elettronica"dei"
Sistemi" Digitali." Noi" cerchiamo" di" implementare" questa" cosa" per" risparmiare" sui" collegamenti" e"
sulla" logica" di" collegamento" tra" una" entità" e" l’altra." Se" il" vhdl" ha" la" pretesa" di" descrivere" sistemi"
reali,"deve"poter"gestire"questa"situazione,"cioè"la"situazione"in"cui"un"unico"segnale"S"è"collegato"a"
più"segnali."Nel"corpo"dell’architettura"avremmo"che"S"è"collegato"ad""S 1 ,ad"S 2" e"ad"S 3 :"
Architecture…"
."
."
."
s"<="s1;"
7"
"
s"<="s2;"
s"<="s3;"
Questa" cosa" in" generale" non" avrebbe" senso" per" quello" che" abbiamo" visto" prima." Per" farle"
assumere" un" senso" dobbiamo" dire" esplicitamente" che" S" appartiene" ad" un" tipo" in" cui" queste"
situazioni"sono"possibili"perché"esiste"una"funzione"di"risoluzione"associata"che"stabilisce,"secondo"
un"criterio"che"è"quello"che"poi"decidiamo"noi,"quale"debba"essere,"in"una"situazione"di"conflitto,"
l’effettivo" valore" assunto" da" S." In" vhdl" questa"esigenza"nasce" dal" voler"riprodurre" sistemi" digitali"
ma"può"essere"utilizzata"per"qualunque"cosa"in"generale."Riprendiamo"l’esempio"visto"poco"fa:"
a"<="3;"
a"<="7;"
In"una"situazione"del"genere"potremmo"far"si"che"ad"a"venga"assegnato"il"valore"medio"tra"i"due,"il"
valore"minimo,"il"valore"massimo"o"qualunque"altra"cosa."
Se" si" vogliono" utilizzare" dei" segnali" di" tipo" resolved" c’è" bisogno" di" fare" riferimento" ad" un" tipo"
“normale”" (cioè" che" non" ha" associata" la" funzione" di" risoluzione)" che" definiamo" noi" o" che" è"
predefinito." Questo" tipo" si" chiama" unresolved* perché" è" il" punto" di" partenza" per" fare" segnali"
resolved."A"partire"da"questo"tipo"che"ci"siamo"definiti,"dobbiamo"definire"una"funzione"che"serve"
per"gestire"il"conflitto,"cioè"per"stabilire,"sulla"base"di"un"algoritmo,"quale"debba"essere"il"valore"
assunto"quando"ci"sono"più"ingressi"per"lo"stesso"segnale."Tali"ingressi"sono"detti"driver."Il"segnale"
resolved" si" definisce" come" l’insieme" del" tipo," dell’intervallo" di" valori" associati" al" tipo" e" della"
funzione"di"risoluzione.""
"
Vediamo"un"esempio."Costruiamo"un"sistema"in"cui"ad"un"certo"punto"più"segnali"convergono"ad"
uno" stesso" nodo," cioè" abbiamo" un" certo" numero" di" fili" collegati" ad" un" unico" filo." Questi" segnali"
sono" del" tipo" per" enumerazione" colori" che" andiamo" a" definire" per" esempio" dal" più" scuro" al" più"
chiaro." A" partire" da" questo" tipo" per" enumerazione" definiremo" un" segnale" resolved" con" questa"
convenzione:"se"ci"sono"più"segnali"che"convergono"su"unico"nodo,"il"valore"che"viene"assunto"è"ad"
esempio" il" colore" più" chiaro" o" il" colore"più" scuro," lo" decidiamo" noi." Definiamo" questo" tipo" in"un"
package."Immaginiamo"che"in"un"file,"da"qualche"parte,"abbiamo"quanto"segue."
"
package"MIOPKG"is"
type"colori"is"(nero,"blu,"rosso,"verde,"giallo,"bianco);"
type"array_di_colori"is"array(integer"<"">)"of"colori;"
function"resolve_color"(ingress:"in"array_di_colori)"return"colori;"
subtype"res_colori"is"resolve_color"colori;"
end"package"miopkg;"
"
Un"package"ha"una"parte"dichiarativa"ed"un"corpo."La"parte"dichiarativa"è"quella"in"cui"possiamo"
definire"tipi,"sottotipi,"etc."Quella"sopra"è"la"parte"dichiarativa."Abbiamo"necessità"di"utilizzare"un"
package" ad" esempio" quando" vogliamo" usare" dei" tipi" non" predefiniti" come" ingresso" e" uscita" di"
entità,"perché"l’entità"è"la"prima"cosa"che"si"incontra"nella"definizione"di"qualunque"tipo"di"sistema"
e,"nella"definizione"delle"porte,"c’è"bisogno"di"specificare"di"che"tipo"sono"gli"ingressi,"le"uscite"e"gli"
ingressiuuscita" e" nell’entità" non" è" prevista" la" definizione" dei" tipi." Abbiamo" quindi" bisogno" di" un"
8"
"
posto" dove" conservare" i" tipi" definiti" da" noi" che" vogliamo" usare" come" ingressi," uscite" o" ingressiu
uscite"delle"entità."Nei"package"possiamo"definire"tipi"e"sottotipi."
Quando" definiamo" un" tipo" per" enumerazione," l’ordine" è" importante" perché" esistono" diversi"
attributi"che"ci"permettono"di"scegliere"ad"esempio"il"valore"più"a"destra"o"più"a"sinistra."Inoltre,"
all’inizio"dei"tempi"vengono"attivati"tutti"i"process"e"il"valore"di"default"è"quello"più"a"sinistra"del"
tipo."Nel"nostro"esempio,"tutti"i"segnali"che"saranno"definiti"come"tipo"colori"avranno,"come"valore"
iniziale"all’origine"dei"tempi,"il"valore"nero.""
La"funzione"risoluzione"funziona"così:"in"maniera"automatica,"se"ci"sono"più"segnali"che"insistono"
sullo"stesso"segnale"(visto"come"risultato"di"questo"sistema),"questi"vengono"visti"dalla"funzione"di"
risoluzione" come" un" array" di" segnali" che" gli" viene" passato" come" argomento." Dobbiamo" quindi"
prevedere" che" esista" un" array" array_di_colori" perché" questo" sarà" l’argomento" della" funzione" di"
risoluzione" in" maniera" automatica." La" funzione" di" risoluzione" userà" questo" array" per" decidere," a"
seconda"di"quello"che"noi"scriveremo,"il"valore"da"assegnare"al"segnale"stesso."
Quando"costruiamo"un"sistema,"in"linea"di"principio"non"possiamo"sapere"quanti"saranno"i"segnali"
collegati"insieme,"quindi"l’array"che"andiamo"a"definire"sarà"di"tipo"unconstrained"in"modo"tale"che"
si"abbia"la"possibilità"di"decidere"“sul"momento”"quale"debba"essere"la"sua"dimensione.""
Una" volta" definito" il" tipo" e" l’array_di_colori" dobbiamo" definire," nella" parte" dichiarativa" del"
package," il" prototipo" della" funzione" di" risoluzione." Il" prototipo" di" una" funzione" è" dato" dal" nome"
della" funzione," dalla" lista" degli" argomenti" e" dal" valore" ritornato." Il" corpo" della" funzione" viene"
invece"scritto"nel"corpo"del"package."
Nell’esempio," la" funzione" di" risoluzione" resolve_color" ha" un" unico" argomento" che" è" un" array"
unconstrained." Essa" riceve" in" ingresso" l’insieme" dei" possibili" valori" e" restituisce" il" valore" da"
assegnare"al"segnale."
Quando" definiamo" il" sottotipo" res_colori" specifichiamo" che" il" tipo" a" cui" facciamo" riferimento" è"
colori" e" la" funzione" di" risoluzione" è" resolve_color." Questo" sottotipo" assume" i" valori" dell’insieme"
colori"e"utilizza"la"funzione"resolve_color"per"risolvere"i"conflitti."L’uso"di"questa"funzione"avverrà"
in"maniera"automatica,"noi"non"lo"vediamo,"e"verrà"utilizzata"tutte"le"volte"che"c’è"da"risolvere"un"
conflitto."
Abbiamo"ordinato"in"qualche"modo"i"colori"dal"più"scuro"al"più"chiaro"e"la"funzione"di"risoluzione"fa"
in"modo"da"restituirmi"il"valore"più"chiaro"tra"quelli"dati"ogni"volta"che"c’è"un"conflitto."
Scriviamo"di"seguito"il"corpo"del"package."
"
package"body"miopkg"is"
function"resolve_color"(ingressi:"in"array_di_colori)"return"colori"is"
variable"colore_chiaro:"colori;"
begin"
" colore_chiaro:"nero;"
" for"index"in"ingressi’range"loop"
" " if"(colori"‘pos"(ingressi(index))">"colori’pos(colore_chiaro))"then"
" " " colore_chiaro:=ingressi(index);"
" " end"if;"
" end"loop"
9"
"
" return"colore_chiaro;"
end"function;"
end"package"body;"
"
Nel" corpo" del" package" inizialmente" metto" nero," dopodiché," esaminando" tutti" gli" elementi"
dell’array,"mano"a"mano"che"trovo"un"colore"più"chiaro"lo"metto"in"colore_chiaro"in"modo"che"alla"
fine"dell’array"avremo"ottenuto"il"colore"più"chiaro"in"colore_chiaro."
"
INIZIO+PARTE+AL+PC+
Vediamo"i"file"miopkg.vhd"e"testres.vhd"(vedi"allegati)."
Nel"prompt"scriviamo:"
vi"miopkg.vhd"
vi"testres.vhd"
"
Nel"file"miopkg"abbiamo"tutto"quello"che"serve"per"la"definizione"del"sottotipo"resolved"res_colori"
e"quindi"il"tipo"di"riferimento,"l’array"che"serve"necessariamente"per"la"definizione"della"funzione"di"
risoluzione,"il"sottotipo."
Il" package" body" è" semplicemente" la" definizione" della" funzione" resolve_color." Usiamo" questo"
package"all’interno"di"una"entità"di"test"per"renderci"conto"di"come"funziona"questa"cosa."
"
Nel" file" testres.vhd" abbiamo" l’entità" testres" senza" ingressi" e" senza" uscite," è" semplicemente"
l’involucro"che"serve"per"poter"simulare"il"comportamento"di"questo"sistema.""
Tutto"quello"che"è"contenuto"nel"package"in"termini"di"definizioni"di"tipi,"funzioni,"etc."è"contenuto"
nell’oggetto"work.nomeDellaLibreria.""
“.all”"significa"“rendi"disponibile"all’entità"tutto"quello"che"è"contenuto"all’interno."Esistono"modi"
per"specificare"solo"alcune"parti."
All’interno" dell’entità," quindi" dell’architettura," sono" disponibili" i" tipi" che" abbiamo" definito" nel"
package," il" tipo" per" risoluzione" e" quindi" automaticamente" anche" quella" funzione" che" abbiamo"
definito."
In"questa"architettura,"che"serve"solo"a"mostrare"il"comportamento"della"funzione"di"risoluzione,"ci"
sono"due"processi."
"
I"seguenti"sono"process"impliciti:"
risultato<=a;"
risultato<=b;"
risultato<=c;"
risultato<=d;"
risultato<=e;""
Tutte"le"volte"che"cambia"un"valore"tra"a,"b,"c,"d"ed"e,"ci"troviamo"nella"situazione"in"cui"risultato*
(che"è"un"segnale"di"tipo"colori)"risulta"collegato"a"questo"insieme"di"uscite"che"convergono"su"un"
unico"nodo.""
"
10"
"
Consideriamo"il"seguente"process:"
process"
begin"
" a<=nero;"
" b<=giallo;"
" c<=rosso;"
" d<=blu;"
" e<=verde;"
" wait"for"20"ns;"
" a<=bianco;"
" wait"for"20"ns;"
" a<=verde;"
" wait"for"20"ns;"
" wait;"
end"process;"
Esso" serve" per" dare" un" evoluzione" temporale" al" sistema." Un" attimo" dopo" l’inizio" dei" tempi" a" è"
nero,"b"è"giallo,"c"è"rosso,"d"è"blu,"e"è"verde."Dopo"un"po’"di"tempo"a"diventa"bianco,"si"aspetta"un"
po’"di"tempo"e"a"diventa"verde,"si"aspetta"ancora"un"po’"di"tempo"e"si"esce."
"
Consideriamo"l’altro"process:"
process"(risultato)""
begin""
"
" assert"false"report""il"colore"ottenuto"risulta""&"colori'image(risultato)"severity"note;"
end"process;"
Il" process" sul" risultato" serve" a" vedere" cosa" sta" succedendo:" tutte" le" volte" che" il" risultato" cambia"
vedremo" che" valore" viene" assunto." All’inizio" dei" tempi," cioè" al" tempo" t=0," risultato" (che" è" un"
segnale"di"tipo"colori)"assume"il"valore"più"a"sinistra"del"tipo,"cioè"nero."Un"attimo"dopo"l’inizio"dei"
tempi,"a"diventerà"nero,"b"giallo,"c"rosso,"d"blu,"e"verde."Dopo"un"po’,"nero"cambierà"in"modo"da"
assumere"sempre"il"più"chiaro"tra"i"colori"che"sono"assunti"da"a,"b,"c,"d"ed"e."
"
Nel"prompt"scriviamo:"
ghdl"–a"miopkg.vhd"
ghdl"–a"testres.vhd"
ghdl"–e"testres"
ghdl"–r"testeres"
"
“ghdl"–a”"è"seguito"dal"nome"del"file."
“ue”"si"riferisce"alla"costruzione"di"tutti"i"legami"fra"i"diversi"enti…"
“ur”"si"riferisce"alla"simulazione."
L’argomento" è" la" top" entity" (in" questo" caso" testres)," l’entità" principale" a" cui" vogliamo" fare"
riferimento"e,"in"linea"di"principio,"non"è"il"nome"del"file"anche"se"nel"nostro"esempio"coincidono."
11"
"
In"alcuni"sistemi"VHDL,"il"nome"del"file"in"cui"è"contenuta"la"top"entity"deve"essere"il"nome"della"
top"entity"(e"nel"nostro"esempio"coincidono.)."
"
Commentiamo"i"risultati"della"simulazione."
A" 0" ms," cioè" all’inizio" dei" tempi," il" colore" ottenuto" risulta" nero." All’inizio" dei" tempi" si" ha" una"
transizione"automatica:"tutti"i"segnali"vengono"marcati"come"“hai"avuto"una"transizione”"e"il"valore"
assunto,"se"non"è"stato"specificato"come"default"nella"fase"di"dichiarazione,"è"quello"più"a"sinistra"
del"tipo,"per"i"tipi"scalari."
A"0"ms"(ricordiamo"che"però"la"risoluzione"di"questo"sistema"è"molto"più"elevata)"il"colore"assunto"
diventa"giallo,"perché"in"un"tempo"brevissimo"a,"b,"c,"d"ed"e"diventano"contemporaneamente"uno"
nero,"uno"giallo,"uno"rosso,"uno"blu"e"uno"verde."Essi"sono"tutti"collegati"al"risultato"e"il"più"chiaro"
fra" questi" colori," secondo" la" nostra" convenzione" sulla" funzione" di" risoluzione," è" giallo," per" cui" il"
risultato"diventa"giallo."
Dopo"20"ns"a"diventa"bianco."A"questo"punto"il"colore"più"chiaro"che"c’è"nell’insieme"è"bianco"e"
infatti,"a"20"ns,"il"colore"ottenuto"diventa"bianco."
Se"poi"a"cambia"di"nuovo"e"diventa"verde"ed"è"rimasto"il"b"ancora"giallo,"sarà"il"giallo"ad"essere"il"
colore"più"chiaro"tra"quelli"che"abbiamo"visto."
FINE+PARTE+AL+PC+
+
Quanto" abbiamo" fatto" può" servire" ad" esempio" a" descrivere" il" comportamento" di" un" sistema"
ridondante." Supponiamo" ad" esempio" che" ci" siano" 5" sensori" di" carrello" sollevato/sceso" su" un"
aeroplano:" le" regole" di" sicurezza," per" esempio," dicono" che" almeno" 4" devono" confermare" che" il"
carrello"è"sceso,"altrimenti"l’atterraggio"non"si"può"fare."Tipicamente"il"segnale"della"spia"di"carrello"
sceso"è"un"segnale"resolved"e"lo"possiamo"vedere"in"questa"maniera:"riceve"in"ingresso"il"segnale"di"
5"oggetti"e"le"uscite"possibili"sono"“il"carrello"è"giù”"oppure"“il"carrello"non"è"giù”"oppure"quando"
c’è"discordanza"maggiore"di"una"certa"soglia"(ad"esempio"3"su"2"non"è"accettabile)"significa"che"c’è"
un"malfunzionamento"del"sistema"e"questo"viene"segnalato."Questo"è"un"tipico"contesto"in"cui"un"
segnale"resolved"si"può"usare.""
"
Vediamo"il"tipo"standard"logic"che"è"un"tipo"resolved,"cioè"è"l’insieme"di"un"tipo"unresolved"(che"è"
ulogic,"cioè"unresolved"standard"logic,"che"è"un"tipo"definito"per"enumerazione)"più"una"funzione"
di"risoluzione"il"cui"scopo"è"quello"di"tendere"a"rappresentare"il"comportamento"di"sistemi"logici"
reali"in"un"certo"numero"di"condizioni"che"ora"andiamo"ad"elencare."
"
type"std_ulogic"is(‘u’,"’x’,"’0’,"’1’,"’z’,"’w’,"’L’,"’H’,"’u‘)""""
è" il" tipo" di" partenza" dal" quale" definiremo" il" tipo" che" useremo" sempre" e" che" è" std_logic."
Quest’ultimo" è" un" sottotipo" dello" standard" ulogic" con" associata" una" funzione" di" risoluzione."
Vediamo" cosa" significa" ciascuno" di" quei" valori." Essi" fanno" riferimento" non" tanto" al" processo" di"
descrizione"e"simulazione"quanto"al"processo"di"sintesi."
Ciascuno" degli" oggetti" in" questione" ha" un" significato." Ad" esempio" la" lettera" u" sta" per" non"
inizializzato"(uninitialized).""

12"
"
/Users/giuseppegerace/Documents…lved function colori/miopkg.vhdl Page 1 of 1
Saved: 23/11/12 18:19:28 Printed For: giuseppegerace

1 package miopkg is
2 ! ! ! ! --! 0! 1! 2! ! 3! 4 5
3 ! type colori is (nero, blu, rosso, verde, giallo, bianco);
4 ! type array_di_colori is array(integer range <>) of colori;
5 ! function resolve_color (ingressi: in array_di_colori) return colori;
6 ! subtype res_colori is resolve_color colori;
7 end package miopkg;
8
9
10 package body miopkg is
11 ! function resolve_color (ingressi: in array_di_colori) return colori is
12 ! ! variable colore_chiaro: colori;
13 ! ! begin
14 ! ! colore_chiaro:= nero;
15 ! ! for index in ingressi'range loop
16 ! ! ! if (colori 'pos (ingressi(index)) > colori'pos(colore_chiaro)) th
17 ! ! ! ! colore_chiaro:=ingressi(index);
18 ! ! ! end if;
19 ! ! end loop;
20 ! ! return colore_chiaro;
21 ! end function;
22 end package body;
/Users/giuseppegerace/Documents…esolved function colori/rgb.vhdl Page 1 of 1
Saved: 26/11/12 16:05:19 Printed For: giuseppegerace

1 library work;
2 use work.miopkg.ALL;
3
4 entity rgb is
5 ! port(A, B, C, D, E: in colori; risultato: out res_colori);
6 end rgb;
7
8 architecture behav of rgb is
9 ! begin
10 ! ! risultato<=A;
11 ! ! risultato<=B;
12 ! ! risultato<=C;
13 ! ! risultato<=D;
14 ! ! risultato<=E;
15 end behav;
16
17 entity testbench is
18 end testbench;
19
20 architecture comportamento of testbench is
21 ! signal in1,in2,in3,in4,in5: work.miopkg.colori;
22 ! signal u1: work.miopkg.res_colori;
23 ! begin
24 ! resolution:entity work.rgb(behav)
25 ! port map (in1,in2,in3,in4,in5,u1);
26 ! ! !
27 ! simula: process is
28 ! ! begin! ! ! ! ! ! ! ! ! ! -- istante 0 ms
29 ! ! in1<= work.miopkg.colori'val(0);! ! ! -- l'indice del primo
30 ! ! in2<= work.miopkg.colori'val(4);! ! ! -- di un tipo definito
31 ! ! in3<= work.miopkg.colori'val(2);! ! ! -- per enumerazione è
32 ! ! in4<= work.miopkg.colori'val(1);
33 ! ! in5<= work.miopkg.colori'val(3);
34 ! ! wait for 20 ns;!! ! ! ! ! ! ! -- istante 20 ns
35 ! ! in1<= work.miopkg.colori'val(5);
36 ! ! wait for 20 ns;!! ! ! ! ! ! ! -- istante 40 ns
37 ! ! in1<= work.miopkg.colori'val(3);
38 ! ! wait for 20 ns;!! ! ! ! ! ! ! -- istante 60 ns
39 ! ! wait;
40 ! end process simula;
41 !
42 ! process (u1)
43 ! ! begin
44 ! ! assert false report "il colore ottenuto risulta "
45 ! ! & work.miopkg.colori'image(u1) severity note;
46 ! end process;
47 !
48 end comportamento;
V3.4 VHDL Compiler Reference

You can declare signals in architectures, entities, and blocks,


and use them in processes and subprograms. Processes and
subprograms cannot declare signals for internal use.
You can use signals in expressions, as described in Chapter 5.
Signals are assigned values by signal assignment statements,
as described in Chapter 6.

Resolution Functions
Resolution functions are used with signals that can be con-
nected (wired together). For example, if two drivers are
directly connected to a signal, the resolution function deter-
mines whether the signal value is the AND, OR, or three-state
function of the driving values.
Use resolution functions to assign the driving value when there
are multiple drivers. For simulation, you can write an arbitrary
function to resolve bus conflicts.

Note:
A resolution function may change the value of a re-
solved signal even if all drivers have the same value.

The resolution function for a signal is part of that signal’s


subtype declaration. You create a resolved signal in four
steps:
–– Step 1
type SIGNAL_TYPE is ...
–– signal’s base type is SIGNAL_TYPE

HOME CONTENTS INDEX


For further assistance, email support_center@synopsys.com or call your local support center
V3.4 VHDL Compiler Reference

–– Step 2
subtype res_type is res_function SIGNAL_TYPE;
–– name of the subtype is res_type
–– name of function is res_function
–– signal type is res_type (a subtype of SIGNAL_TYPE)
...
–– Step 3
function res_function (DATA: ARRAY_TYPE)
return SIGNAL_TYPE is
–– declaration of the resolution function
–– ARRAY_TYPE must be an unconstrained array of SIGNAL_TYPE
...
–– Step 4
signal resolved_signal_name:res_type;
–– resolved_signal_name is a resolved signal
...

1. The signal’s base type is declared.


2. The resolved signal’s subtype is declared as a subtype of
the base type, and includes the name of the resolution
function.
3. The resolution function itself is declared (and later de-
fined).
4. Resolved signals are declared resolved subtypes.

VHDL Compiler does not support arbitrary resolution functions.


Only wired-and, wired-or, and three-state functions are
allowed. VHDL Compiler requires that you mark all resolution
functions with a special directive indicating the kind of resolu-
tion performed.

HOME CONTENTS INDEX


For further assistance, email support_center@synopsys.com or call your local support center
V3.4 VHDL Compiler Reference

Note:
VHDL Compiler considers the directive only when creat-
ing hardware. The body of the resolution function is
parsed but ignored; using unsupported VHDL constructs
(see Appendix C) generates errors.

Do not connect signals that use different resolution


functions. VHDL Compiler supports only one resolution
function per network.

The three resolution function directives are


–– synopsys resolution_method wired_and

–– synopsys resolution_method wired_or

–– synopsys resolution_method three_state

WARNING
Presynthesis and postsynthesis simulation results may not
match if the body of the resolution function used by the
simulator does not match the directive used by the
synthesizer.

Example 3–17 shows how to create and use resolved signals,


and how to use compiler directives for resolution functions.
The signal’s base type is the predefined type BIT.

HOME CONTENTS INDEX


For further assistance, email support_center@synopsys.com or call your local support center
V3.4 VHDL Compiler Reference

Example 3–17 Resolved Signal and its Resolution Function


package RES_PACK is
function RES_FUNC(DATA: in BIT_VECTOR) return BIT;
subtype RESOLVED_BIT is RES_FUNC BIT;
end;

package body RES_PACK is


function RES_FUNC(DATA: in BIT_VECTOR) return BIT is
–– pragma resolution_method wired_and
begin
–– The code in this function is ignored by VHDL Compiler
–– but parsed for correct VHDL syntax

for I in DATA’range loop


if DATA(I) = ’0’ then
return ’0’;
end if;
end loop;
return ’1’;
end;
end;

use work.RES_PACK.all;

entity WAND_VHDL is
port(X, Y: in BIT; Z: out RESOLVED_BIT);
end WAND_VHDL;

architecture WAND_VHDL of WAND_VHDL is


begin
Z <= X;
Z <= Y;
end WAND_VHDL;

HOME CONTENTS INDEX


For further assistance, email support_center@synopsys.com or call your local support center
/Users/giuseppegerace/Documents…/resolved function/res_pack.vhdl Page 1 of 1
Saved: 23/11/12 11:11:21 Printed For: giuseppegerace

1 package RES_PACK is
2 ! function RES_FUNC(DATA: in BIT_VECTOR) return BIT;
3 ! subtype RESOLVED_BIT is RES_FUNC BIT;
4 end;
5
6
7 package body RES_PACK is
8 ! function RES_FUNC(DATA: in BIT_VECTOR) return BIT is
9 ! ! -- pragma resolution_method wired_and
10 ! ! begin
11 ! ! -- The code in this function is ignored by VHDL Compiler
12 ! ! -- but parsed for correct VHDL syntax
13 ! ! for I in DATA'range loop
14 ! ! ! assert false report "indice ciclo "& integer'image(I) severity n
15 ! ! ! if DATA(I) = '0' then
16 ! ! ! ! return '0';
17 ! ! ! end if;
18 ! ! end loop;
19 ! ! return '1';
20 ! end;
21 end;
/Users/giuseppegerace/Documents…resolved function/wand_vhdl.vhdl Page 1 of 1
Saved: 23/11/12 12:07:58 Printed For: giuseppegerace

1 use work.RES_PACK.all;
2 entity WAND_VHDL is
3 ! port(X, Y: in BIT; Z: out RESOLVED_BIT);
4 end WAND_VHDL;
5 architecture WAND_VHDL of WAND_VHDL is
6 ! begin
7 ! Z <= X;
8 ! Z <= Y;
9 end WAND_VHDL;
10
11 entity testbench1 is
12 end testbench1;
13
14 architecture behav of testbench1 is
15 ! signal in1,in2:bit;
16 ! signal u1:work.RES_PACK.RESOLVED_BIT;
17 ! begin
18 ! resolution:entity work.WAND_VHDL(WAND_VHDL)
19 ! port map (in1,in2,u1);
20 ! simula: process is
21 ! begin! ! ! ! ! ! ! ! -- istante 0 ns
22 ! in1<='0';
23 ! in2<='0';
24 ! wait for 10 ns;!! ! ! ! ! -- istante 10 ns
25 ! in1<='1';
26 ! wait for 10 ns;!! ! ! ! ! -- istante 20 ns
27 ! in1<='0';
28 ! wait for 10 ns;!! ! ! ! ! -- istante 30 ns
29 ! in2<='1';
30 ! wait for 10 ns;!! ! ! ! ! -- istante 40 ns
31 ! in1<='1';
32 ! wait for 10 ns;!! ! ! ! ! -- istante 50 ns
33 ! in2<='0';
34 ! in1<='0';
35 ! wait for 20 ns;!! ! ! ! ! -- istante 70 ns
36 ! wait;
37 ! end process simula;
38 end behav;
Subprograms and Packages 119
The first component instantiation statement for the trans component
labeled U1 shows how conversion functions are used for inout ports. The
first port mapping maps portx1 to a(0). Port a(0) is a nineval type;
therefore, the signal created by the port is a nineval type. When this sig-
nal is mapped to port x1 of component trans, it must be converted to a
fourstate type. Conversion function convert9val must be called to com-
plete the conversion. When data is transferred out to port x1 for the out
portion of the inout port, conversion function convert4state must be
called.
The conversion functions are organized such that the side of the port
mapping clause that changes contains the conversion function that must
be called. When x1 changes, function convert4state is called to convert
the fourstate value to a nineval value before it is passed to the con-
taining entity trans2. Conversely, when port a(0) changes, function
convert9val is called to convert the nineval value to a fourstate value
that can be used within the trans model.
Conversion functions are used to convert a value of one type to a value of
another type. They can be called explicitly as part of execution or implicitly
from a mapping in a component instantiation.

Resolution Functions
A resolution function is used to return the value of a signal when the sig-
nal is driven by multiple drivers. It is illegal in VHDL to have a signal with
multiple drivers without a resolution function attached to that signal.
A resolution function consists of a function that is called whenever one of
the drivers for the signal has an event occur on it. The resolution function
is executed and returns a single value from all of the driver values; this
value is the new value of the signal.
In typical simulators, resolution functions are built in, or fixed. With
VHDL, the designer has the ability to define any type of resolution function
desired, wired-or, wired-and, average signal value, and so on.
A resolution function has a single-argument input and returns a single
value. The single-input argument consists of an unconstrained array of
driver values for the signal that the resolution function is attached to. If
the signal has two drivers, the unconstrained array is two elements long;
if the signal has three drivers, the unconstrained array is three elements
long. The resolution function examines the values of all of the drivers and
returns a single value called the resolved value of the signal.
120 Chapter Five

Let’s examine a resolution function for the type fourval that was used
in the conversion function examples. The type declaration for fourval is
shown here:

TYPE fourval IS (X, L, H, Z);

Four distinct values are declared that represent all of the possible
values that the signal can obtain. The value L represents a logical 0, the
value H represents a logical 1, the value Z represents a high-impedance
or open-collector condition, and, finally, the value X represents an unknown
condition in which the value can represent an L or an H, but we’re not sure
which. This condition can occur when two drivers are driving a signal, one
driver driving with an H, and the other driving with an L.
Listed by order of strength, with the weakest at the top, the values are
as follows:

■ Z —Weakest, H, L, or X can override


■ H,L —Medium strength, only X can override
■ X —Strong, no override

Using this information, a truth table for two inputs can be developed,
as shown in Figure 5-1.
This truth table is for two input values. It can be expanded to more
inputs by successively applying it to two values at a time. This can be done
because the table is commutative and associative. An L and a Z, or a Z and
an L, gives the same results. An (L, Z) with H gives the same results as an
(H, Z) with an L. These principles are very important, because the order of
driver values within the input argument to the resolution function is non-
deterministic from the designer’s point of view. Any dependence on order
can cause nondeterministic results from the resolution function.

Figure 5-1 Z L H X
Four State Truth
Table. Z Z L H X

L L L X X

H H X H X

X X X X X
Subprograms and Packages 121
Using all of this information, a designer can write a resolution function
for this type. The resolution function maintains the highest strength seen
so far and compares this value with new values a single element at a time,
until all values have been exhausted. This algorithm returns the highest-
strength value.
Following is an example of such a resolution function:

PACKAGE fourpack IS
TYPE fourval IS (X, L, H, Z);
TYPE fourval_vector IS ARRAY (natural RANGE <> ) OF
fourval;

FUNCTION resolve( s: fourval_vector) RETURN fourval;


END fourpack;

PACKAGE BODY fourpack IS


FUNCTION resolve( s: fourval_vector) RETURN fourval IS
VARIABLE result : fourval := Z;
BEGIN
FOR i IN s’RANGE LOOP
CASE result IS
WHEN Z =>
CASE s(i) IS
WHEN H =>
result := H;
WHEN L =>
result := L;
WHEN X =>
result := X;
WHEN OTHERS =>
NULL;
END CASE;

WHEN L =>
CASE s(i) IS
WHEN H =>
result := X;
WHEN X =>
result := X;
WHEN OTHERS =>
NULL;
END CASE;

WHEN H =>
CASE s(i) IS
WHEN L =>
result := X;
WHEN X =>
result := X;
WHEN OTHERS =>
122 Chapter Five

NULL;
END CASE;

WHEN X =>
result := X;

END CASE;
END LOOP;
RETURN result;
END resolve;
END fourpack;

The input argument is an unconstrained array of the driver-base


type, fourval. The resolution function examines all of the values of the
drivers passed in argument s one at a time and returns a single value
of fourval type to be scheduled as the signal value.
Variable result is initialized to a Z value to take care of the case of zero
drivers for the signal. In this case, the loop is never executed, and the
result value returned is the initialization value. It is also a good idea to
initialize the result value to the weakest value of the value system to allow
overwriting by stronger values.
If a nonzero number of drivers exists for the signal being resolved, then
the loop is executed once for each driver value passed in argument s. Each
driver value is compared with the current value stored in variable result.
If the new value is stronger according to the rules outlined earlier, then
the current result is updated with the new value.
Let’s look at some example driver values to see how this works. Assuming
that argument s contained the driver values shown in Figure 5-2, what
would the result be?

Initial
Figure 5-2 Value Driver Values
Four State Resolution
with Two Values.
Z Z H

H Resultant Value
Subprograms and Packages 123
Because there are two drivers, the loop is executed twice. The first time
through, the loop variable result contains the initial value Z. The first
driver value is also a Z value. Value Z compared with value Z produces a
resulting value Z.
The next iteration through the loop retrieves the next driver value,
which is H. The value H compared with value Z returns value H. The
function therefore returns the value H as the resolved value of the signal.
Another case is shown in Figure 5-3. In this example, there are three
drivers, and the resolution function executes the loop three times. In the
first iteration of the loop, the initial value of result (Z) is compared with
the first driver value (H). The value H is assigned to result. In the next
iteration, result (H) is compared with the second driver (Z). The value H
remains in result because the value Z is weaker. Finally, the last itera-
tion result (H) is compared with the last driver value (L). Because these
values are of the same strength, the value X is assigned to result. The
value X is returned from the function as the resolved value for the signal.

NINE-VALUE RESOLUTION FUNCTION Some simulators use


more complex types to represent the value of a signal. For instance, what
might a resolution function look like for a nine-value system, typical of
most workstation-based simulators in use currently? Following are the
nine values in the value system:

Initial
Figure 5-3
Value Driver Values
Four State Resolution
with Three Values.
Z H Z L

X Resultant Value
124 Chapter Five

Z0, Z1, ZX, R0, R1, RX, F0, F1, FX

weakest-----------------------------strongest

The system consists of three strengths and three logic values. The three
strengths represent the following:

■ Z —High impedance strength, few hundred k of resistance


■ R —Resistive, few k of resistance
■ F —Forcing, few ohms of resistance

The three logic levels are represented as follows:

■ 0 —Logical 0 or false
■ 1 —Logical 1 or true
■ X —Logical unknown

The nine states are described as follows:

■ Z0 —High-impedance 0
■ Z1 —High-impedance 1
■ ZX —High-impedance unknown
■ R0 —Resistive 0
■ R1 —Resistive 1
■ RX —Resistive unknown
■ F0 —Forcing 0
■ F1 —Forcing 1
■ FX —Forcing unknown

A few simple rules can be used to define how the resolution function
should work:

—Strongest strength always wins.


—If strengths are the same and values are different, return same
strength but X value.

Following are the type declarations needed for the value system:

PACKAGE ninepack IS
TYPE strength IS (Z, R, F);
TYPE nineval IS ( Z0, Z1, ZX,
TYPE nineval IS ( R0, R1, RX,
TYPE nineval IS ( F0, F1, FX );

TYPE ninevalvec IS ARRAY(natural RANGE <>) OF nineval;


Subprograms and Packages 125
TYPE ninevaltab IS ARRAY(nineval’LOW TO
nineval’HIGH) OF nineval;

TYPE strengthtab IS ARRAY(strength’LOW TO


strength’HIGH) OF nineval;

FUNCTION resolve9( s: ninevalvec) RETURN nineval;

END ninepack;

The package body contains the resolution function (package bodies are
discussed near the end of this chapter).

PACKAGE BODY ninepack IS


FUNCTION resolve9( s: ninevalvec) RETURN nineval IS
VARIABLE result: nineval;
CONSTANT get_strength : ninevaltab :=
(Z, --Z0
Z, --Z1
Z, --ZX
R, --R0
R, --R1
R, --RX
F, --F0
F, --F1
F); --FX

CONSTANT x_tab : strengthtab :=


(ZX, --Z
RX, --R
FX); --F
BEGIN
IF s’LENGTH = 0 THEN RETURN ZX; END IF;
result := s(0);

FOR i IN s’RANGE LOOP


IF get_strength(result) < get_strength(s(i)) THEN
result := s(i);

ELSIF get_strength(result) = get_strength(s(i)) THEN


IF result /= s(i) THEN
result := x_tab(get_strength(result));
END IF;

END IF;
END LOOP;
RETURN result;

END resolve9;
END ninepack;

The package ninepack declares a number of types used in this example,


including some array types to make the resolution function easier to
126 Chapter Five

implement. The basic algorithm of the function is the same as the fourval
resolution function; however, the operations with nine values are a little
more complex. Function resolve9 still does a pairwise comparison of the
input values to determine the resultant value. With a nine-value system,
the comparison operation is more complicated, and therefore some constant
arrays were declared to make the job easier.
The constant get_strength returns the driving strength of the driver
value. The constant x_tab returns the appropriate unknown nine-state
value, given the strength of the input. These constants could have been
implemented as IF statements or CASE statements, but constant arrays
are much more efficient.
In the nine-value system, there are three values at the lowest strength
level, so the variable result has to be initialized more carefully to predict
correct results. If there are no drivers, the range attribute of argument s
returns 0, and the default value (ZX) is returned.
Let’s look at a few examples of driver-input arguments and see what
the resolution function predicts. An example of two drivers is shown in
Figure 5-4.
This example contains two driver values, Z1 and R0. Variable result is
initialized to the first driver value, and the loop executes as many times
as there are drivers. The first time through the loop, result equals Z1 and
the first driver equals Z1. Variable result remains at Z1 because the
values are equal. The next time through the loop, variable result con-
tains Z1, and the second driver contains R0. The constant get_strength
returns strength R. The constant get_strength for variable result returns
strength Z. Strength R is lexically greater than strength Z. This is because
value R has a higher position number than Z, because R is listed after Z
in the type declaration for type strength. The fact that the new driver has

Initial
Figure 5-4 Value Driver Values
Nine State Resolution
with Two Values.
Z1 Z1 R0

Z1

R0 Resultant Value
Subprograms and Packages 127
a stronger strength value than variable result causes variable result to
be updated with the stronger value, R0.
Another example shows how the constant x_tab is used to predict the
correct value for conflicting inputs. The driver values are shown in the
array in Figure 5-5.
In this example, variable result is initialized to F0. The first iteration
of the loop does nothing because the first driver and the result-
initialization value are the same value. The next iteration starts with
variable result containing the value F0, and the next driver value as R0.
Because the value in variable result is greater in strength than the value
of the new driver, no action is implemented, except to advance the loop to
the next driver.
The last driver contains the value F1. The strength of the value contained
in variable result and the new driver value are the same. Therefore, the
IF statement checking this condition is executed and succeeds. The next
IF statement checks to see if the logical values are the same for both vari-
able result and the new driver. Variable result contains an F0, and the
new driver value contains an F1. The values are not the same, and the
x_tab table is used to return the correct unknown value for the strength
of the driver values. The x_tab table returns the value FX, which is returned
as the resolved value.
A more efficient method to implement the loop would be to skip the
first iteration where the first driver is compared to itself, because the
value in variable result is initialized to the first driver value. It is left
as an exercise to the reader to write this new loop iteration mechanism.

Initial
Figure 5-5
Value Driver Values
Nine State Resolution
with Three Values.
F0 F0 R0 F1

F0

F0

FX Resultant Value
128 Chapter Five

Although VHDL simulators can support any type of resolution that can
be legally written in the language, synthesis tools can only support a
subset. The reason stems from the fact that the synthesis tools must build
actual hardware from the VHDL description. If the Resolution Function
maps into a common hardware behavior such as wired-or or wired-and,
then most synthesis tools allow the user the ability to tag the resolution
function appropriately. For instance, a Resolution Function that performs
a wired-or function is tagged with an attribute that tells the synthesis
tools to connect the outputs together.

COMPOSITE TYPE RESOLUTION For simple signal values such as


the nineval and fourval types, it is easy to see how to create the resolu-
tion function. But for signals of composite types, it is not so obvious. How
can one value of a composite type be stronger than another?
The answer is that one value must be designated as weaker than all of
the other values. Then the principle is the same as any other type being
resolved. In the fourval type, the value Z was considered the weakest
state, and any of the other values could overwrite this value. In the
nineval type, all values with a strength of Z could be overridden by val-
ues with a strength of R or F, and all values with strength R could be over-
ridden by strength F.
To resolve a composite type, designate one value of the composite type
as unusable except to indicate that the signal is not currently being driven.
The resolution function checks how many drivers have this value and how
many drivers have a driving value. If only one driving value exists, then
the resolution function can return this value as the resolved value. If more
than one driving value is present, then an error condition probably exists
and the resolution function can announce the error.
A typical application for a composite type resolution function is shown
in Figure 5-6.
Signal XBUS can be driven from a number of sources, but hopefully only
one at a time. The resolution function must determine how many drivers
are trying to drive XBUS and return the correct value for the signal.
Following is the type declarations and resolution function for a com-
posite type used in such a circuit:

PACKAGE composite_res IS
TYPE xtype IS
RECORD
addr : INTEGER;
data : INTEGER;
Subprograms and Packages 129
MEMORY
Figure 5-6
Block Diagram of
Computer. CPU

XBUS

IO_PORT
DISK_CONTROL

END RECORD;

TYPE xtypevector IS ARRAY( natural RANGE <>) OF xtype;


CONSTANT notdriven : xtype := (-1,-1);

FUNCTION cresolve( t : xtypevector) RETURN xtype;


END composite_res;

PACKAGE BODY composite_res IS


FUNCTION cresolve( t : xtypevector) RETURN xtype IS
VARIABLE result : xtype := notdriven;
VARIABLE drive_count : INTEGER := 0;
BEGIN
IF t’LENGTH = 0 THEN RETURN notdriven;
END IF;

FOR i IN t’RANGE LOOP


IF t(i) /= notdriven THEN
drive_count := drive_count + 1;
IF drive_count = 1 THEN
result := t(i);
ELSE
result := notdriven;
ASSERT FALSE
REPORT “multiple drivers detected”
SEVERITY ERROR;
END IF;
END IF;
END LOOP;
RETURN result;
130 Chapter Five

END cresolve;
END composite_res;

Type xtype declares the record type for signal xbus. Type xtypevector
is an unconstrained array type of xtype values used for the resolution
function input argument t. Constant notdriven declares the value of the
record that is used to signify that a signal driver is not driving. Negative
number values were used to represent the notdriven state because, in this
example, only positive values are used in the addr and data fields. But
what happens if all of the values must be used for a particular type? The
easiest solution is probably to declare a new type which is a record, con-
taining the original type as one field of the record, and a new field which
is a boolean that determines whether the driver is driving or not driving.
In this example, resolution function cresolve first checks to make certain
that at least one driver value is passed in argument t (drivers can be turned
off using guarded signal assignment). If at least one driver is driving, the
loop statement loops through all driver values, looking for driving values.
If a driving value is detected, and it is the first, then this value is assumed
to be the output resolved value, until proven otherwise. If only one driving
value occurs, that value is returned as the resolved value.
If a second driving value appears, the output is set to the nondriven
value, signifying that the outcome is uncertain, and the ASSERT statement
writes out an error message to that effect.
In this example, the negative numbers of the integer type were not
used except to indicate whether the signal was driving or not. We reserved
one value to indicate this condition. Another value could be reserved to
indicate the multiple-driven case such that when multiple drivers are
detected on the signal, this value would be returned as the resolved value.
An example might look like this:

CONSTANT multiple_drive : xtype := (-2,-2);

This constant provides the capability of distinguishing between a non-


driven signal and a multiple-driven signal.

RESOLVED SIGNALS So far we have discussed how to write resolu-


tion functions that can resolve signals of multiple drivers, but we have not
discussed how all of the appropriate declarations are structured to ac-
complish this.
Resolved signals are created using one of two methods. The first is
to create a resolved subtype and declare a signal using this type. The
second is to declare a signal specifying a resolution function as part of
the signal declaration.
Subprograms and Packages 131
Let’s discuss the resolved subtype method first. To create a resolved sub-
type, the designer declares the base type, then declares the subtype speci-
fying the resolution function to use for this type. An example looks like this:

TYPE fourval IS (X, L, H, Z); -- won’t compile


SUBTYPE resfour IS resolve fourval; -- as is

The first declaration declares the enumerated type fourval. The second
declaration is used to declare a subtype named resfour, which uses a
resolution function named resolve to resolve the base type fourval. This
syntax does not compile as is because the function resolve is not visible.
To declare a resolved subtype requires a very specific combination of
statements, in a very specific ordering.
Following is a correct example of the resolved type:

PACKAGE fourpack IS
TYPE fourval IS (X, L, H, Z); -- line 1
TYPE fourvalvector IS ARRAY(natural RANGE <>)
OF fourval; -- line 2

FUNCTION resolve( s: fourvalvector) RETURN fourval;


-- line 3

SUBTYPE resfour IS resolve fourval; -- line 4


END fourpack;

The statement in line 2 declares an unconstrained array of the base


type that is used to contain the driver values passed to the resolution
function. The statement in line 3 declares the definition of the resolution
function resolve so that the subtype declaration can make use of it. The
body of the resolution function is implemented in the package body. Finally,
the statement in line 4 declares the resolved subtype using the base type
and the resolution function declaration.
The order of the statements is important, because each statement
declares something that is used in the next statement. If the uncon-
strained array declaration is left out, the resolution function could not be
declared, and if the resolution function was not declared, the subtype
could not be declared.
The second method of obtaining a resolved signal is to specify the reso-
lution function in the signal declaration. In the following example, a signal
is declared using the resolution function resolve:

PACKAGE fourpack IS
TYPE fourval IS (X, L, H, Z);
132 Chapter Five

TYPE fourvalvector IS ARRAY(natural RANGE <>) OF fourval;

FUNCTION resolve( s: fourvalvector) RETURN fourval;


SUBTYPE resfour IS resolve fourval;
END fourpack;

USE WORK.fourpack.ALL;
ENTITY mux2 IS
PORT( i1, i2, a : IN fourval;
q : OUT fourval);
END mux2;

ARCHITECTURE different OF mux2 IS


COMPONENT and2
PORT( a, b : IN fourval;
c : OUT fourval);
END COMPONENT;

COMPONENT inv
PORT( a : IN fourval;
b : OUT fourval);
END COMPONENT;

SIGNAL nota : fourval;

-- resolved signal
SIGNAL intq : resolve fourval := X;

BEGIN

U1: inv PORT MAP(a, nota);

U2: and2 PORT MAP(i1, a, intq);

U3: and2 PORT MAP(i2, nota, intq);

q <= intq;

END different;

The package fourpack declares all of the appropriate types and function
declarations so that the resolution function resolve is visible in the entity.
In the architecture declaration section, signal intq is declared of type
fourval, using the resolution function resolve. This signal is also given
an initial value of X.
Signal intq is required to have a resolution function because it is the
output signal for components U2 and U3. Each component provides a driver
to signal intq. Resolution function resolve is used to determine the end
result of the two driver values. Signal nota is not required to have a reso-
lution function because it only has one driver, component U1.
Transistori MOS
Ing. Ivan Blunno
21 aprile 2005

1 Introduzione
In questa dispensa verranno presentati i transistor MOS (Metal Oxide Semicon-
ductor) di tipo N e P dal punto di vista del loro funzionamento elettrico, senza
analizzare i fenomeni di trasporto di carica che ne determinano il comportamen-
to. In particolar modo verranno discusse le equazioni e le curve caratteristiche
dei componenti MOS.

2 Il transistor NMOS
Il transistor NMOS ha il simbolo circuitale mostrato in figura 1. I tre morsetti
sono chiamati gate (G), source (S) e drain (D). Il funzionamento di base del
transistor NMOS puo’ essere riassunto dicendo che
• La corrente che entra nel morsetto G è nulla (impedenza infinita).

• La corrente che scorre tra i morsetti D e S (IDS ) dipende in modo NON


lineare dalle tensioni VGS e VDS .

I parametri fondamentali che definiscono un transistor NMOS sono:


• La tensione di soglia VT n > 0.

G I DS VDS

VGS S

Figura 1: Transistor NMOS: simbolo circuitale

1
• Il guadagno βn = µn CLox W . In questa formula µn rappresenta la mobilità
degli elettroni, Cox la capacità dello strato di ossido tra gate e substrato
e W e L rispettivamente la larghezza e la lunghezza del canale.
A seconda dei valori delle tensioni VDS e VGS possono essere individuate 4
zone di funzionamento del transistor.

Zona di interdizione: VGS < VT n , ∀VDS

IDS = 0

Zona lineare: VGS > VT n , VDS ¿ VGS − VT n

IDS = βn (VGS − VT n )VDS

Zona triodo: VGS > VT n , VDS < VGS − VT n


h V2 i
IDS = βn (VGS − VT n )VDS − DS
2

Zona di saturazione: VGS > VT n , VDS > VGS − VT n


βn
IDS = (VGS − VT n )2
2

Il comportamento globale del transistor NMOS può essere osservato nel gra-
fico di figura 2. Il grafico è parametrico secondo il valore di VGS − VT n . Dalle
equazioni mostrate in precedenza si può notare che il passaggio dalla zona trio-
do a quella di saturazione si ha quando VDS = VGS − VT n . Sostituendo questa
uguaglianza nell’equazione della zona triodo (o di quella della zona di satura-
zione) si ottiene che IDS = β2n VDS 2
. Questa relazione è un funzione parabolica
che rappresenta il luogo dei punti di passaggio dalla zona III alla zona IV. La
zona I rappresenta la zona di interdizione in cui la corrente IDS vale 0 (tutto
l’asse VDS ). La zona II è la zona lineare. In questa zona il transistor si compor-
ta come una resistenza variabile di valore R = βn (VGS1−VT n ) . Infine in zona di
saturazione (zona IV) il transistor si comporta come un generatore di corrente
di valore I = βn (VGS − VT n )2 .

3 Il transistor PMOS
Il comportamento del transistor PMOS (il cui simbolo circuitale è rappresentato
in figura 3) può essere derivato da quello del transistor NMOS fatte salve alcune
differenze che verranno di seguito evidenziate:

2
I DS

III IV
VGS - VTn = 4

VGS - VTn = 3
II
VGS - VTn = 2

VGS - VTn = 1

I VDS
Figura 2: Transistor NMOS: curve caratteristiche.

S
VGS

G I DS VDS

Figura 3: Transistor PMOS: simbolo circuitale

3
VDD

I DS R
vo

vi
VDS
VGS

Figura 4: Inverter NMOS

• La tensione di soglia VT p < 0. La condizione di conduzione sarà pertanto


VGS < VT p la cui analogia con l’equivalente relazione del NMOS può essere
meglio rilevata considerando i valori in modulo: |VGS | > |VT p |
• Le tensioni VGS , VDS e la corrente IDS sono tutte negative. Anche in
questo caso le equazioni che le contengono rimangono invariate rispetto
al caso del NMOS se invece del valore reale si considera il loro valore in
modulo.
µp Cox W
• Il guadagno sarà βp = L dove in questo caso µp rappresenta la
mobilità delle lacune.
A puro titolo di esempio viene di seguito riportata l’equazione della corrente
IDS che scorre in un PMOS in zona lineare (|VGS | > |VT n |):

|IDS | = βp (|VGS | − |VT p |)|VDS |

La corrente scorrerà da S verso D.

4 Inverter NMOS
Allo scopo di meglio comprendere il funzionamento del transistor MOS e di pre-
sentarne un primo possibile utilizzo analizziamo il funzionamento dell’inverter
NMOS rappresentato in figura 4. Il funzionamento di principio è il seguente:
per una tensione di ingresso vi = 0 il transistor sarà interdetto e la corrente
IDS = 0. Non essendoci caduta sulla resistenza R la tensione di uscita vo ri-
sulterà pari a VDD . Per una tensione di ingresso vi = VDD il transistor sarà
in conduzione e la corente IDS comporterà un abbassamento della tensione vo .

4
Il comportamento di questo circuito è proprio quello di un inverter in cui una
tensione bassa in ingresso comporta una tensione alta in uscita e viceversa.
Come esercizio proviamo a determinare quale deve essere il valore di R tale
da avere vo = VDD
2 per vi = VDD
2 nel caso che:

W = 2L , VT n = 0.7V , µn Cox = 100µA/V 2 , VDD = 5V

In queste condizioni
µn Cox W
βn = = 200µA/V 2
L
Poiché deve essere vo = vi = VDD
2 e poiché vo = VDS e vi = VGS avremo
anche che VDS = VGS . Questa condizione identifica la zona di funzionamento
del NMOS che è quella di saturazione. La corrente che scorre in R sarà pertanto
µ ¶2
βn VDD
IDS = − VT n = 324µA/V 2
2 2
vo = VDD − IDS R
VDD
= VDD − IDS R
2
VDD
R= = 7.72kΩ
2IDS
Completiamo questo semplice esercizio determinando qual è il valore mini-
mo di vo che si può ottenere. Tale valore si otterrà per il massimo valore di
IDS . Allora applichiamo il massimo valore di tensione all’ingresso: vi = VDD .
In questo caso non possiamo sapere il quale zona starà lavorando il transistor.
In ogni caso, poiché VGS − VT n = 5 − 0.7 = 4.3V e poiché ci aspettiamo che
VDS abbia un valore basso, sicuramente non saremo in zona di saturazione. In
ogni caso possiamo verificare numericamente che l’assunzione di essere in zona
di saturazione sarebbe sbagliata:
βn
IDS = (VDD − VT n )2 = 1.85mA
2
vo = VDD − IDS R = 5 − 14.27 = −9.27V

Ovviamente non può essere vo < 0 e quindi è evidente che l’assunzione


di trovarci in zona di saturazione è sbagliata. Analogamente si verifica (con
qualche calcolo in più) che il transistor non può essere neanche in zona triodo.
Allora il transistor si trova in linearità. È cosı̀ possibile calcolare la corrente IDS .

5
vo

vi

Figura 5: Inverter NMOS: realizzazione circuitale

IDS = βn (VGS − VT n )VDS = βn (VDD − VT n )(VDD − IDS R)

βn (VDD − VT n )VDD 4.3 · 10−3


IDS = = = 562µA
βn (VDD − Vtn )R + 1 7.64
vo = VDD − IDS R = 5 − 4.3 = 0.7V

In realtà gli inverter NMOS vengono realizzati sostituendo alla resistenza di


pull-up un transistor PMOS come mostrato in figura 5.

6
In"alcuni"sistemi"VHDL,"il"nome"del"file"in"cui"è"contenuta"la"top"entity"deve"essere"il"nome"della"
top"entity"(e"nel"nostro"esempio"coincidono.)."
"
Commentiamo"i"risultati"della"simulazione."
A" 0" ms," cioè" all’inizio" dei" tempi," il" colore" ottenuto" risulta" nero." All’inizio" dei" tempi" si" ha" una"
transizione"automatica:"tutti"i"segnali"vengono"marcati"come"“hai"avuto"una"transizione”"e"il"valore"
assunto,"se"non"è"stato"specificato"come"default"nella"fase"di"dichiarazione,"è"quello"più"a"sinistra"
del"tipo,"per"i"tipi"scalari."
A"0"ms"(ricordiamo"che"però"la"risoluzione"di"questo"sistema"è"molto"più"elevata)"il"colore"assunto"
diventa"giallo,"perché"in"un"tempo"brevissimo"a,"b,"c,"d"ed"e"diventano"contemporaneamente"uno"
nero,"uno"giallo,"uno"rosso,"uno"blu"e"uno"verde."Essi"sono"tutti"collegati"al"risultato"e"il"più"chiaro"
fra" questi" colori," secondo" la" nostra" convenzione" sulla" funzione" di" risoluzione," è" giallo," per" cui" il"
risultato"diventa"giallo."
Dopo"20"ns"a"diventa"bianco."A"questo"punto"il"colore"più"chiaro"che"c’è"nell’insieme"è"bianco"e"
infatti,"a"20"ns,"il"colore"ottenuto"diventa"bianco."
Se"poi"a"cambia"di"nuovo"e"diventa"verde"ed"è"rimasto"il"b"ancora"giallo,"sarà"il"giallo"ad"essere"il"
colore"più"chiaro"tra"quelli"che"abbiamo"visto."
FINE+PARTE+AL+PC+
+
Quanto" abbiamo" fatto" può" servire" ad" esempio" a" descrivere" il" comportamento" di" un" sistema"
ridondante." Supponiamo" ad" esempio" che" ci" siano" 5" sensori" di" carrello" sollevato/sceso" su" un"
aeroplano:" le" regole" di" sicurezza," per" esempio," dicono" che" almeno" 4" devono" confermare" che" il"
carrello"è"sceso,"altrimenti"l’atterraggio"non"si"può"fare."Tipicamente"il"segnale"della"spia"di"carrello"
sceso"è"un"segnale"resolved"e"lo"possiamo"vedere"in"questa"maniera:"riceve"in"ingresso"il"segnale"di"
5"oggetti"e"le"uscite"possibili"sono"“il"carrello"è"giù”"oppure"“il"carrello"non"è"giù”"oppure"quando"
c’è"discordanza"maggiore"di"una"certa"soglia"(ad"esempio"3"su"2"non"è"accettabile)"significa"che"c’è"
un"malfunzionamento"del"sistema"e"questo"viene"segnalato."Questo"è"un"tipico"contesto"in"cui"un"
segnale"resolved"si"può"usare.""
"
Vediamo"il"tipo"standard"logic"che"è"un"tipo"resolved,"cioè"è"l’insieme"di"un"tipo"unresolved"(che"è"
ulogic,"cioè"unresolved"standard"logic,"che"è"un"tipo"definito"per"enumerazione)"più"una"funzione"
di"risoluzione"il"cui"scopo"è"quello"di"tendere"a"rappresentare"il"comportamento"di"sistemi"logici"
reali"in"un"certo"numero"di"condizioni"che"ora"andiamo"ad"elencare."
"
type"std_ulogic"is(‘u’,"’x’,"’0’,"’1’,"’z’,"’w’,"’L’,"’H’,"’u‘)""""
è" il" tipo" di" partenza" dal" quale" definiremo" il" tipo" che" useremo" sempre" e" che" è" std_logic."
Quest’ultimo" è" un" sottotipo" dello" standard" ulogic" con" associata" una" funzione" di" risoluzione."
Vediamo" cosa" significa" ciascuno" di" quei" valori." Essi" fanno" riferimento" non" tanto" al" processo" di"
descrizione"e"simulazione"quanto"al"processo"di"sintesi."
Ciascuno" degli" oggetti" in" questione" ha" un" significato." Ad" esempio" la" lettera" u" sta" per" non"
inizializzato"(uninitialized).""

12"
"
Vediamo"qual"è"lo"scopo"di"tutto"questo."Quando"descriviamo"un"sistema"reale,"in"un"sistema"di"
simulazione" possiamo" assumere," mano" a" mano" che" ci" serve," che" una" certa" uscita" ed" un" certo"
ingresso" abbiano"un" certo" valore" e" lo" scriviamo" esplicitamente." Dobbiamo" specificare" noi" in" che"
stato" si" trovino" tutti" i" segnali." Di" default," un" segnale" che" non" viene" esplicitamente" inizializzato"
assume"il"valore"più"a"sinistra"dell’insieme."Quella"u"c’è"messa"in"maniera"tale"che,"se"ad"un"certo"
punto"esaminiamo"lo"stato"del"sistema"un"momento"dopo"la"partenza,"ci"possiamo"rendere"conto"
che"ci"sono"alcuni"segnali"che"non"sono"stati"esplicitamente"fissati"da"noi"perché"hanno"il"valore"u."
Facciamo"l’esempio"del"caso"del"tipo"bit"in"cui"abbiamo"soltanto"i"valori"0"o"1:"se"vediamo"che"un"
segnale"bit"ha"il"valore"0,"non"sappiamo"se"esso"ha"tale"valore"perché"non"lo"abbiamo"inizializzato"
e" quello" è" il" valore" di" default" che" viene" assunto" al" momento" della" partenza" del" sistema" o" se"
l’abbiamo"messo"noi"esplicitamente"a"0."L’uso"di"quella"u"ci"consente"di"verificare"questo"fatto."
Torniamo"a"discutere"i"singoli"valori."
0"e"1"sono,"in"qualche"maniera,"il"valore"di"livello"logico"alto"e"livello"logico"basso"corrispondenti"
alla" situazione" in" cui" questi" valori" vengono" forzati" (infatti" si" chiamano" forced* 0" e" forced* 1)."
Immaginiamo"ad"esempio"ad"una"logica"a"rapporto:"stiamo"parlando"del"segnale"che"viene"messo"
a"0"o"a"1"in"maniera"forte"attraverso"l’interuttore."Per"intenderci,"pensiamo"al"seguente"inverter"a"
rapporto"fatto"con"un"numos"o"con"qualunque"altro"sistema."

"
"
Immaginiamo"di"collegare"all’uscita"di"questo"inverter"qualunque"altra"cosa,"un’altra"porta,"ad"es."
l’uscita" di" un" altro" inverter." In" questo" sistema" lo" 0" si" intende" forte" (cioè" descriveremmo" il"
comportamento"del"livello"logico"basso"di"questo"sistema"come"forte)"perché,"se"l’interruttore"a"
sinistra"è"chiuso,"in"ogni"caso"la"tensione"del"nodo"A"è"vicino"a"0,"indipendentemente"da"quello"che"
usciva"dall’altra"parte."
"

A"

"
"
In" questo" tipo" di" sistema" lo" 0" è" forte" (perché" è" forzato" il" collegamento" a" massa)" e" l’1" è" debole."
Infatti" se" l’uscita" dell’inverter" a" sinistra" è" a" 1," lo" è" perché" l’numos" è" spento," ma," se" l’uscita" in"
questione"è"collegata"all’uscita"di"un"altro"inverter"numos"(come"in"figura"sopra)"con"l’interruttore"
chiuso,"la"tensione"in"A"è"0."
Nel"caso"del"cumos,"l’1"e"lo"0"sono"entrambi"di"tipo"forte"(forced)."
13"
"
A" rappresentare" le" situazioni" di" 1" e" di" 0" deboli," cioè" che" non" sono" forti," intervengono" i" valori"
L(basso" debole)" ed" H(alto" debole)." Pensiamo" sempre" all’ultima" figura" vista." Il" sistema" a" sinistra,"
visto" come" inverter," che" ha" uno" 0" forte" ed" 1" debole." Lo" stesso" vale" per" il" sistema" a" destra."
Immaginiamo"che"i"due"sistemi"siano"scollegati:"
"

‘H’"
‘0’"

"
Immaginiamo"l’interruttore"a"sinistra"chiuso"e"quello"a"destra"aperto.""
Nell’uscita"a"destra"avremmo"un"1"debole"perché"c’è"il"collegamento"della"resistenza"verso"l’alto."A"
sinistra"avremmo"uno"0"forte."Ci"chiediamo"cosa"succederebbe"nella"realtà"se"questi"due"sistemi"
fossero" collegati"tra"di" loro." Quello" che" succederebbe" è" che" avremmo" una" tensione" vicina" allo" 0"
perché"avremmo"un"interruttore"chiuso"col"parallelo"di"due"resistenze,"salvo"questioni"sui"margine"
di"rumore."Come"risultato"dello"0"forte"e"di"1"alto"debole"otterremmo"dunque"una"tensione"che"è"
vicina"a"0."
Lo" scopo" di" aver" introdotto" questo" 0" forte" e" questo" alto" debole," in" questo" caso," è" quello" di"
rappresentare"questa"situazione."Diciamo"meglio"che"la"funzione"di"risoluzione"è"fatta"in"modo"tale"
da" dare" interpretazione"a" questa" situazione." Se"ci" troviamo"nella" situazione" in" cui" ad" uno" stesso"
segnale"arrivano"uno"0"forte"e"un"1"debole,"la"funzione"di"risoluzione"assegnerà"a"questo"segnale"lo"
0"forte."
"
Allo"stesso"modo"potremmo"immaginare"quello"che"succede"nel"caso"di"un"inverter"pumos"a"carico"
resistivo."Questa"volta"sarà"l’1"ad"essere"forte"perché"l’1"è"ottenuto"con"l’interruttore"chiuso"ed"è"
lo" 0" ad" essere" debole." Immaginiamo" di" avere" due" inverter" pumos" e" di" fare" il" seguente"
collegamento:"
"

‘1’" ‘1’"
‘L’" ‘L’"

"
"
Sia"a"sinistra"che"a"destra"possiamo"avere"o"un"1"forte"o"uno"0"debole."Nel"caso"in"cui"“si"scontrino”"
(cioè" sono" collegati" tra" di" loro)" un" 1" forte" ed" uno" 0" debole" vince" l’1" forte," quindi" il" risultato" di"
questo"collegamento"della"funzione"di"risoluzione"è"quello"di"tirar"fuori"un"1"forte."
Non"si"può"però"risolvere"tutto."Immaginiamo"ad"esempio"di"avere"la"seguente"situazione"con"un"
inverter"nmos"a"carico"resistivo"nmos"ed"un"inverter"pmos"con"carico"resistivo:""
"
14"
"
‘0’"
‘H’" ‘1’"
‘L’"

"
A"sinistra"abbiamo"o"uno"0"forte"o"un"1"debole,"a"destra"abbiamo"un"1"forte"o"un"basso"debole."
Quando"“si"scontra”"l’H"debole"e"l’1"forte"abbiamo"l’1"forte,"quando"si"scontrano"l’L"basso"e"lo"0"
forte"abbiamo"uno"0"forte."Quando"però"si"scontrano"lo"0"forte"e"l’1"forte,"cioè"nella"situazione"in"
cui" entrambi" i" mos" conducono" come" interruttori," non" sappiamo" bene" quello" che" succede," è"
difficile" prevedere" quello" che" succede." Di" questa" cosa" si" tiene" conto" in" vhdl" nella" funzione" di"
risoluzione:" nel" caso" in" cui" sono" collegati" tra" di" loro" uno" 0" forte" ed" un" 1" forte," il" risultato" che"
otteniamo"è"X."
X" sta" per" forcing* unknown" non" che" vuol" dire:" c’erano" due" segnali" contrastanti" (0" e" 1)," il" valore"
logico"assegnato"è"“non"lo"so"che"cosa"devo"dirti”."La"stessa"cosa"avviene"se"venissero"collegati"l’H"
debole" e" l’L" debole." In" questo" caso" avremmo" un" partitore" di" resistenze" e" il" valore" all’uscita" non"
sappiamo"se"è"vicino"allo"0,"se"è"vicino"all’1"o"se"è"intermedio."In"una"situazione"del"genere"viene"
restituito"W"che"sta"per"weak*unknown."Il"vantaggio"di"avere"il"weak"unkown"è"che,"se"abbiamo"un"
weak"unknown"collegato"ad"1"forte"o"ad"uno"0"forte"avremmo"rispettivamente"un"1"forte"o"uno"0"
forte.""
Forcing"unknown"collegato"con"forcing"unknown"dà"forcing"unknown."Forcing"unknown"collegato"
con"H"debole"o"con"L"debole"dà"sempre"forcing"unknown."
Z" è" la" situazione" di" alta" impedenza," cioè" il" tipo" di" uscita" che" otterremmo" per" esempio" nella" ttl"
tristate" nella" situazione" in" cui" quel" segnale" si" comporta" come" un" ramo" aperto." Nei" sistemi" mos"
possiamo"ottenere"un"sistema"tristate."Immaginiamo"un"inverter"cmos"e"poi"una"porta"pass"gate:"
"

"
"
Quando"il"pass"gate"è"nella"situazione"open"in"quel"nodo"è"come"se"non"avessimo"niente."Quando"
Z," alta" impedenza," è" collegato" a" qualsiasi" altra" cosa," questo" non" influisce" in" nessun" modo:" il"
risultato"di"Z"e"1"è"1,"il"risultato"di"Z"e"0"è"0,"il"risultato"di"Z"e"X"è"X,"il"risultato"di"Z"e"W"è"W"e"così"
via."
"
Il" valore" meno" ‘u‘" sta" per" non" specificato" e" non" è" tanto" utile," nel" senso" che" non" trova" riscontro"
nella" pretesa" di" rappresentare" situazioni" realistiche" come" quelle" che" abbiamo" visto." Viene" usato"
piuttosto" nella" fase" di" ottimizzazione" di" un" sistema," quando" dopo" la" nostra" descrizione" c’è"
qualcuno" che" si" deve" preoccupare" di" costruirlo." Ad" esempio" i" sistemi" di" sintesi" automatica"

15"
"
gradiscono"che,"laddove"non"abbiamo"necessità"di"sapere"che"valore"debba"avere"un"segnale"(nel"
senso" che" per" esempio" sappiamo" che" quel" tipo" di" combinazione" di" bit" non" verrà" mai" ottenuta"
perché"tanto"in"quello"stato"non"ci"andremo"mai"in"una"macchina"a"stati),"lasciamo"tale"valore"non"
specificato."In"questo"modo,"chi"deve"costruire"questo"sistema"gli"possa"mettere"lo"0"o"l’1"come"
valore" a" seconda" di" quello" che" gli" conviene" in" termini" di" semplicità," di" velocità" o" di" altra" roba."
Quindi" quel" meno" viene" introdotto" nella" prospettiva" che" il" file" vhdl" sia" il" punto" di" partenza" non"
tanto" verso" la" descrizione" del" sistema" ma" per" la" sintesi" di" un" sistema" vero" e" proprio" in" forma"
automatica.""
È" possibile" fare" una" tabellina" di" tutte" le" combinazioni" possibili." La" funzione" di" risoluzione" deve"
prevedere" cosa" succede" in" tutti" i" casi" in" cui" più" segnali" sono" collegati" tra" di" loro." Tale" tabellina"
segue"la"logica"che"abbiamo"detto.""
Tutte" le" funzioni" di" risoluzione" devono" godere" in" qualche" maniera" della" proprietà" associativa" e"
della" proprietà" distributiva." Se" abbiamo" la" tabellina" di" risoluzione" per" due" segnali," possiamo"
estenderla" a" 3" segnali" e" così" via" perché" prendiamo" il" risultato" di" due" segnali" e" quello" diventa" il"
termine" per" poter" calcolare" il" prossimo" elemento" di" risoluzione." Non" è" detto" che" sia" così." Se"
abbiamo"da"sapere"cosa"succede"se"abbiamo"0,"1"e"W."0"e"1"dà"forcing"unknown"(X)"e"poi"X"e"W"dà"
X." Quando" si" confrontano" un" non" inizializzato" con" qualunque" degli" altri" valori," il" risultato" è" non"
inizializzato"per"tutti"perché"il"non"inizializzato"potrebbe"essere"un"valore"forte,"un"valore"debole"o"
qualunque"altra"cosa."
"
"
"
"
"
"
"
"
+
+

16"
"
Con riferimento alla definizione delle Entità c’è la possibilità, nella definizione, di impiegare dei parametri che
servono a rendere configurabile o programmabile la entità stessa.
Questo avviene tramite dei parametri che vanno inclusi nella parte dichiarativa dell’entità e che si chiamano
“Generic Constant” . Ad esempio nella definizione di una porta NOR in cui è possibile di volta in volta, a
seconda del suo utilizzo, definire gli ingressi e i tempi di ritardo. In questo modo evitiamo di dover riscrivere
tante entità diverse ognuna con i suoi tempi di ritardo. Proprio in riferimento alla porta NOR vediamo un
esempio di applicazione dei parametri Generic:

entity nor_gen is
generic (num_ingr:integrer:=2; prop_delay:time:=5ns);
port (ingressi: in bit_vector(1 to num_ingr); y:out bit);
end entity nor_gen;

Nella seconda riga di comando abbiamo definito due generic che saranno poi sostituite di volta in volta e alle
quali diamo un valore di default iniziale. (Le generic sono il numero di ingressi e il tempo di propagazione).
La definizione delle porte avviene dopo la dichiarazione delle generic poiché è chiaro che se gli ingressi
cambiano, cambieranno i valori delle porte.
Immaginiamo di voler utilizzare questa entità come parte della libreria Work, allora dovremo scrivere, con
riferimento ad una architettura che chiamiamo Behav:

porta nor1: work.nor_gen(behav)


generic map (num_ingr=>3, prop_delay=>20ns);
port map (………………………..);

Possiamo avere una eventuale altra porta (nor2):

porta nor2: work.nor_gen(behav)


generic map(prop_delay=>50ns);

In questo caso vediamo che abbiamo cambiato il valore di prop_delay mentre non tocchiamo il numero di
ingressi che rimane quello di default.Con questo sistema possiamo scrivere tutte le porte NOR che ci servono
cambiandone di volta per volta i parametri fondamentali.
Vediamo come si procede quando si vogliono realizzare dei circuiti strutturati, dove siamo in presenza di una
ripetizione di un certo numero di elementi,ad esempio un sommatore con un certo numero di bit.
Se volessi realizzare un FullAdder a 32 bit ed avessi a disposizione un componente singolo che mi realizza il
FullAdder , dovrei collegare 32 FullAdder tra loro e controllare che i collegamenti siano esatti.
Oppure se si vuole realizzare un moltiplicatore (n x m); questo si può comporre come n2 celle tutte uguali e poi
specializzarle singolarmente. Quindi abbiamo all’interno della struttura principale nxm oggetti tutti uguali tra
loro e collegati tra loro. E’ chiaro che scrivere a mano una struttura del genere può comportare facilmente degli
errori.
Lo statement che ci consente di realizzare una architettura composta da un certo numero di elementi base (come
ad esempio per realizzare un sommatore) è lo statement Generate.

begin

Generate_label: for identifier in discrete_range generate


[{block_declarative_item} begin]
{concurrent_statement}
end generate [generate_label];
Vediamo come descrivere un somatore a n bit in termini di un componente che io immagino di averea
disposizione e di descrivere il sommatore n bit con lo statement generate. Inoltre vediamo come stabilire il
collegamento tra il nostro FullAdder e l’entità con cui verrà implementato.

entity adder_enne_bit is
generic (enne:integre:=8);
port (
c_ in: in bit;
x_ in: in bit_vector (enne-1 downto 0);
y_ in: in bit_vector (enne-1 downto 0);
c_ out: out bit;
s_ out: out bit_vector (enne-1 downto 0 );
);
end entity adder_enne_bit;

Una architettura possibile per l’entità che abbiamo appena descritto può essere la seguente:

architecture gen1 of adder_enne_bit is


signal carry_chain: in bit_vector( enne-2 downto 0);

Abbiamo scritto “enne-2” perchè per fare il collegamento tra il c-out e il c-in di ogni blocco sommatore il
minimo numero che mi serve è enne-2.

component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit;
);
end component cfa;

All’interno di questa architettura devo mettere una catena di FullAdder collegati tra loro secondo una certa
regola.
Ogni FA deve avere 3 ingressi e 2 uscite come tutti. Posso collegare i vari FA con uno statement, facendo
attenzione alla differenza che esiste tra le celle che si trovano in mezzo alla catena e le celle esterne. (Figura).

begin

gen_stat: for index in 0 to (enne-1) generate


prima_cella: if (index=0) generate
begin
cella: component cfa
port map
(a=>x_in(0), b=>y_in(0), ci=>c_in, s=>s_out(0), co=>carry_chain(0));
end generate prima_cella;

ultima_cella: if (index=(enne-1)) generate


cella:component cfa
port map
(a=>x_in(enne-1), b=>y_in(enne-1), ci=>carry_chain(enne-2), s=>s_out(enne-1), co=>c_out);
end generate ultima_cella;

altre celle: if ((index/=0) and (index/=(enne-1)) generate


cella: component cfa
port map
(a=>x_in(index), b=>y_in(index), ci=>carry_chain(index-1), s=>s_out(index), co=>carry_chain(index));
end generate altre_celle;

end generate gen_stat;


end architecture gen1;

Grazie all’uso delle label e al ciclo for utilizzato nello statement generate, si tiene conto automaticamente del
valore dell’indice nel momento in cui viene generate una cella e tutte le componenti della cella sono
univocamente determinate.
Ricordiamo che stiamo parlando della entità “adder_enne_bit” !
Supponiamo che sia già definita l’entità adder_enne_bit (alla quale si fanno corrispondere i componenti cfa)
nello stesso file che contiene la definizione di entità ed architettura del sommatore. ( Archietettura Behav)
Dobbiamo configurare il sistema e la procedura è la seguente:

configuration sommatore of adder_enne_bit is


for gen1
for gen_stat
for prima_cella
for cella: cfa
use entity work.full_adder (behav);
end for;
end for;

for ultima_cella
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;

for altre_celle
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;
end for;
end for;
end configuration sommatore;

Vogliamo verificare se il comportamento della entità appena definita è corretto. Allora testiamo il tutto
mediante un testbench. Fissiamo enne a 4. La struttura del testbench è la seguente:

entity testbench is
end entity testbench;

architecture behav of testbench is

signal add1, add2, somma: bit_vector (3 downto 0);


signal riporto: bit;

begin

sommatore4: configuration work.sommatore


generic map (enne=>4)
port map (x_in=> add1, y_in=>add2, c_in=>’0’, s_out=> somma, c_out=>riporto);

process is
begin
add1<=”0000”;
add2<=”0000”;
wait for 100ns; (programmo le varie transizioni dei segnali per verificare che
add1<=”0001”; tutto funzioni correttamente)
add2<=”0001”;
wait for 100ns;
.
.
.
Ecc
.
end process;
end architecture behav;
Generic, component, configuration and generate

In VHDL è possibile descrivere il comportamento di una entità in termini della interconnessione fra
altre entità. Nell'esempio seguente, descriviamo l'architettura di un flip flop SR mediante
l'inetrconnessione di due porte nor.

entity nor_port is
port (a,b:in bit; y:out bit);
end entity nor_port;

architecture behav of nor_port is


begin
y<=a nor b after 5 ns;
end architecture behav;

entity ff_sr is
port (s,r: in bit; q, not_q: out bit);
end entity ff_sr;

architecture struct of ff_sr is


signal q_int, not_q_int: bit;
begin
nor1:entity work.nor_port port map (a=>s, b=>q_int, y=>not_q_int);
nor2:entity work.nor_port port map (a=>r, b=>not_q_int, y=>q_int);

q<=q_int;
not_q<=not_q_int;

end architecture struct;

Le modalità di descrizione strutturale dell'esempio precedente prevedono che le entità che


intendiamo usare come parte di una architettura siano state precedentemente definite e
immagazzinate some parte di una libreria (la libreria predefinita work nel caso precedente). Questo
significa che per poter descrivere l'architettura del flip flop in termini della interconnesisone di
entità, queste ultime devono essere completamente definite prima che possano essere impiegate
nella descrizione stessa.
Questi vincoli costituiscono una limitazione nella possibilità di affrontare la progettazione di un
sistema digitale in un approccio top-down, ovvero in una situazione in cui si voglia in primo luogo
definire l'architettura generale del sistema in tremini della interconnessione di blocchi funzionali per
poi passare in un secondo momento a definire l'architettura dettagliata di ogni sottoblocco.
Nell'esempio che abbiamo riportato più sopra questa limitazione può apparire tollerabile, ma non è
necessariamente così nel caso di sistemi più complessi. Si immagini di dover progettare un
microcontrollore, ovvero un sistema che sia composto da un microprocessore, una memoria e
numerose periferiche interconnesse fra loro a formare un unico circuito integrato. In una situazione
del genere è praticamente indispensabile affrontare il progetto a partire dal livello di astrazione più
alto per poi scendere via via a definire le specifiche a più basso livello.
Fortunatamente in VHDL è prevista la possibilità di poter descrivere una architettura in tremini
della interconnessione fra entità di cui si conosca (o si preveda) in dettaglio la configurazione delle
porte ma non la funzione o la struttura interna dettagliata. Ciò avviene mediante il riscorso all'uso
del costrutto component che varrà descritto più avanti. Evidentemente, a un certo punto dello
sviluppo del progetto sarà necessario definire in dettaglio l'architettura di ogni component. Questo

1
avviene mediante una procedura di configurazione (configuration) che consente di associare una
specifica entità (e relativa architettura) a ciascun component, una volta che siano disponibili le
descrizioni funzionali o strutturali complete delle entità che costituiscono l'effettiva
implementazione di ciascun componente.

In questa stessa sede affronteremo il problema della “programmabilità” delle entità, intendendo con
questo la possibilità di specificare alcuni parametri relativi al comportamento di una unità che, in
generale, possono essere conosciuti solo all'atto del suo impiego in una descrizione strutturale nei
termini dell'esempio proposto o, come vedremo più avanti, all'atto della operazione di
configurazione. Per comprendere l'ooprtunità di disporre di un certo grado di programmabilità per
una entità, faremo riferimento a due situazioni tipiche.
In una prima situazione, supponiamo che si debba creare una entità che rappresenti il
comportamento di una porta nor. Facciamo riferimento alla coppia entità-architettura usate
nell'esempio precedente:
entity nor_port is
port (a,b:in bit; y:out bit);
end entity nor_port;

architecture behav of nor_port is


begin
y<=a nor b after 5 ns;
end architecture behav;

Nell'architettura della porta nor abbiamo specificato un ritardo di propagazione di 5 ns. Ora è ben
noto che il ritardo di propagazione non è una proprietà intrinseca di una porta, ma dipende dal
numero di ingressi che la sua uscita deve pilotare. Senza pretesa di eccessiva accuraztezza, nel caso
di una tecnologia CMOS possiamo sostenere che il ritardo di propagazione è proporzionale al
numero di ingressi pilotati. In effetti, nel caso del CMOS, il ritardo di propagazione di una porta
NOR può essere diverso anche in ragione del modo in cui sono pilotati gli ingressi, ma per non
complicare troppo la discussione, trascureremo questo aspetto. Se volessimo tener conto della
dipendenza del tempo di propagazione del numero di ingressi pilotati, attenendoci alla sintassi
riportata più sopra, non avremmo altra scenta che definire più architetture per la stessa porta nor in
modo da tener conto del numero di ingressi che si pilotano. Così, per esempio, potremmo scrivere:

architecture behav_1 of nor_port is


begin
y<=a nor b after 5 ns;
end architecture behav_1;

architecture behav_2 of nor_port is


begin
y<=a nor b after 10 ns;
end architecture behav_2;
.
.
.
architecture behav_13 of nor_port is
begin
y<=a nor b after 65 ns;
end architecture behav_13;
.
.
.

2
e volta per volta, quando utilizziamo una porta nor come parte di una descrizione strutturale,
potremmo sceglere l'architettura che corrisponde al ritardo corretto in base al numero di porte che si
prevede di pilotare.
Supponiamo ora che si preveda di dover usare porte nor a 2, a 3, a 4 o a 5 e più ingressi. In linea di
principio dovremmo definire una entità distinta per ogni numero possibile di ingressi e, per ciascuna
di esse, una architettura distinta per ogni possibile ritardo. Si osservi che il fatto di dover definire
entità distinte per ogni diverso numero di ingressi è, per la verità, un fatto che appare assolutamente
naturale: una nor a 2 ingressi è cosa diversa da una nor a 4 ingressi, e pertanto non si vede perché si
debba voler vedere questo fatto come una limitazione o un problema. Tuttavia il fatto è che,
soprattutto in una descrizione funzionale ad alto livello, è piuttosto scomodo dover fare riferimento
a entità distinte che svolgono la medesima funzione su un numero diverso di ingressi. Questo non
vale solo per le funzioni logiche: sarebbe estremamente comodo avere la possibilità di disporre di
una unica entità che svolge la funzione di “sommatore su n bit” o “contatore a n bit” con la
possibilità di specificare il valore n solo all'atto dell'utilizzo dell'entità all'interno di una specifica
architettura.
La possibilità di ottenere entità “programmabili” o, se si vuole “configurabili” in VHDL è
demandata alla possibilità di definire l'entità e la sua relativa architettura in termini di parametri così
detti generic.
La sintassi per la dichiarazione di entità nel caso in cui si vogliano usare parametri generic è la
seguente:

entity_declaration<=
entity identifier is
[generic (generic_list)];
[ port (port_interface_list);]
{entity_declarative_item}
end [entity][identifier];

generic_list <=(identifier{, …}:subtype_indication[:=expression]) {;.....}

Per chiarire l'uso dei parametri generic facciamo riferimento a una situazione in cui vogliamo poter
definire, all'atto dell'impiego di una porta nor all'interno di una architettura, sia il numero di
ingressi, sia il ritardo di propagazione. Si noti che per ottenere ciò, nell'esempio che segue, gli
ingressi delle porta nor sono gli elementi di un vettore di tipo bit_vector.

entity nor_gen is
generic (num_ingr:integer:=2; prop_delay:time:=5 ns);
port (ingressi:in bit_vector(1 to num_ingr); y:out bit);
end entity nor_gen;

architecture behav of nor_gen is

begin

process (ingressi)
variable uscita:bit;

begin
uscita:='1';
for i in 1 to num_ingr loop
if (ingressi(i)='1') then
uscita:='0';
exit;

3
end if;
end loop;
y<=uscita after prop_delay;
end process;
end architecture behav;

Nella procedura che implementa la funzione nor abbiamo sfruttato il fatto che l'uscita di una nor è
'0' quando almeno uno degli ingressi è a '1'. Si noti l'uso della parola chiave exit per concludere il
for—loop non appena si verifica che un ingresso è a 1.
Supponiamo ora di voler descrivere una entità a quattro ingressi e una uscita che esegue la funzione
logica:

y= x 1x 2 x 2 x 3x 4 


Per implementare questa funzione sono necessarie 2 porte nor a due ingressi e una porta nor a tre
ingressi. Supporremo, al solo fine di esemplificare l'uso dei parametri generic, che le porte a due
ingressi siano caratterizzate da un ritardo di propagazione di 5 ns mentre la porta a tre ingressi sia
caratterizzata da un tempo di propagazione di 10 ns.

Di seguito riportiamo la definizione di entità e l'architettura strutturale che sfrutta l'entità nor_gen
prima definita.

entity comb_nor_gen is
port (x_1,x_2,x_3,x_4:in bit; y:out bit);
end entity comb_nor_gen;

architecture struct of comb_nor_gen is

signal vect_input_3:bit_vector (1 to 3);


signal vect_input_2: bit_vector (1 to 2);
signal vect_internal_2:bit_vector (1 to 2);

begin

vect_input_3(1)<=x_1;
vect_input_3(2)<=x_2;
vect_input_3(3)<=x_3;
vect_input_2(1)<=x_1;
vect_input_2(1)<=x_2;

nor_3in:entity work.nor_gen
generic map (num_ingr=>3, prop_delay=>10 ns)
port map (ingressi=>vect_input_3, y=>vect_internal_2(1));
nor_2in_1:entity work.nor_gen
generic map (num_ingr=>2, prop_delay=>5 ns)
port map (ingressi=>vect_input_2, y=>vect_internal_2(2));
nor_2in_2:entity work.nor_gen
generic map (num_ingr=>2, prop_delay=>5 ns)
port map (ingressi=>vect_internal_2, y=>y);

end architecture struct;

Si noti come non sia stato necessario usare esplicitamente alcun process.

4
Component e configuration

Come accennavamo all'inizio di queste note, possiamo essere interessati a scrivere la struttura di
una entità in termini del collegamento di altre entità prima che queste siano state effettivamente
definite. In questo caso, all'interno della parte dichiarativa dell' architettura che vogliamo
descrivere, potremo dichiarare la presunta esistenza di tali entità, definendo le porte di connessione
e gli eventuali parametri generic che saranno poi effettivamente presenti allorché stabiliremo,
mediante una successiva fase di configurazione, l'effettiva corrispondenza fra le entità presunte e
quelle realmente presenti nel nostro progetto completo. La dichirazione dell'esistenza presunta di
entità si ottiene mediante la parola chiave component con una sintassi del tutto simile alla
dichiarazione di entità.

component_declaration<=
component identifier is
[generic (generi c_list)];
[ port (port_interface_list);]
end component [identifier];

Nell'esempio che segue supporremo di iniziare a lavorare in una directory vuota e di usare più file
per la descrizione dell'intero progetto e per la simulazione del somportamento dell'entità principale.
Per semplicità, faremo riferimento al progetto di generatore di clock a due fasi.
Immaginiamo di voler descrivere il flip-flop con una architettura strutturale in termini di due
componenti presunti: nor_port e inv_port.

Nel file two_phase.vhd possiamo scrivere quanto segue:

entity two_phase is
port (ck:in bit; fl, f2:out bit);
end entity two_phase;

architecture struct of two_phase is

component nor_port is
generic(prop_delay: time);
port (a,b:in bit; y:out bit);
end component nor_port;

component inv_port is
generic(prop_delay: time);
port (a:in bit; y:out bit);
end component inv_port;

signal fl int:bit;
signal f2 int:bit;
signal aa:bit;
begin

inv_l:component inv port


generic map (prop_delay=>5 ns)
port map (a=>ck, y=>aa);

nor l:component nor_port


generic map(prop delay=> 5 ns)

5
port map (a=>ck,b=>f1_int,y=>f2_int);

nor_2:component nor_port
generic map(prop_delay=> 5 ns)
port map (a=>aa, b=>f2_int,y=>f1_int);
f1<=f1_int;
f2<=f2_int;
end architecture struct;

Il comando ghdl -a two_phase.vhd non produce alcun errore.


Se proviamo a eseguire il comando ghdl -e two_phase, nel tentativo di comporre tutto quanto serve
a descrivere l'entità two_phase ai fini della successiva simulazione, riceviamo messaggi di warning
che ci segnalano che ai componenti nor_port e inv_port non corrisponde nessuna architettura.

Procediamo allora inserendo nel file comp.vhd le definizioni delle entità di una porta nor e di una
porta inverter che poi, in un successivo momento indicheremo come effettive implementazioni dei
componenti utilizzati.

Nel file comp.vhd potremo scrivere:

entity porta_nor is
generic(prop_delay: time);
port (a,b:in bit; y:out bit);
end entity porta_nor;

entity porta_inv is
generic(prop_delay: time);
port (a:in bit; y:out bit);
end entity porta_inv;

architecture behav of porta_inv is

begin

y<=not a after prop_delay;

end architecture behav;

architecture behav of porta_nor is

begin

y<=(a nor b) after prop_delay;

end architecture behav;

Si presti attenzione al fatto che è necessario usare gli stessi nomi “formali” per gli identificatori
delle porte e delle generic nella dichiarzione di una entità e del componente a cui essa si riferisce.

6
Una volta eseguito il comando ghdl -s comp.vhd, la descrizione e l'architettura delle entità è
contenuta nella libreria di default work.

A questo punto è necessario ricorrere alla cofigurazione dell'entità two_phase. Ciò può essere
ottenuto grazie al costrutto configuration. Per i dettagli circa questo costrutto si rimanda al manuale
VHDL. Supponiamo di create il file configurazion.vhd come segue:

configuration test_configurazione of two_phase is


for struct
for nor_1,nor_2:nor_port
use entity work.porta_nor(behav);
end for;
for inv_1:inv_port
use entity work.porta_inv(behav);
end for;
end for;
end configuration test_configurazione;

In ossequio alle regole sintatiche, test:configurazione è il nome della configurazione dell'entità


two_phase per la quale intendiamo fare riferimento all'architettura struct.

Quando si esegue il comando ghdl -a, le informazioni relative alla configurazione sono
memorizzate nella libreria di default work. Si presti attenzione che, se si vuole usare ora l'entità
two_phase all'interno di un test bench, la sua effettiva implementazione è indicata dall'identificatore
usato per la configurazione, ovvero da test_configurazione nel nostro caso.

Un possibile test_bench per la simulazione del generatore di clock a due fasi è allora il seguente:

entity test_bench is
end entity test_bench;
architecture behav of test_bench is

signal clock, fase1,fase2:bit;

begin

generatore: configuration work.test_configurazione


port map (ck=>clock, f1=>fase1, f2=>fase2);

simula:process
begin
clock<='0';
wait for 20 ns;
clock<='1';
wait for 20 ns;
clock<='0';

wait for 20 ns;


clock<='1';
wait for 20 ns;

7
clock<='0';

wait for 20 ns;


clock<='1';
wait for 20 ns;
clock<='0';

wait;
end process simula ;

end architecture behav;

E' possibile avere diverse configurazioni per la stessa entità e per la stessa architettura, facendo
corrispondere diverse entità effettive (o diverse architetture di queste) a ciascun componente.

Quando all'interno di una architettura sono impiegati componenti a cui corrispondono entità che
impiegano a loro volta componenti e così via, è evidentemente necessario specificare la
configurazione di tutti i componenti. I modi per ottenere questo risultato sono molteplici e il loro
esame dettagliato esula dagli obbiettivi di questo corso.

Vale la pena di fare tuttavia almeno un esempio. Supponiamo che l'architettura “struct” dell'entità
top_entity impieghi i componenti porta_a e porta_b. Supponiamo di voler far corrispondere al
primo componente l'architettura “behav” della entità med_entity_a che al suo interno non impiega
componenti. Supponiamo invece di voler far corrispondere al secondo componente l'architettura
“struct_med” dell'entità med_entity_b che a sua volta impiega i componenti low_a e low_b che
devono corrispondere alle architettura “behav” delle entità “low_entity_b” e “low_entity_b”.
Supponiamo che tutte le definizioni di componenti e architetture siano contenute nella libreria di
default work.

Sia “esempio” l'identificatore della configurazione. Per ottenere la configurazione della entità
top_entity scriveremo:

configuration esempio of top_entity is


for struct
for all : port_a
use entity work.med_entity_a(behav);
end for;
for all: port_b
use entity work.med_entity_b(med_struct);
for med_struct
for all:low_a
use entity work.low_entity_a(behav);
end for;
for all: low_b
use entity work.low_entity_b(behav);
end for;
end for;
end for;
end for;

8
end configuration esempio;

9
Generate

Lo statement generate è usato in VHDL per inserire all'interno di una architettura un insieme di
statement concorrenti in tutti quei casi in cui la struttura da implementare sia sufficientemente
regolare da consentire di descriverne il comportamento in forma algoritmica. Gli statement
concorrenti che possono essere inseriti grazie allo statement generate sono tipicamente process o
componenti.

La sintassi di uno statement generate è tipicamente il seguente:

generate_label: for identifier in discrete_range generate


[{block_declarative_item} begin]
{concurrent_statement}
end generate [generate_label];

E' inoltre possibile che la generazione (ovvero l'inserimento all'interno dell'architettura) di uno
statement concorrente sia condizionato al valore di una espressione di tipo boolean. In questo caso
la sintassi diventa:

generate_label: if boolean_expression generate


[{block_declarative_item} begin]
{concurrent_statement}
end generate [generate_label];

Facciamo subito un esempio per chiarire l'uso dello statement generate. Supponiamo di voler
descrivere la seguente entità:

entity adder_enne_bit is
generic (enne:integer:=8);
port (
c_in:in bit;
x_in:in bit_vector (enne-1 downto 0);
y_in:in bit_vector (enne-1 downto 0);
c_out:out bit;
s_out:out bit_vector (enne-1 downto 0)
);
end entity adder_enne_bit;

L'entità deve svolgere la funzione di addizionatore su enne bit. Supponiamo di volerne descrivere
l'architettura in termini dell'interconnessione fra la replica di enne component di nome cfa ciascuno
dei quali implementa la funzione di un full adder. Supponiamo che il componente cfa sia definito
nel modo seguente:

component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;

10
co:out bit
);
end component cfa;

In questa situazione si può descrivere la struttura del sistema algoritmicamente come segue.
Sia index l'intero che rappresenta l'ordine dei bit degli ingressi x_in e y_in. Con lo stesso intero
index possimao fare riferimento al full_adder (cfa) al quale sono collegati, come addendi, i bit di
ordine index degli ingressi x_in e y_in. In una descrizione “parole” potremmo descrivere la struttura
del sommatore come segue:

per index che assume tutti i valori fra 0 e (enne-1):


se index è 0 allora agli ingressi del relativo full_adder vanno collegati x_in(0),y_in(0) e c_in; alle
uscite va collegato s_out(0) per quanto riguarda la somma, mentre all'uscita di carry (co) dobbiamo
collegare un segnale che servirà a poter sfruttare questa uscita come ingresso di riporto per il
full_adder di ordine 1;
se index è (enne-1) allora agli ingressi del relativo full_adder vanno collegati x_in(enne-
1),y_in(enne-1) e il segnale al quale è collegato il riporto in uscita del full-adder precedente; alle
uscite va collegato s_out(enne-1) per quanto riguarda la somma, mentre all'uscita di carry (co)
dobbiamo collegare c_out;

se index è diverso da 0 e da (enne-1) allora agli ingressi del relativo full_adder vanno collegati
x_in(i),y_in(i) e il segnale al quale è collegato il riporto in uscita del full-adder precedente; alle
uscite va collegato s_out(enne-1) per quanto riguarda la somma, mentre all'uscita di carry (co)
dobbiamo collegare un segnale che servirà a poter sfruttare questa uscita come ingresso di riporto
per il full_adder di ordine 1;

Lo statement generate consente in effetti di tradurre VHDL la descrizione “a parole” sopra riportata.

Una architettura possibile per l'entità adder_enne_bit è quindi la seguente:

architecture gen1 of adder_enne_bit is

signal carry_chain: bit_vector (enne-2 downto 0);

component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit
);
end component cfa;

begin

gen_stat:for index in 0 to (enne-1) generate


begin

prima_cella:if (index=0) generate


begin
cella: component cfa
port map

11
(a=>x_in(0),b=>y_in(0),ci=>c_in,s=>s_out(0),co=>carry_chain(0));
end generate prima_cella;

ultima_cella:if (index=(enne-1)) generate


cella: component cfa
port map (a=>x_in(enne-1),b=>y_in(enne-
1),ci=>carry_chain(enne-2),s=>s_out(enne-1),co=>c_out );
end generate ultima_cella;

altre_celle:if ((index/=0) and (index/=(enne-1))) generate


cella: component cfa
port map
(a=>x_in(index),b=>y_in(index),ci=>carry_chain(index-
1),s=>s_out(index),co=>carry_chain(index));
end generate altre_celle;

end generate gen_stat;

end architecture gen1;

Nell'architettura gen1, il segnale carry_chain è usato per collegare il riporto in uscita a ciascun full
adder con il riporto in ingresso al full adder successivo.

Si noti che le label usate prima di ogni generate sono necessarie affinché sia possibile eseguire la
corretta configurazione delle celle. Si noti anche che abbiamo usato la label cella al momento di
inserire ciascun full adder. A questo proposito si tenga presente che la prima “cella” è identificata
internamente al vhdl in quando facente parte dall'entità adder_enne_bit, dell'architettura gen1, del
blocco generate gen_stat e del blocco generate prima_cella (oltre che dal fatto che viene “generata”
in corrispondenza dell'indice 0). Potremmo quindi, usando una sintassi arbitraria, identificarla
come

adder_enne_bit::gen1::gen_stat(0)::prima_cella::cella

L'ultima cella sarebbe identificata da (supponiamo enne=8):

adder_enne_bit::gen1::gen_stat(7)::ultima_cella::cella

Le altre 6 celle sarebbero identificate da:

adder_enne_bit::gen1::gen_stat(1)::altre_celle::cella
adder_enne_bit::gen1::gen_stat(2)::altre_celle::cella
.
.
adder_enne_bit::gen1::gen_stat(6)::altre_celle::cella

In sostanza, grazie all'uso delle label e al fatto che in uno statement generate eseguito con un ciclo
for si tiene conto automaticamente del valore dell'indice nel momento in cui viene generata una
cella, tutte i componenti “cella” sono univocamente determinati.

Alle label usate nei blocchi generate si fa infatti riferimento al momento della cofigurazione.

Supponiamo infatti che sia definita l' entità full_adder (alla quale si faranno corrispondere i
componenti cfa) nello stesso file che contiene la la definizione di entità ed architettura del

12
sommatore (file adder.vhd riportato in appendice).
Con riferimento al file adder.vhd, la configurazione del sistema è la seguente:

configuration sommatore of adder_enne_bit is


for gen1
for gen_stat
for prima_cella
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;

for ultima_cella
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;

for altre_celle
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;
end for;
end for;
end configuration sommatore;

Supponiamo ora di voler verificare il comportamento dell'entità appena definita (e configurata)


mediante un testbench. Supponiamo di voler fissare enne a 4.

Un possibile testbench è allora il seguente:

entity testbench is
end entity testbench;

architecture behav of testbench is

signal add1,add2,somma: bit_vector (3 downto 0);


signal riporto: bit;

begin

sommatore4: configuration work.sommatore


generic map (enne=>4)
port map (x_in=>add1,y_in=>add2,c_in=>'0', s_out=>somma,
c_out=>riporto);

process is
begin
add1<="0000";

13
add2<="0000";
wait for 100 ns;
add1<="0001";
add2<="0001";
wait for 100 ns;
add1<="0010";
add2<="0001";
wait for 100 ns;
add1<="0010";
add2<="0010";
wait for 100 ns;
add1<="0011";
add2<="0011";
wait for 100 ns;
add1<="0100";
add2<="0100";
wait for 100 ns;
add1<="0101";
add2<="0101";
wait for 100 ns;
add1<="1100";
add2<="1100";
wait for 100 ns;
add1<="1101";
add2<="1101";
wait for 100 ns;
add1<="1110";
add2<="1110";
wait;
end process;
end architecture behav;

All'interno di blocchi generate possono essere definiti dei segnali che possono essere utilizzati per
collegare fra loro i componenti che vengono generati. Supponiamo per esempio di voler riscrivere
l'architettura del sommatore a enne bit usando degli half adder per eseguire la somma dei signoli bit.

L'arhitettura gen2 di adder_enne_bit potrebbe essere la seguente:

architecture gen2 of adder_enne_bit is

signal carry_chain: bit_vector (enne-1 downto 0);

component ha is
port (
a:in bit;
b:in bit;
s:out bit;
c:out bit
);
end component ha;

14
component cor is
port (
a:in bit;
b:in bit;
c:out bit
);

end component cor;

begin

gen_stat:for index in 0 to (enne-1) generate


signal sig1, sig2,sig3:bit;
begin

prima_cella:if (index=0) generate


begin
cella1: component ha
port map
(a=>x_in(0),b=>y_in(0),s=>sig1,c=>sig2);
cella2: component ha
port map (a=>c_in,b=>sig1,s=>s_out(0), c=>sig3)
;
cella3: component cor
port map (a=>sig3,b=>sig2,c=>carry_chain(0));
end generate prima_cella;

altre_celle:if (index/=0) generate


cella1: component ha
port map
(a=>x_in(index),b=>y_in(index),s=>sig1,c=>sig2);
cella2: component ha
port map (a=>carry_chain(index-
1),b=>sig1,s=>s_out(index ), c=>sig3) ;
cella3: component cor
port map
(a=>sig3,b=>sig2,c=>carry_chain(index));
end generate altre_celle;
end generate gen_stat;

c_out<=carry_chain(enne-1);

end architecture gen2;

Si noti che, grazie al fatto di aver definito carry_chain di dimensione 8, è stato possibile
semplificare lo statement generate in quanto sono ora distinguibili due soli casi (vedi ultimo
statement concorrente nell'architettura). Sarebbe possibile, definendo carry_chain di dimensione 9 e

15
usando carry_chain(i) come ingresso di carry per la cella i e carry_chain(i+1) come uscita di carry
per la cella i, eliminare completamente la distinzione fra le diverse celle (basterebbe aggiungere lo
statement carry_chain(0)<=c_in all'interno dell'architettura.

Si noti in particolare che sono generate enne triplette distinte di segnali sig1,sig2 e sig3. Infatti
ciascuna tripletta di segnali è relativa a ciascuna singola iterazione di generazione e distinta dalle
triplette della altre generazioni. Usando una sintassi arbitraria, come giaà fatto precendentemente, è
come se si fossero definiti enne distinti segnali sig1, ovvero:

adder_enne_bit::gen1::gen_stat(0)::sig1
adder_enne_bit::gen1::gen_stat(1)::sig1
.
adder_enne_bit::gen1::gen_stat(enne-1)::sig1

Supponendo che siano state definite delle opportune entità per l'implementazione degli half_adder e
della porta or, il file di configurazione potrebbe essere:

configuration sommatore of adder_enne_bit is


for gen2
for gen_stat
for prima_cella
for all:ha
use entity work.half_adder (behav);
end for;
for all:cor
use entity work.orport(behav);
end for;

end for;

for altre_celle
for all:ha
use entity work.half_adder (behav);
end for;
for all:cor
use entity work.orport(behav);
end for;
end for;
end for;
end for;
end configuration sommatore;

16
File adder.vhd

entity full_adder is
port(
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit
);
end entity full_adder;

architecture behav of full_adder is


begin
s<=a xor b xor ci after 10 ns;
co<=(a and b) or (a and ci) or (b and ci) after 5 ns;
end architecture behav;

entity adder_enne_bit is
generic (enne:integer:=8);
port (
c_in:in bit;
x_in:in bit_vector (enne-1 downto 0);
y_in:in bit_vector (enne-1 downto 0);
c_out:out bit;
s_out:out bit_vector (enne-1 downto 0)
);
end entity adder_enne_bit;

architecture gen1 of adder_enne_bit is

signal carry_chain: bit_vector (enne-2 downto 0);

component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit
);
end component cfa;

begin

gen_stat:for index in 0 to (enne-1) generate


begin

prima_cella:if (index=0) generate

17
begin
cella: component cfa
port map
(a=>x_in(0),b=>y_in(0),ci=>c_in,s=>s_out(0),co=>carry_chain(0));
end generate prima_cella;

ultima_cella:if (index=(enne-1)) generate


cella: component cfa
port map (a=>x_in(enne-1),b=>y_in(enne-
1),ci=>carry_chain(enne-2),s=>s_out(enne-1),co=>c_out );
end generate ultima_cella;

altre_celle:if ((index/=0) and (index/=(enne-1)))


generate
cella: component cfa
port map
(a=>x_in(index),b=>y_in(index),ci=>carry_chain(index-
1),s=>s_out(index),co=>carry_chain(index));
end generate altre_celle;

end generate gen_stat;

end architecture gen1;

file di configurazione

configuration sommatore of adder_enne_bit is


for gen1
for gen_stat
for prima_cella
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;

for ultima_cella
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;

for altre_celle
for cella:cfa
use entity work.full_adder(behav);
end for;
end for;
end for;
end for;
end configuration sommatore;

18
file testbench

entity testbench is
end entity testbench;

architecture behav of testbench is

signal add1,add2,somma: bit_vector (3 downto 0);


signal riporto: bit;

begin

sommatore4: configuration work.sommatore


generic map (enne=>4)
port map (x_in=>add1,y_in=>add2,c_in=>'0', s_out=>somma,
c_out=>riporto);

process is
begin
add1<="0000";
add2<="0000";
wait for 100 ns;
add1<="0001";
add2<="0001";
wait for 100 ns;
add1<="0010";
add2<="0001";
wait for 100 ns;
add1<="0010";
add2<="0010";
wait for 100 ns;
add1<="0011";
add2<="0011";
wait for 100 ns;
add1<="0100";
add2<="0100";
wait for 100 ns;
add1<="0101";
add2<="0101";
wait for 100 ns;
add1<="1100";
add2<="1100";
wait for 100 ns;
add1<="1101";
add2<="1101";
wait for 100 ns;
add1<="1110";
add2<="1110";
wait;
end process;

19
end architecture behav;

20
CpE 487: Digital System Design
Fall 2009

Lecture 9
Generics and Configurations

Prof. Haibo He
Department of Electrical and Computer Engineering
Stevens Institute of Technology
Hoboken, NJ 07086

Review of the previous lecture

• Structural modeling
– Component declaration
– Component instantiation
– Design examples

2
Outline of this lecture

• Generics and configurations


– Generics
– Why configurations?
– Configuration specification and declaration

Generics

library IEEE;
use IEEE.std_logic_1164.all;

entity xor2 is
generic (gate_delay : Time:= 2 ns);
port(In1, In2 : in std_logic;
z : out std_logic);
end entity xor2;

architecture behavioral of xor2 is


begin
z <= (In1 xor In2) after gate_delay;
end architecture behavioral;

• Enables the construction of parameterized


models
4
Generics

Pass certain types of information (parameters) into a design


description from its environment.

For instance:
1. rise and fall delays
2. size of interface ports

Generics are very useful for making design units more


general-purpose

Generics in Hierarchical Models

architecture generic_delay of half_adder is


component xor2
generic (gate_delay: Time);
port (a, b : in std_logic;
c : out std_logic);
end component;
component and2
generic (gate_delay: Time);
port (a, b : in std_logic;
c : out std_logic);
end component;
begin
EX1: xor2 generic map (gate_delay => 6 ns) --note: there is no “;” after the generic map () construct
port map(a => a, b => b, c => sum);
A1: and2 generic map (gate_delay => 3 ns)
port map(a=> a, b=> b, c=> carry);
end architecture generic_delay;

• Parameter values are passed through the hierarchy


6
Precedence of Generic Declarations

architecture generic_delay2 of half_adder is


component xor2
generic (gate_delay: Time := 7 ns);
port(a,b : in std_logic;
c : out std_logic);
end component;

component and2
generic (gate_delay: Time:= 6 ns);
takes precedence
port (a, b : in std_logic;
c : out std_logic);
end component;

begin
EX1: xor2 generic map (gate_delay => gate_delay)
port map(a => a, b => b, c => sum);
A1: and2 generic map (gate_delay => 4 ns)
port map(a=> a, b=> b, c=> carry);
end generic_delay2;
• Generic map takes precedence over the component declaration 7

Another example

8
Simulation result

ZOOM IN

Generics: Properties

Design Entity
VHDL Program
signal

signal signal

value value

• Generics are constant objects and can only be read


• The values of generics must be known at compile time
• They are a part of the interface specification but do not have a physical
interpretation
• Use of generics is not limited to “delay like” parameters and are in fact
a very powerful structuring mechanism

10
N-input AND gate

11

3-input AND gate

12
Example: N-Input OR Gate

entity generic_or is
generic (n: positive:=2);
port (in1 : in std_logic_vector ((n-1) downto 0);
z : out std_logic);
end entity generic_or;

architecture behavioral of generic_or is


begin
process (in1) is
variable sum : std_logic:= ‘0’;
begin
sum := ‘0’; -- on an input signal transition sum must be reset
for i in 0 to (n-1) loop
sum := sum or in1(i);
end loop;
z <= sum;
end process;
end architecture behavioral;

• Map the generics to create different size OR gates


13

Example: N-bit Register

entity generic_reg is
generic (n: positive:=2);
port ( clk, reset, enable : in std_logic;
d : in std_logic_vector (n-1 downto 0);
q : out std_logic_vector (n-1 downto 0));
end entity generic_reg;
architecture behavioral of generic_reg is
begin
reg_process: process (clk, reset)
begin
if reset = ‘1’ then
q <= (others => ‘0’);
elsif (rising_edge(clk)) then -- rising_edge is defined in package std_logic_1164
if enable = ‘1’ then q <= d;
end if;
end if;
end process reg_process;
end architecture behavioral;

• This model is used in the same manner as the generic OR gate 14


Configuration

(1) It may be convenient to specify multiple views for a single


entity and use any one of these for simulation. This can
easily done by specifying one architecture body for each
view and using a configuration to build the desired
architecture body;
(2) It may be desirable to associate a component with any one
of a set of entities.

A configuration is therefore used to bind the following parts:


1. An architecture body to its entity declaration;
2. A component with an entity;

15

Configurations

entity configuration

binding

architecture-3
architecture-2
architecture-1

• A design entity can have multiple alternative architectures


• A configuration specifies the architecture that is to be used to
implement a design entity

16
Component Binding

architecture gate_level of comb is

architecture low_power of comb is

a combinational z
b logic
Binding Information
carry
Q D

Q Clk
architecture high_speed of comb is
R

architecture behavioral of comb is

• We are concerned with configuring the architecture and not the entity
• Enhances sharing of designs: simply change the configuration
17

Two ways for configuration

1. Configuration specification
2. Configuration declaration

First, let’s see the Default binding rules

18
Default Binding Rules

architecture structural of serial_adder is


component comb is
port (a, b, c_in : in std_logic;
z, carry : out std_logic);
end component comb;

component dff is
port (clk, reset, d :in std_logic;
q, qbar :out std_logic);
end component dff;
signal s1, s2 : std_logic;

begin
C1: comb port map (a => a, b => b, c_in => s1, z =>z, carry => s2);
D1: dff port map(clk => clk, reset =>reset, d=> s2, q=>s1, qbar =>open);
end architecture structural;

• Search for entity with the same component name


• If multiple such entities exist, bind the last compiled architecture for
that entity
19
• How do we get more control over binding?

Configuration specification

A configuration specification is used to bind component


instantiations to specific entities stored in design libraries.

The specification appears in the declarations part of the


architecture or block in which the components are
instantiated.

20
Configuration specification

Example 1:
s1 sum
In1
HA HA
In2 s2

c_out
c_in s3

a sum
a
out
b carry b

ports

21

Configuration Specification

library name
architecture structural of full_adder is entity name
-- architecture name
--declare components here
signal s1, s2, s3: std_logic;
--
-- configuration specification
for H1: half_adder use entity WORK.half_adder (behavioral);
for H2: half_adder use entity WORK.half_adder (structural);
for O1: or_2 use entity POWER.lpo2 (behavioral)
generic map(gate_delay => gate_delay)
port map (I1 => a, I2 => b, Z=>c);
begin -- component instantiation statements
H1: half_adder port map (a =>In1, b => In2, sum => s1, carry=> s2);
H2: half_adder port map (a => s1, b => c_in, sum => sum, carry => s2);
O1: or_2 port map(a => s2, b => s3, c => c_out);
end structural;

• We can specify any binding where ports and arguments match 22


Configuration Specification

• If all the half adder use the same entity-architecture, then we can use the
following short form:

for all: half_adder use entity WORK.half_adder (behavioral);

23

Configuration specification

Example 2:

A S1
X1 SUM
X2
B

S2
A1 S5
O1 COUT
O2
S3
A2

CIN S4
A3

24
Configuration specification

Example 2:
Library HS_LIB, COMS_LIB
Entity FULL_ADDER is
port (A, B, CIN : in BIT; SUM, COUT: out BIT);
End;

Architecture FA_STR of FULL_ADDER is


component XOR2
port (D1, D2 : in BIT; DZ: out BIT);
end component;
component AND2
port (Z: out BIT; A0, A1: in BIT);
end component;
component OR2
port (N1, N2: in BIT; Z : out BIT);
end component
25

Configuration specification – an example

-- following are four configuration specifications:


for X1, X2 : XOR2
use entity WORK.XOR2(XOR2BEH); -- binding the entity with
--more than one instantiation of a component
for A3: AND2
use entity HS_LIB.AND2HS(AND2STR)
port map (HS_B => A, HS_Z=>Z, HS_A=> A0); -- binding the
--entity with a single instantiation of a component;
for all : OR2
use entity CMOS_LIB.OR2COMS(OR2STR); -- binding the
--entity with all instantiations of OR2 component.
for others: AND2
use entity WORK.A_GATE(A_GATE_BODY)
port map(A0, A1, Z); -- binding the entity with all unbound
-- instantiation of AND2 component.

26
Configuration specification – an example

Signal S1, S2, S3, S4, S5 : BIT;

Begin
X1: XOR2 port map (A, B, S1);
X2 : XOR2 port map (S1, CIN, SUM);
A1 : AND2 port map (S2, A, B);
A2 : AND2 port map (S3, B, CIN);
A3 : AND 2 port map (S4, A, CIN);
O1: OR2 port map (S2, S3, S5);
O2: OR2 port map (S4, S5, COUT);
End FA_STR;

27

Configuration specification – an example

Different instances bound to different entities:

28
Configuration specification – an example

Different components bound to same entity

29

Configuration declaration

Configuration specification have to appear in an architecture body – to


change a binding it is necessary to change the architecture body and re-
analyze it.

A configuration declaration is a separate design unit – binding can be


performed after the architecture body has been written.

Syntax:

configuration configuration-name of entity-name is


block-configuration
end [configuration] [configuration-name];

30
Configuration Declaration

Example 1: configuration Config_A of full_adder is -- name the configuration


-- for the entity
for structural -- name of the architecture being configured
for H1: half_adder use entity WORK.half_adder (behavioral);
end for;
--
for H2: half_adder use entity WORK.half_adder (structural);
end for;
--
for O1: or_2 use entity POWER.lpo2 (behavioral)
generic map(gate_delay => gate_delay)
port map (I1 => a, I2 => b, Z=>c);
end for;
--
end for;
end Config_A;

• Written as a separate design unit


• Can be written to span a design hierarchy 31

Configuration declaration

Example 2:
library CMOS_LIB;
configuration FA_CON of FULL_ADDER is
for FA_STR
use WORK.all;
for A1, A2, A3 : AND2
use entity CMOS_LIB.BIGAND2(AND2STR);
end for;
for others: OR2 -- use defaults, i.e. use OR2 from library
--work
end for;

for all: XOR2


use configuration WORK.XOR2XON;
end for;
end for;
32
end FA_CON
Reference

The lectures notes and pictures are based on the following sources:

[1] J. Bhasker, A VHDL Primer,3rd edition, J. Bhasker, Prentice Hall, ISBN 0-13-096575-8, 1999
[2] S. Tewksbury, VHDL class notes
http://stewks.ece.stevens-tech.edu/CpE487-S05/
[2] J. V. Spiegel, VHDL tutorial.
http://www.seas.upenn.edu/~ese201/vhdl/vhdl_primer.html
[3] J. A. Starzyk, VHDL class lecture notes
http://www.ent.ohiou.edu/~starzyk/network/Class/ee515/index.html
[4] S. Yalamanchili, Introductory VHDL: From Simulation to Synthesis, Prentice Hall, ISBN 0-13-
080982-9, 2001.
[5] S. Yalamanchili, VHDL: A Starter's Guide,, Prentice Hall, ISBN: 0-13-145735-7, 2005.
[6] V. A. Pedroni, Circuit Design with VHDL,, MIT Press, ISBN: 0-262-16224-5, 2004.
[7] K. C. Chang, Digital Design and Modeling with VHDL and Synthesis, , IEEE Computer Society
Press, ISBN: 0-8186-7716-3, 1997
[8] J. M. Rabaey, A. Chandrakasan, B. Nikolic, Digital integrated circuits- a design perspective, 2nd
edition, prentice hall.

33
Sintesi Sequenziale Sincrona
Mariagiovanna Sami
Corso di reti Logiche 8
Anno 2007-08

2007-
2007-08

Introduzione

Le uscite di un circuito sequenziale in un dato istante di


tempo t dipendono:
Dalla condizione iniziale del circuito (se è possibile forzare
inizialmente il circuito stesso in un predefinito stato iniziale);
Dalla sequenza di ingressi, applicata in un arco temporale finito,
fino all’istante considerato
Questo aspetto implica che il dispositivo deve mantenere
memoria degli eventi passati
In un generico istante t l’informazione relativa al “contenuto”
di questa memoria è rappresentata dal concetto di stato
– Nota: le reti combinatorie possono essere considerate un caso
particolare di sistema sequenziale dove lo stato è unico

09/04/2008 -2- 2007-


2007-08
Progetto di reti sequenziali

L'ottimizzazione di circuiti sequenziali è in costante


evoluzione
Si devono distinguere circuiti sequenziali sincroni e
asincroni:
Circuito sequenziale sincrono: oltre agli ingressi “di
informazione” esiste un ingresso di controllo cui si applica il
segnale di clock: il circuito risponde agli eventi esterni (=
configurazioni di valori applicate agli ingressi di informazione)
solo quando il segnale di clock è attivo;
Circuito sequenziale asincrono: non esiste ingresso di clock
(quindi manca il concetto di “istante temporale”), gli eventi
esterni sono variazioni dei valori degli ingressi.
In questo corso si studieranno solo circuiti sincroni (oggi
di gran lunga i più diffusi).
09/04/2008 -3- 2007-
2007-08

Modello del circuito sequenziale

Il modello di un circuito sincrono può essere


Comportamentale (descrive l’evoluzione degli stati e
delle uscite del dispositivo)
– La transizione fra gli stati è descritta mediante tabelle o
diagrammi
– Le informazioni sugli stati sono esplicite
– Le informazioni sull'area e sui ritardi sono implicite

Strutturale
– Il modello del circuito è costituito da un insieme di componenti
(registri e logica combinatoria) collegati tra loro
– Le informazioni sugli stati sono implicite
– Le informazioni su area e ritardi sono esplicite

09/04/2008 -4- 2007-


2007-08
Modello del circuito sequenziale

Di norma:
Nella fase di analisi, viene fornito un modello strutturale
da cui si deve dedurre la tabella degli stati (e quindi il
comportamento) del circuito;
Nella fase di sintesi si parte da una descrizione (più o
meno formalizzata) del comportamento che si chiede al
circuito; da questa descrizione si deduce la tabella degli
stati e delle uscite che costituisce il punto di partenza
per la sintesi logica vera e propria.

09/04/2008 -5- 2007-


2007-08

Modello comportamentale

Il modello generale delle macchine sequenziali a cui si fa


riferimento è quello delle Macchine a Stati Finiti
Deterministiche (Finite State Machine - FSM). Con
questo modello le macchine sequenziali vengono
descritte tramite la teoria degli automi

Macchine a stati finiti deterministiche


– Condizioni per la fisica realizzabilità: il numero di stati è finito
e il comportamento della macchina in un istante t non dipende
da eventi futuri
– Dato uno stato ed una configurazione di ingresso lo stato
prossimo in cui la macchina si porta è identificato
univocamente ( determinismo)

09/04/2008 -6- 2007-


2007-08
Sintesi comportamentale di FSM (1)

Una macchina sequenziale è definita dalla quintupla (I,U,S, , )


I - Alfabeto di Ingresso
– È costituito dall'insieme finito dei simboli di ingresso
– Con n linee di ingresso si hanno 2n simboli

U - Alfabeto d'Uscita
– È costituito dall'insieme finito e non vuoto dei simboli d'uscita
– Con m linee d'uscita si hanno 2m simboli

S - Insieme degli Stati


– Insieme finito e non vuoto degli stati. Spesso si definisce anche uno
stato iniziale (o stato di reset) in cui la macchina deve portarsi
all’accensione o all’applicazione del segnale di reset

- Funzione stato prossimo


- Funzione d’uscita
09/04/2008 -7- 2007-
2007-08

Sintesi comportamentale di FSM (2)

Funzione stato prossimo


– Per ogni stato presente e per ogni simbolo di ingresso la funzione
definisce lo stato prossimo:
:S I S
– Ad ogni coppia {stato, simbolo di ingresso} è associato, se specificato,
uno ed uno solo stato prossimo.
– La funzione stato prossimo definisce l’evoluzione della macchina nel
tempo, in risposta agli eventi in ingresso
Funzione d'uscita
– Genera il simbolo d'uscita: esistono due alternative:
– Macchine di Mealy: l’uscita dipende sia dallo stato presente sia
dall’ingresso:
:S I U
– Macchine di Moore: l’uscita dipende solamente dallo stato presente:
:S U

09/04/2008 -8- 2007-


2007-08
Macchine di Mealy e Macchine di Moore

Macchine di Mealy
– la funzione di uscita costituisce la risposta della macchina quando,
trovandosi in un dato stato presente, riceve un simbolo di ingresso;
– nelle macchine di Mealy, l’uscita va “letta” mentre la macchina
subisce una transizione di stato

Macchine di Moore
– la funzione di uscita costituisce la risposta della macchina associata
allo stato in cui si trova
– nelle macchine di Moore, l’uscita viene letta mentre la macchina si
trova in un determinato stato

E’ possibile trasformare una macchina di Mealy in una macchina di


Moore equivalente, e viceversa

09/04/2008 -9- 2007-


2007-08

Architettura generale di una rete


sequenziale sincrona

Struttura generale di una macchina sequenziale (Huffman):


x1 z1
Ingressi (anche x2 Uscite (anche
z2
“ingressi primari”) “uscite primarie”)
xn RETE zn
COMBINATORIA
;

Stato Presente St y1 Y1 Stato Prossimo St+1


FF1
y1, y2…yk: variabili di stato
Y1, Y2…Yk: variabili di stato
presente (anche “ingressi
y2 Y2 prossimo (anche “uscite di
di stato”) FF2 stato)

yk Yk
Nota:
Nota: la
la Memoria
Memoria di di stato
stato FFk
coincide
coincide con
con ii Registri
Registri di
di
stato
stato solo
solo nel
nel caso
caso di
di Clock
bistabili
bistabili di
di tipo
tipo DD Memoria di stato

09/04/2008 - 10 - 2007-
2007-08
Architettura generale: macchina di Mealy

Struttura generale di una macchina di Mealy:


x1 z1
x2 RETE z2
xn
COMBINATORIA
zn

Ingressi RETE Uscite


COMBINATORIA

Stato Presente St y1 Y1 Stato Prossimo St+1


FF1

y2 Y2
FF2

yk Yk
FFk

Clock
Memoria di stato

09/04/2008 - 11 - 2007-
2007-08

Architettura generale: macchina di Moore

Struttura generale di una macchina di Moore:


x1
x2

xn RETE
COMBINATORIA
Ingressi

y1 Y1 Stato Prossimo St+1


FF1
Stato Presente St
COMBINATORIA

z1
z2 y2 Y2
FF2
RETE

zn

Uscite yk Yk
FFk

Clock
Memoria di stato

09/04/2008 - 12 - 2007-
2007-08
Il processo di sintesi: generalità

Riferendosi al modello generale di rete sincrona, la


sintesi di una rete sequenziale partendo dalla
descrizione comportamentale consiste in:
– Identificazione delle funzioni e
– Sintesi della rete combinatoria che le realizza
Elementi di memoria: Flip-Flop (i flip-flop di tipo D sono
quelli usati più comunemente)
La sintesi della funzione di stato prossimo dipende dal
tipo di bistabili utilizzati.
La sintesi della funzione di uscita non dipende dal tipo
di bistabili utilizzati.

09/04/2008 - 13 - 2007-
2007-08

Il processo di sintesi: generalità

La sintesi della funzione dipende dai bistabili utilizzati:


x1 z1
x2 z2
FUNZIONI
xn zn
;
Ingressi Uscite

Stato Presente St y1 Y1
FF1
INGRESSI FLIP-FLOP
STATO PROSSIMO/

y2 Y2 Stato Prossimo St+1


FF2

yk Yk
FFk

Clock
Registri di stato
09/04/2008 - 14 - 2007-
2007-08
Tabella degli stati
Il comportamento di una FSM può essere descritto mediante la Tabella
degli stati
Gli indici di colonna sono i simboli di ingresso i I
Gli indici di riga sono i simboli di stato sj S che indicano lo stato
presente
In ogni casella si indicano: i1 i2 . .
Per le macchine di Mealy: la coppia {u ,sj } S1t Sjt+1/uj Skt+1/uk . . .
u = (i , sj ) è il simbolo di uscita S2t Smt+1/um Slt+1/ul . . .
sj = (i , sj ) è il simbolo stato prossimo
. . . . . . . . . . .

Per le macchine di Moore: il simbolo stato prossimo sj


i1 i2 . .
sj = (i , sj ) è il simbolo stato prossimo
S1 t Sj t+1 Sk t+1 . . .
i simboli d'uscita sono associati allo stato presente u1
S2t Smt+1 Slt+1 . . . u2
. . . . . . . . . . . .

09/04/2008 - 15 - 2007-
2007-08

Diagramma degli stati

Nella fase di sintesi, spesso conviene far precedere alla stesura della
Tabella degli stati una rappresentazione grafica ad essa equivalente,
denominata Diagramma degli stati, ricavata dalla descrizione informale
(“a parole”) del comportamento della macchina.
Il diagramma degli stati è un grafo orientato G(V,E,L)
– V - Insieme dei nodi
• Ogni nodo rappresenta uno stato
• Per le macchine di Moore, ad ogni nodo è associato un simbolo d'uscita ()
– E - Insieme degli archi
• Ogni arco rappresenta le transizioni di stato, e gli è sempre associato il simbolo
di ingresso che le provoca
• Per le macchina di Mealy, ad ogni arco è associato anche un simbolo di uscita ()
– L - Insieme delle “etichette” degli archi:
• Ingressi e Uscite (macchina di Mealy)
• Ingressi (macchina di Moore)

09/04/2008 - 16 - 2007-
2007-08
Macchina di Mealy: Esempio

Equivalenza delle due rappresentazioni nel caso di una


macchina di Mealy

Diagramma degli stati Tabella degli stati

0/1 0 1
s0 s1
1/1 S0 S1/1 S2/1
0/1
1/1 0/0 S1 S3/0 S2/1
1/0 S2 S1/1 S3/0
s2 s3
1/0
S3 S3/0 S0/0
0/0

09/04/2008 - 17 - 2007-
2007-08

Macchina di Moore: Esempio

Equivalenza delle due rappresentazioni nel caso di una


macchina di Moore

Diagramma degli stati Tabella degli stati

0 0 1 U
S0/00 S1/01
1 S0 S1 S2 00
0
1 0 S1 S3 S2 01
1 S2 S1 S3 10
S2/10 S3/11
1 S3 S3 S0 11
0

09/04/2008 - 18 - 2007-
2007-08
Passi della Sintesi di una FSM

1. Realizzazione del diagramma degli stati a partire dalle


specifiche funzionali (informali) del comportamento del
sistema. È il passo che richiede maggior intuito.
Se la specifica della macchina prevede uno stato iniziale (o “di
reset”), si inizia da tale stato e ad esso si applicano tutte le
possibili configurazioni di ingresso; altrimenti, è necessario
identificare una sequenza di ingresso (possibilmente breve) che
consenta di identificare in modo univoco uno stato da cui iniziare
e che si tratta “come se” fosse lo stato iniziale.
Ogni configurazione di ingresso può portare a uno stato già
esistente oppure a un nuovo stato che viene aggiunto al
diagramma
Per ogni nuovo stato introdotto, si applicano tutte le configurazioni di
ingresso ….
Il procedimento termina quando non vengono più introdotti nuovi stati

09/04/2008 - 19 - 2007-
2007-08

Passi della Sintesi di una FSM:


Esempio
Controllore di parità (dispari)
Una macchina sequenziale sincrona di tipo Moore ha un ingresso x e
un’uscita z. L’uscita z assume il valore 1 se e solo se sull’ingresso si è
presentato un numero dispari di 1. In ogni altro caso è z uguale a 0.
All’accensione la macchina riconosce parità dispari non verificata (è quindi
definito esplicitamente lo stato di RESET).
Nel diagramma degli stati si inserisce innanzitutto lo stato iniziale “P” (che
corrisponde alla condizione “pari”, e cui è associata uscita 0). Fino a
quando l’ingresso vale 0, la condizione non cambia e si resta in P.
RESET

P/0
0

09/04/2008 - 20 - 2007-
2007-08
Passi della Sintesi di una FSM:
Esempio
Se in ingresso giunge il simbolo 1, si deve introdurre un nuovo stato: il
numero di 1 presenti nella sequenza è infatti ora dispari. Si indica con D il
nuovo stato, cui è associata uscita 1, e si introduce un arco da P a D con
etichetta 1.
Quando la macchina si trova in D, se arrivano uno o più 0 consecutivi la
sequenza mantiene un numero dispari di 1, quindi si resta in D; se arriva un
simbolo 1, il numero di 1 presenti nella sequenza diventa pari e ci si riporta
in P (arco da D a P marcato con il simbolo 1). Non si sono introdotti altri
stati, quindi il diagramma è terminato.
x
Reset 1 St 0 1

P D P P D 0
0 0
/0 /1
D D P 1
1 St+1 Z

09/04/2008 - 21 - 2007-
2007-08

Passi della Sintesi di una FSM

2. A partire da diagramma degli stati si costruisce la tabella


degli stati: questa non introduce alcuna informazione aggiuntiva.
Definisce la cardinalità iniziale dell’insieme degli stati e le funzioni
e in forma astratta. Si noti: in genere, da una specifica iniziale
si possono dedurre più diagrammi degli stati (e quindi tabelle degli
stati) con diverso numero di stati ma che pure rappresentano tutti
correttamente il comportamento della macchina specificata;
occorre quindi prevedere un passo relativo alla
3. Riduzione del numero degli stati: fase di ottimizzazione
sequenziale, che si riconduce alla identificazione di una
macchina equivalente (con lo stesso comportamento) a quella
rappresentata dalla tabella degli stati cui si è giunti nelle prime
fasi, ma minima – che ha cioè il minimo numero di stati fra tutte
le macchine che rispecchiano il comportamento richiesto. I criteri
per la riduzione del numero degli stati verranno affrontati in
seguito.

09/04/2008 - 22 - 2007-
2007-08
Passi della Sintesi di una FSM

4. Nella tabella degli stati, gli stati stessi sono identificati


con “nomi” (simbolici). Per potere passare alla
successiva sintesi digitale occorre assegnare ad ogni
stato una codifica binaria – così da identificare le
variabili di stato che compaiono nel modello generale
della FSM. Questa fase comporta alcuni passi successivi,
e precisamente:
1. Identificazione del numero minimo di bit necessari per la
codifica, e quindi delle variabili di stato (se è la cardinalità
dell’insieme degli stati, il numero minimo delle variabili di
stato sarà pari a log 2 ). yi indica il valore presente della
variabile di stato, Yi quello prossimo. Si determina così anche il
numero di flip-flop necessari a realizzare la macchina

09/04/2008 - 23 - 2007-
2007-08

Passi della Sintesi di una FSM

2. Assegnamento di una codifica (configurazione tra quelle


disponibili nel codice, una volta determinato il numero di bit)
ad ogni stato. Si noti che la scelta della codifica influenza in
modo significativo la realizzazione e complessità circuitale
della funzione stato prossimo (anche in funzione dei bistabili
utilizzati). Non esistono criteri praticamente utilizzabili che
garantiscano un assegnamento ottimo: si possono adottare
euristiche che portino a un “buon” assegnamento.
3. Costruzione della tabella delle transizioni, dedotta dalla
tabella degli stati e delle uscite sostituendo ai nomi simbolici
degli stati le codifiche ora assegnate.

09/04/2008 - 24 - 2007-
2007-08
Passi della Sintesi di una FSM: Esempio

Costruzione della tabella delle transizioni della FSM

Sia data la tabella degli Assegnamento degli stati Tabella delle transizioni
stati
•4 stati 2 variabili di stato y0y1I
y0y1 (quindi 2 bistabili) 0 1 U
0 1 U
00 01 11 00
S0 S1 S2 00 •Assegnamento banale 01 10 11 01
S1 S3 S2 01
S0 = 00 11 01 10 10
S2 S1 S3 10
10 10 00 11
S3 S3 S0 11 S1 = 01
Y0Y1 =
S2 = 11 z
stato prossimo
S3 = 10

09/04/2008 - 25 - 2007-
2007-08

Passi della Sintesi di una FSM

5. Dalla tabella delle transizioni, una volta scelto il tipo di


bistabili che si intende utilizzare (ovviamente sincrono) si
passa alla costruzione della tabella delle eccitazioni. Questa
si ottiene partendo dalla tabella delle transizioni della macchina e
da quella delle eccitazioni del bistabile scelto. Al termine di questo
passo, per ogni bistabile si hanno le funzioni di commutazione
relative ai suoi ingressi che consentono la transizione stato
presente - stato prossimo

6. Sintesi ottimizzata sia della rete combinatoria che realizza la


funzione stato prossimo sia della rete combinatoria che
realizza la funzione d'uscita

09/04/2008 - 26 - 2007-
2007-08
Passi della Sintesi di una FSM

Struttura generale Rete combinatoria


derivata dalla
tabella delle
Rete combinatoria Uscite
Ingressi eccitazioni
per realizzare
derivata dalla
tabella delle
transizioni Stato Prossimo St+1

eccitazioni Rete combinatoria Nota bene:


per realizzare se il bistabile scelto è il
per la FF D, la tabella delle
Stato Presente St trasformazione eccitazioni coincide con
stato-eccitazione la tabella delle
Registri di stato transizioni

09/04/2008 - 27 - 2007-
2007-08

Passi della Sintesi di una FSM

La tabella delle transizioni descrive la relazione tra i bit di stato


presente e quelli di stato futuro.
– La configurazione binaria dello stato presente corrisponde all’uscita dei
bistabili relativi
– La configurazione binaria dello stato prossimo precisa quello che si
desidera ottenere
A seconda del tipo di bistabile scelto, variano i segnali che devono
essere generati per realizzare la transizione stato presente – stato
prossimo desiderata. I segnali di ingresso di un bistabile prendono il
nome di eccitazioni
La tabella delle eccitazioni di un bistabile rappresenta il mezzo di
collegamento tra la tabella delle transizioni e la tabella delle
eccitazioni di una specifica macchina a stati.

09/04/2008 - 28 - 2007-
2007-08
Passi della Sintesi di una FSM

Es.: Si scelgano bistabili SR


Tabella delle eccitazioni Tabella delle eccitazioni
Tabella delle transizioni
del FF SR con FF SR
y0y1I Q Q* C S R y0y1 I
0 1 U 0 1 U
0 0 0 - -
00 01 11 00 00 0-,10 10,10 00
1 1 0 - -
01 10 11 01 0 0 1 0 - 01 10,01 10,-0 01
11 01 10 10 0 1 1 1 0 11 01,-0 -0,01 10
10 10 00 11 1 0 1 0 1 10 -0,0- 01,0- 11
1 1 1 - 0
Y0Y1 =
z S0R0 S1R1 z
stato prossimo

Dalla tabella delle eccitazioni posso sintetizzare le reti combinatorie (es., usando
le mappe di Karnaugh) che realizzano S0R0 S1R1 in funzione di y0,y1 e I

09/04/2008 - 29 - 2007-
2007-08

Diagramma degli stati - Esempio 1: specifiche

Specifiche
Una macchina sequenziale sincrona ha un ingresso x e un’uscita z.
L’uscita z assume il valore 1 se e solo se sull’ingresso si sono
presentati almeno due 0 seguiti esattamente da due 1 (z va a 1 in
corrispondenza del secondo 1 su x). In ogni altro caso è z uguale a 0.
Considerazioni:
– specifiche funzionali analitiche: non è necessario ulteriore raffinamento
delle specifiche
– dalle specifiche, la macchina da sintetizzare è una macchina di Mealy
– la macchina è un riconoscitore di sequenze nella forma

x = ..0011..
z = ..00010..

09/04/2008 - 30 - 2007-
2007-08
Diagramma degli stati - Esempio 1: stato iniziale
caso (a)
(a) Le specifiche non indicano la presenza di uno stato di reset; occorre quindi
identificare uno stato da usare come stato iniziale per la stesura del
diagramma degli stati. Si analizzano le specifiche:
– “ … z assume il valore 1 se e solo se sull’ingresso si sono presentati almeno due 0
seguiti esattamente da due 1…”
– Si deduce che una sequenza di tre o più 1 su x, indipendentemente dalla
successione di valori ricevuti precedentemente sull’ingresso, porta la macchina
in uno stato in cui
• Non si è “all’interno” di una sequenza da riconoscere (“sequenza utile”);
• Sicuramente l’uscita vale 0.
• Potrebbe col prossimo simbolo in ingresso iniziare una sequenza utile, ove il simbolo
fosse 0;
– Chiamiamo questa sequenza di tre o più 1 “non utile” a fini del riconoscimento;
la sequenza di esattamente tre 1 su x è la minima sequenza non utile (una
sequenza di soli due 1 non sarebbe univoca per l’uscita, in quanto in
corrispondenza del secondo 1 l’uscita potrebbe valere 1 - sequenza precedente
riconosciuta - oppure 0 - sequenza precedente non riconosciuta)
Si prende quindi come stato da cui iniziare la realizzazione del
diagramma quello cui si giunge con tre 1 consecutivi su x,
indipendentemente dai valori precedenti.
09/04/2008 - 31 - 2007-
2007-08

Diagramma degli stati - Esempio 1: stato iniziale


caso (a)

D: primo 1 E: secondo 1
dopo almeno sequenza
B: primo 0 C: secondo 0 due 0 riconosciuta
1
1
0/0 0/0 1/0 1/1
1/0

A B C D E
0/0
0/0 0/0 1/0
1/0
1/0 B
B
A

101: non una


sequenza utile - si
111: si torna nello
porta nello stato A 010: lo 0 può essere il
stato A
primo di una 0110: lo 0 può essere
sequenza utile - si il primo di una
porta nello stato B sequenza utile - si
porta nello stato B

09/04/2008 - 32 - 2007-
2007-08
Tabella degli stati - Esempio 1 - caso (a)

Tabella degli stati Riduzione della tabella degli stati


(banale!!)
0 1 0 1 0 1
A B,0 A,0 A B,0 A,0 A B,0 A,0
B C,0 A,0 B C,0 A,0 B C,0 A,0
C C,0 D,0 C C,0 D,0 C C,0 D,0
D B,0 E,1 D B,0 E,1 D B,0 A,1
E B,0 A,0 A = E B,0 A,0

09/04/2008 - 33 - 2007-
2007-08

Diagramma degli stati - Esempio 1: stato iniziale


caso (b)

(b) Scelta dello stato iniziale per la stesura del diagramma degli stati
– dalle specifiche: “ ….. z assume il valore 1 se e solo se sull’ingresso si
sono presentati almeno due 0 seguiti esattamente da due 1…..”
– una sequenza di due o più 0 su x, indipendentemente dalla successione
di valori di x ricevuti precedentemente, porta la macchina in uno stato
in cui “si è presentata la parte iniziale, indispensabile, della sequenza
da riconoscere”. Inoltre, sicuramente l’uscita vale 0. La sequenza di due
o più 0 è chiaramente identificabile e fa parte della sequenza “utile” a
fini del riconoscimento
– la sequenza di esattamente due 0 su x è la minima sequenza
identificabile come parte di una sequenza utile
– stato iniziale = stato in cui la macchina si porta con due 0 su x,
indipendentemente dai valori in ingresso precedenti

09/04/2008 - 34 - 2007-
2007-08
Diagramma degli stati - Esempio 1 - caso (b)

B: primo 1 D: secondo 1
dopo almeno sequenza
due 0 riconosciuta
0
0/0
1/0 1/1 0/0

A B D C

0/0 0/0 1/0

0/0 1/0
1/0
A: riconosciuti
almeno due 0 C E

E: sequenza
C: primo 0 0/0 non utile

09/04/2008 - 35 - 2007-
2007-08

Diagramma degli stati - Esempio 2:


specifiche

Specifiche
Si vuole realizzare un controllore di semaforo all’incrocio tra via
Mazzini e via Garibaldi mediante una macchina sequenziale
sincrona. La macchina riceve un segnale di sincronismo con periodo
di un minuto. Esiste un pulsante P per attraversamento pedonale.
Normalmente il semaforo alterna un minuto VERDE su via Mazzini e
ROSSO su via Garibaldi, poi un minuto VERDE su via Garibaldi e
ROSSO su via Mazzini, e così via
Se si preme il pulsante P, alla scadenza del minuto si porta il ROSSO
su entrambe le strade e lo si mantiene per due minuti
indipendentemente dal valore poi presente su P
al termine dei due minuti riparte il funzionamento normale con la
configurazione VERDE-ROSSO per la via in cui precedentemente ai
due minuti era ROSSO e successivamente, dopo una nuova
alternanza, si prende in considerazione P

09/04/2008 - 36 - 2007-
2007-08
Diagramma degli stati - Esempio 2:
specifiche
Considerazioni:
– le specifiche funzionali non sono immediatamente traducibili in una
FSM: è utile un ulteriore raffinamento
– dalle specifiche, la macchina da sintetizzare è una macchina di Moore:
infatti le uscite devono mantenere il loro valore stabile nell’intervallo
tra due impulsi di sincronismo
Riscrittura delle specifiche: la macchina ha
– Due uscite G e M; ogni uscita vale 0 se semaforo rosso, 1 se semaforo
verde;
– Un ingresso primario: P vale 1 se premuto, 0 altrimenti;
– Il passaggio del tempo è segnalato semplicemente dal clock.
“sintetizzare una macchina sequenziale sincrona di tipo Moore
con un ingresso P e due uscite G e M. Se P=0, le due uscite si
alternano a 1 ad ogni impulso di sincronismo. Se P=1, le due uscite
vanno a 0 per due impulsi di sincronismo. Successivamente,
ritornano ad alternarsi con un 1 su quella che precedentemente era
0. Solo dopo una nuova alternanza, P viene preso in considerazione.
09/04/2008 - 37 - 2007-
2007-08

Diagramma degli stati - Esempio 2:


stato iniziale

Scelta dello stato iniziale per la stesura del diagramma degli stati
Si sceglie uno stato in cui non è richiesto attraversamento pedonale:
ad esempio, stato a con uscite 01 e ingresso 0
1 0; 1 0; 1 0; 1

b/10 h/01 a/01


d/00 f/00

0 0

c/00 e/00 g/10 b/10


a/01

0 1 0; 1 0; 1 0; 1

09/04/2008 - 38 - 2007-
2007-08
Sintesi: Esempio 3

• Si sintetizzi la funzione stato prossimo della seguente FSM


nell’ipotesi di utilizzare bistabili di tipo SR
Tabella degli stati
00 01 11 10 Z
S0 S0 S0 S2 S1 1
Tabella delle transizioni
S1 S1 S1 S0 S1 0
00 01 11 10 Z
S2 S2 S3 S0 S2 1
00 00 00 11 01 1
S3 S3 S3 S2 S3 0
01 01 01 00 01 0
Codifica 11 11 10 00 11 1
S0 00 10 10 10 11 10 0
S1 01
S2 11
S3 10

09/04/2008 - 39 - 2007-
2007-08

Sintesi: Esempio 3

Tabella delle eccitazioni


di un bistabile di tipo SR
Q Q’ SR
0 0 0-
0 1 10
1 0 01
1 1 -0
Tabella delle
Tabella delle transizioni eccitazioni (con SR)
I1 I0
Q1 Q0 00 01 11 10
00 01 11 10
00 0- 0- 0- 0- 10 10 0- 10
00 00 00 11 01
01 0- -0 0- -1 0- 0- 0- -0
01 01 01 00 01
11 -0 -0 -0 01 01 01 -0 -0
11 11 10 00 11
10 -0 0- -0 0- -0 10 -0 0-
10 10 10 11 10

09/04/2008 - 40 - 2007-
2007-08
Sintesi: Esempio 3

• Le quattro mappe di Karnaugh che si ottengono sono quindi:


I1 I0 I1 I0
Q1 Q0 00 01 11 10 Q1 Q0 00 01 11 10
00 0 0 1 1 00 - - 0 0
01 - - 0 - 01 0 1 - 0
11 - 0 0 - 11 0 1 1 0
10 0 0 1 0 10 - - 0 -
Set0= Q1’Q0’I1+I1I0Q0’=Q1’Q0’I1+Set1 Res0=Q0I0
I1 I0 I1 I0
Q1 Q0 00 01 11 10 Q1 Q0 00 01 11 10
00 0 0 1 0 00 - - 0 -
01 0 0 0 0 01 - - - -
11 - - 0 - 11 0 0 1 0
10 - - - - 10 0 0 0 0
Set1= I1I0Q0’ Res1=I1I0Q0 = I1Res0
09/04/2008 - 41 - 2007-
2007-08

Riduzione del numero degli


stati
Il numero minimo di elementi di memoria (flip-flop) necessari a
memorizzare tutti gli stati dell’insieme S è:
NFF,min = log2 |S|

Nel modello di una macchina a stati possono esistere stati ridondanti

L’identificazione ed eliminazione di tali stati comporta:


– Numero minore di elementi di memoria
– Reti combinatorie meno costose
• per riduzione del numero di bit necessari per codificare gli stati
(minore numero di ingressi e di uscite alle reti combinatorie che
realizzano la funzione stato futuro e la funzione d’uscita.
• per aumento dei gradi di libertà nella sintesi combinatoria (condizioni
di indifferenza dovute all’utilizzo parziale delle configurazioni che
possono codificare lo stato)

09/04/2008 - 42 - 2007-
2007-08
Riduzione del numero degli stati

Esempio
Macchina con 8 stati, 1 ingresso ed 1 uscita Macchina con 3 stati, 1 ingresso ed 1 uscita

Funzione 1, 1
Funzione ,

3 stati implicano l’uso di 2 bistabili e 3


Eliminando codifiche tra le 4 possibili.
5 stati
0 1
a b/0 c/1
3 Elementi di
b a/0 c/0
memoria c b/0 a/0
- -/- -/-

09/04/2008 - 43 - 2007-
2007-08

Riduzione del numero degli stati


Scopo della riduzione del numero degli stati: individuare la macchina
minima equivalente a quella data
La macchina minima equivalente è funzionalmente equivalente alla
macchina data e dotata del minimo numero di stati
Si affronta dapprima il problema della riduzione degli stati per
macchine completamente specificate – tali cioè che per ogni coppia
(stato presente - simbolo d’ingresso) siano specificati stato prossimo e
simbolo di uscita (se la macchina è di Mealy) oppure (per la macchina
di Moore) per ogni stato presente sia specificato il simbolo di uscita e
per ogni coppia (stato presente - simbolo d’ingresso) sia specificato lo
stato prossimo
Inoltre, deve essere prevista l’eliminazione degli stati non raggiungibili
dallo stato di reset, se questo è specificato

09/04/2008 - 44 - 2007-
2007-08
Riduzione del numero degli stati:
macchine equivalenti
Date due macchine completamente specificate M1 e M2
queste si dicono equivalenti se e solo se per ogni stato
si di M1, esiste uno stato sj di M2 tale che ponendo
la macchina M1 in si e la macchina M2 in sj e
applicando alle due macchine una qualunque
sequenza di ingresso I, le due sequenze di uscita sono
identiche (e viceversa per M2 rispetto ad M1)
Si noti: nella definizione di equivalenza si sono
considerate solo le relazioni ingresso-uscita, quindi le
due macchine possono avere insiemi di stati diversi, in
particolare insiemi di diversa cardinalità

09/04/2008 - 45 - 2007-
2007-08

Riduzione del numero degli stati:


sequenze di uscite e di stati

Sia data una macchina completamente specificata, e sia


I una generica sequenza di ingresso ij,ij+1, ...,
ik ; applicandola a partire da un generico stato si si
ottengono:
1. Una sequenza di stati S = si+1= (si,ij), si+2=
(si+1,ij+1)…
Una sequenza d'uscita U che – nel caso di maccina di
Mealy – sarà data da ui = (si,ij), ui+1 =
(si+1,ij+1), … e – nel caso di macchina di Moorte –
da ui = (si), ui+1 = (si+1), … Più
concisamente, si indicherà U = (si,I )

09/04/2008 - 46 - 2007-
2007-08
Riduzione del numero degli stati:
stati indistinguibili di una stessa macchina
Data una macchina completamente specificata due stati
si e sj appartenenti ad S sono indistinguibili se:
U ,i = (si, I ) = (sj, I ) = U ,j I
In altre parole, ponendo la macchina in si oppure in sj e
applicando una qualsiasi sequenza di ingresso, le
sequenze di uscita prodotte sono identiche.
L’indistinguibilità tra si e sj si indica con il simbolo ~:
si ~ sj

09/04/2008 - 47 - 2007-
2007-08

Riduzione del numero degli stati:


stati equivalenti di una stessa macchina
La relazione di indistinguibilità gode di tre proprietà:
– Riflessiva: si~si
– Simmetrica: si~sj sj~si
– Transitiva: si~sj sj~sk si~sk

Quindi, la relazione di indistinguibilità è una relazione


d'equivalenza. Due stati indistinguibili sono equivalenti e
possono essere sostituiti con un solo stato.
In generale, un insieme di stati tra loro equivalenti può essere
raggruppato in unica classe di equivalenza.

09/04/2008 - 48 - 2007-
2007-08
Riduzione del numero degli stati:
stati equivalenti di una stessa macchina

Si definisce classe massima di equivalenza una


classe di equivalenza che non è contenuta in
nessun’altra classe più grande;
L’insieme delle classi massime di equivalenza
determina l’insieme degli stati della macchina
minima equivalente

09/04/2008 - 49 - 2007-
2007-08

Riduzione del numero degli stati:


partizione di equivalenza
Formalmente, una relazione di equivalenza induce sull'insieme S
degli stati una partizione e di equivalenza costituita da m classi
C1, ..., Cm tale che
– due stati appartengono alla stessa classe se e solo se sono equivalenti
– due stati appartengono a classi diverse se e solo se non sono
equivalenti
– C1 C2 ... Cm = S , e Ci Cj = i, j : i j
Il nuovo insieme degli stati è formato dalle classi della partizione
Esempio:

a a

b c b c

d d
09/04/2008 - 50 - 2007-
2007-08
Riduzione del numero degli stati:
macchina minima
Una macchina M è minima se non esiste nel suo insieme degli stati
nessuna coppia di stati equivalenti
Il problema della riduzione degli stati può quindi essere ricondotto a
quello della “costruzione” di una macchina equivalente minima a
quella data.
data una macchina M e la sua partizione di equivalenza indotta
dall’indistinguibilità tra stati, la macchina M’ il cui insieme degli
stati è costituito dai blocchi della partizione di equivalenza è la
macchina minima equivalente a quella data ed è unica (è
equivalente per costruzione, minima per costruzione, unica per le
caratteristiche di equivalenza)

09/04/2008 - 51 - 2007-
2007-08

Riduzione del numero degli stati:


identificazione degli stati equivalenti
La definizione di indistinguibilità tra stati è di difficile
applicabilità perché richiederebbe di considerare tutte le
sequenze di ingresso (a priori infinite)
Si ricorre ad una regola introdotta da Paull – Unger: due
stati si e sj appartenenti ad S sono indistinguibili se e
solo se per ogni simbolo di ingresso ia :
(si,ia )= (sj,ia) (Le uscite sono uguali per ogni simbolo di
ingresso
(si,ia)~ (sj,ia) (Gli stati prossimi sono indistinguibili)

La regola di Paull – Unger è iterativa

09/04/2008 - 52 - 2007-
2007-08
Riduzione del numero degli stati:
identificazione degli stati equivalenti (i)
Applicando la regola di Paull – Unger agli stati di una macchina, si
possono ottenere tre casi
1. si ~ sj
– Se i simboli d'uscita sono diversi e/o
– Se gli stati prossimi sono già stati verificati come distinguibili

2. si ~ sj
– Se i simboli di uscita sono uguali e
– Se gli stati prossimi sono già stati verificati come indistinguibili

3. si ~ sj se sk ~ sh (vincolo)
– Se i simboli di uscita sono uguali e
– Se gli stati prossimi non sono ancora stati verificati come
indistinguibili

09/04/2008 - 53 - 2007-
2007-08

Riduzione del numero degli stati:


identificazione degli stati equivalenti (ii)
Poiché gli insiemi S ed I hanno cardinalità finita, dopo un
certo numero di passi i vincoli vengono risolti e ci si troverà
in una delle due condizioni:
a) si ~ sj

b) si ~ sj

L’analisi del caso 3. può portare a costruire sequenze di


vincoli nei quali è presente circolarità del vincolo:
l’indistinguibilità di una coppia di stati è vincolata
dall’indistinguibilità della stessa coppia di stati o più in
generale – dalla stessa sequenza, in modo circolare

09/04/2008 - 54 - 2007-
2007-08
Riduzione del numero degli stati:
tabella delle implicazioni (i)
Le relazioni di indistinguibilità o equivalenze possono
essere identificate attraverso l'uso della Tabella delle
Implicazioni
La tabella ha le seguenti caratteristiche:
– Mette in relazione ogni coppia di stati
– E' triangolare (per sfruttare la proprietà simmetrica) e priva
della diagonale principale (per sfruttare la proprietà riflessiva)
Esempio
S1

S2

S3

S0 S1 S2

09/04/2008 - 55 - 2007-
2007-08

Riduzione del numero degli stati:


tabella delle implicazioni (ii)
Ogni elemento della tabella contiene:
– Il simbolo di non equivalenza;
– Il simbolo di equivalenza (se gli stati associati sono equivalenti)
– Le coppie di stati a cui si rimanda la verifica, se non è possibile
pronunciarsi sull’equivalenza degli stati associati all’elemento
Sulla tabella così ottenuta si procede ad una analisi di
tutte le coppie di stati.
Esempio:
S1 x

S2 x ~
S3 S1,S2 x x

S0 S1 S2
09/04/2008 - 56 - 2007-
2007-08
Riduzione del numero degli stati:
tabella delle implicazioni (iii)
Analisi delle coppie di stati
Per ogni coppia di stati:
Una coppia marcata come equivalente non richiede alcuna ulteriore
verifica
Se si trova un rimando ad un’altra coppia:
1. Se tali stati sono equivalenti anche gli stati della coppia in esame sono
equivalenti
2. Se tali non sono equivalenti anche gli stati della coppia in esame non
sono equivalenti
• Se gli stati della coppia cui si rimanda dipendono da una ulteriore
coppia di stati si ripete il procedimento in modo iterativo fino a quando
ci si riconduce ad uno dei due casi precedenti (si ricordi la circolarità
del vincolo)
L'algoritmo termina quando non sono più possibili eliminazioni
Le coppie rimaste sono equivalenti

09/04/2008 - 57 - 2007-
2007-08

Riduzione del numero degli stati: Esempio

Tabella degli Tabella delle


stati b x implicazioni
0 1 c x ae
a h/0 g/1
b c/0 e/0 d x x x
c b/0 a/0
d e/1 c/0 e dg x x x
e h/0 d/1
f x x x ch x
f e/1 h/0
g a/1 c/0 ae
g x x x ae x
h d/0 f/1 ch
dh x x x dh x x
h fg df
a b c d e f g
09/04/2008 - 58 - 2007-
2007-08
Riduzione del numero degli stati: Esempio

Coppia d;f d;f c;h


• ma c;h distinguibile: risultato d;f distinguibile
b x (X)

Coppia f;g f;g a;e e f;g c;h.


c x ae c;h è distinguibile: risultato f;g distinguibile (X)
~

Coppia a;h a;h d;h e a;h f;g.


d x x x • d;h è distinguibile: risultato a;h distinguibile (X)

dg x x x Coppia e;h e;h d;h e e;h d;f.


e
~ • d;h è distinguibile: Risultato e;h distinguibile
(X)
f x x x ch x
Coppia a;e a;e d;g ma d;g a;e.
ae • Quindi a;e d;g a;e: risultato a~e e d~g
g x x x ae x
~ ch Coppia b;c b;c a;e
dh x x x dh x x poiché a~e (passo 1) anche b~c;
h fg df

a b c d e f g

09/04/2008 - 59 - 2007-
2007-08

Costruzione della partizione di


equivalenza e della macchina minima
Le relazioni d'equivalenza sono rappresentabili su un grafo di
equivalenza:
– Vertice: rappresenta uno stato
– Lato: due vertici sono uniti da un lato se e solo se sono equivalenti
Le classi di equivalenza sono i sottografi completi del grafo (o clique):
a
h b
0 1
a h/0 g/1
0 1
g c b c/0 e/0
c b/0 a/0 h,0 ,1
f d d e/1 c/0 ,0 ,0
e e h/0 d/1 ,1 ,0
f e/1 h/0
f ,1 h,0
e={ {a, e}, {b, c}, {d, g}, h, f } = g a/1 c/0
= { , , , h, f } h ,0 f,1
h d/0 f/1

09/04/2008 - 60 - 2007-
2007-08
Riduzione del numero degli stati:
Esempio 1

Diagramma degli stati Tabella degli stati

1/01 0/10

a c 0/10 d
1/11 0 1
0/00 1/11 a g/00 c/01
0/01 1/01
1/01
b g/00 d/01
0/00
g b c d/10 a/11
0/00 d c/10 b/11
1/11
1/11 e g/00 f/01
f e f f/10 e/11
g a/01 f/11
1/01
0/10

09/04/2008 - 61 - 2007-
2007-08

Riduzione del numero degli stati:


Esempio 1

Tabella degli stati Tabella delle implicazioni

b cd

0 1 c x x
a g/00 c/01
b g/00 d/01 d x x ab
c d/10 a/11
e cf fd x x
d c/10 b/11
e g/00 f/01
x x ae be x
f df cf
f f/10 e/11
g a/01 f/11 g x x x x x x

a b c d e f

09/04/2008 - 62 - 2007-
2007-08
Riduzione del numero degli stati:
Esempio 1

Analisi della tabella delle


implicazioni
Coppia a;b a;b c;d ma c;d a;b
b cd quindi a;b c;d a;b: risultato a~b e c~d

x x Coppia a;e
c
a;e c;f ma c;f a;e e c;f d;f quindi
d x x ab a;e c;f d;f ma d;f b;e e d;f c;f
quindi
e cf fd x x a;e c;f d;f b;e ma b;e d;f quindi
a;e c;f d;f b;e quindi
x x ae be x
f df cf a~e, c~f, d~f e b~e

g x x x x x x A questo punto, l’analisi delle altre coppie è


già risolta
a b c d e f

09/04/2008 - 63 - 2007-
2007-08

Riduzione del numero degli stati:


Esempio 1

Grafo di equivalenza
Tabella delle a
implicazioni b Tabella ridotta
g degli stati
b cd
~ c
0
g/00
1
/01
c x x
f /10 /11
d x x ab g /01 /11
~ e d

e cf fd x x
~ ~
ae Partizione
f x x ~ be
df~
cf
x

g x x x x x x e= { {a, b, e}, {c, d, f}, g }


={ , ,g}
a b c d e f
09/04/2008 - 64 - 2007-
2007-08
Sintesi: Esempio 2

Sintetizzare una macchina di Moore secondo le


specifiche:
– La FSM ha due ingressi A e B;
– La FSM ha un’uscita Z, che assume valore iniziale 1
– Quando A=1, l’uscita assume il valore di B e tale valore permane
fino a quando si presenta la condizione A = B = Z = 1
– Al presentarsi della condizione su indicata, il ruolo assunto da A
e B viene scambiato
Si sceglie innanzitutto uno stato iniziale: si seleziona uno
stato s0 in cui l’uscita vale 1, l’ingresso controllante è A
e gli ingressi sono A=B=0.

09/04/2008 - 65 - 2007-
2007-08

Sintesi: Esempio 2

A=1;B=0
A=0;B=1 A=0;B=1
A=0;B=0 A=0;B=0
Tabella degli stati
A=1;B=1

S0/1 S1/0
A controlla A controlla 00 01 11 10 Z
A=1;B=0
S0 S0 S0 S2 S1 1
A=1 A=1 S1 S1 S1 S0 S1 0
B=1 B=1
S2 S2 S3 S0 S2 1
A=1;B=1 S3 S3 S3 S2 S3 0

S2/1 S3/0
B controlla B controlla
A=0;B=1

A=1;B=0 A=1;B=0
A=0;B=0 A=0;B=1
A=0;B=0

09/04/2008 - 66 - 2007-
2007-08
Sintesi: Esempio 2

Tabella degli stati Tabella delle implicazioni

00 01 11 10 Z
S0 S0 S0 S2 S1 1 S1 x
S1 S1 S1 S0 S1 0 S0,S3
S2 S1,S2 x
S2 S2 S3 S0 S2 1
S3 x S2,S0 x
S3 S3 S3 S2 S3 0
S0 S1 S2

09/04/2008 - 67 - 2007-
2007-08

Riduzione del numero degli stati:


Esempio 3

Tabella degli stati Tabella delle implicazioni

S2 x
00 01 11 10 S2,S4
S3 S6,S7 x
S1 S2/0 S8/1 S6/0 S3/0
S3,S5
S2 S7/0 S1/1 S5/1 S8/1 S6,S7
S3 S4/0 S8/1 S7/0 S5/0 S4 x S5,S1 x
S4 S6/0 S3/1 S1/1 S8/1 S3,S1
S5 S6,S7 x S4,S2 x
S5 S2/0 S8/1 S7/0 S1/0
S3,S1 S5,S1
S6 S1/1 S6/0 S3/1 S7/1 S6 x x x x x
S7 S3/1 S6/0 S5/1 S7/1 S7 x x x x x S3,S1
S8 S1/1 S2/1 S8/1 S7/1 S3,S5
S8 x x x x x x x
S1 S2 S3 S4 S5 S6 S7

09/04/2008 - 68 - 2007-
2007-08
Riduzione del numero degli stati:
Esempio 3
Grafo di equivalenza
Tabella delle implicazioni 1
8 2

S2 x 7 3
S2,S4
S3
~
S6,S7
S3,S5
x

S6,S7 6 4
5
S4 x
~
S5,S1
S3,S1
x

S5 S6,S7 x S4,S2 x
~
S3,S1 ~
S5,S1
S6 x x x x x
S7 x x x x x S3,S1 Partizione
~
S3,S5
S8 x x x x x x x e= { {S1, S3, S5}, {S2, S4}, {S6, S7}, S8 } =
S1 S2 S3 S4 S5 S6 S7 ={ a, b, c, S8 }

09/04/2008 - 69 - 2007-
2007-08

Riduzione del numero degli stati:


Esempio 3

Grafo di equivalenza

8
1 Tabella ridotta degli stati
2

00 01 11 10
7 3
a b/0 S8/1 c/0 a/0
b c/0 a/1 a/1 S8/1
6
c a/1 c/0 a/1 c/1
4
5 S8 a/1 b/1 S8/1 c/1

Partizione

e={ {S1, S3, S5}, {S2, S4}, {S6, S7}, S8 } =


={ a, b, c, S8 }

09/04/2008 - 70 - 2007-
2007-08
Riduzione del numero degli stati:
eliminazione degli stati irraggiungibili
Uno stato è non raggiungibile se non esiste alcuna sequenza di
transizioni di stato che porti dallo stato iniziale in tale stato. Gli stati
irraggiungibili possono essere eliminasti senza modificare il
comportamento della macchina.
0/10 Transizioni di RESET
Reset 1/11 c 0/10 d Reset

0/01 -/00 1/11 0/01 -/00


1/01

0/00
g b g

0/00 0/00
1/11 1/11
1/11 1/11
f e f e

1/11 1/11
0/10 0/10

09/04/2008 - 71 - 2007-
2007-08

Assegnamento degli stati


La riduzione del numero degli stati minimizza il numero di variabili di
stato che descrivono la macchina da sintetizzare
A pari numero di stati la complessità della rete combinatoria che
sintetizza la funzione dipende dal particolare assegnamento scelto
per gli stati
L’assegnamento degli stati permette di passare dalla tabella degli
stati in tabella delle transizioni, che rappresenta in forma iniziale
(manca la scelta del bistabile) l’insieme delle tabelle delle
eccitazioni della macchina (soggette ad effettiva sintesi)
Le adiacenze di 1 (o 0) nelle corrispondenti mappe di Karnaugh
consentono di ottenere reti combinatorie più o meno complesse, a
pari metodo di ottimizzazione

09/04/2008 - 72 - 2007-
2007-08
Scelta del codice
Il processo di codifica degli stati porta a identificare per ogni
rappresentazione simbolica dello stato una corrispondente
rappresentazione binaria. Si distinguono due problemi:
1. Scelta del codice: si può scegliere
– A minimo numero di bit (n°di elementi di memoria= log2
S “codifica densa”)
– One-Hot (n°di elementi di memoria= S “codifica sparsa”)
– Distanza Minima: gli stati che sono in corrispondenza delle
transizioni più frequenti sono posti a distanza Hamming più
piccola possibile mantenendo il vincolo del minimo numero di
bit.
– …
2. Identificazione della codifica di ogni stato.

09/04/2008 - 73 - 2007-
2007-08

Codifica degli stati


Scelto il codice, la codifica degli stati influisce sia sull’area sia sulle
prestazioni del dispositivo.
Il problema della identificazione della codifica ottima è un problema
NP-completo
Impone l’uso di euristiche per prevedere l’influenza sul processo di
ottimizzazione della codifica scelta. Ad esempio, il numero possibili
codifiche per il codice a minimo numero di bit è:
log 2 S
2 1 !
log 2 S
2 S ! log 2 S !
– Con |S| = 8 si hanno 840 possibili codifiche
Spesso, scelto il codice, si preferisce non ricorrere ad alcuna
specifica strategia di codifica - il costo della strategia di codifica
rispetto alla affidabilità del risultato ottenuto è ritenuto eccessivo.

09/04/2008 - 74 - 2007-
2007-08
Scelta del codice
Codifiche semplici : binario naturale e one-hot con codifica casuale
Binario Naturale:
– Il numero di bit è quello minimo
– Al primo stato corrisponde si associa la configurazione di bit che
codifica il numero 0, al secondo stato quella che codifica il numero 1...
– L’ordinamento degli stati è quello determinato in fase di realizzazione
della tabella degli stati.
One-Hot:
– Il numero di bit per la codifica dello stato è pari al numero degli stati
– In ogni codifica, un solo bit assume valore 1. Tutti i bit rimanenti
assumono valore 0
– Le codifiche degli stati sono tutte a distanza di Hamming 2

09/04/2008 - 75 - 2007-
2007-08

Scelta del codice

Esempio: sia data la tabella delle transizioni di una


macchina con tre stati, si confrontino la codifica binaria
naturale e quella one-hot:

Binario naturale One-Hot

S0 00 001

S1 01 010

S2 10 100

09/04/2008 - 76 - 2007-
2007-08
Codifica a numero minimo di bit:
flip-flop D
Consideriamo il caso di codifica a numero minimo di bit e utilizzo di
flip flop D

Si possono usare metodi euristici per determinare codifiche che


possano produrre macchine con reti combinatorie semplificate da
una buona scelta dell’associazione codifica-stato

Nel caso di bistabili D è possibile identificare dei criteri di scelta


semplici, poiché la tabella delle transizioni della macchina coincide
con la tabella delle eccitazioni

I criteri di scelta si basano sul principio di generare il più possibile 1


(o 0) adiacenti nella tabella delle transizioni (eccitazioni)

09/04/2008 - 77 - 2007-
2007-08

Codifica a numero minimo di bit:


flip-flop D
Metodo utilizzabile “a mano” su macchine con un numero di stati
ridotto: si basa su considerazioni che generano vincoli di codifica:

1. Se due stati si e sj hanno, per la stessa configurazione di ingresso,


lo stesso stato futuro è opportuno che si e sj abbiano codifiche
adiacenti, in modo da avere coppie di 1 o di 0 adiacenti sulle
colonne
Esempio – punto 1.
Tabella degli stati Tabella delle transizioni
00 01 11 10 Z Codifica 00 01 11 10 Z
S0 S0 S0 S2 S1 1 S0 00 00 00 00 11 01 1
S1 S1 S1 S0 S1 0 S1 01 01 01 01 00 01 0
S2 S2 S3 S0 S2 1 S2 11 11 11 10 00 11 1
S3 S3 S3 S2 S3 0 S3 10 10 10 10 11 10 0

09/04/2008 - 78 - 2007-
2007-08
Codifica a numero minimo di bit:
flip-flop D
1. Se due stati si e sj sono stati prossimi dello stesso stato per
configurazioni di ingresso adiacenti, è opportuno che abbiano
codifiche adiacenti, per avere coppie di 1 o di 0 adiacenti sulle righe

1. Nel caso di macchina di Mealy è possibile esprimere un criterio anche


relativo all’uscita (se si e sj hanno uscite identiche, per qualche
ingresso, è opportuno che i due stati abbiano codifiche adiacenti)

Esempio – punto 2.
Tabella degli stati Tabella delle transizioni
00 01 11 10 Z Codifica 00 01 11 10 Z
S0 S0 S0 S2 S1 1 S0 00 00 00 00 11 01 1
S1 S1 S1 S0 S1 0 S1 01 01 01 01 00 01 0
S2 S2 S3 S0 S2 1 S2 11 11 11 10 00 11 1
S3 S3 S3 S2 S3 0 S3 10 10 10 10 11 10 0

09/04/2008 - 79 - 2007-
2007-08

Codifica a numero minimo di bit:


flip-flop D
I vincoli imposti dai tre criteri di adiacenza possono generare
conflitti e comunque può risultare impossibile soddisfarli

A questi vincoli si può associare una priorità e un peso relativo:


– priorità: regola 1., priorità max, regola 2., priorità media, regola 3.,
priorità min
– peso: cardinalità del vincolo derivante dell’esame della tabella degli
stati, dopo aver applicato le regole esposte

Noi considereremo solo le regole 1. e 2. e il peso

09/04/2008 - 80 - 2007-
2007-08
Codifica degli stati: Esempio 1

Stati aventi lo
stesso stato prossimo Cardinalità dei vincoli
a,b condividono c Adiacenze Cardinalità
a,d condividono c a,b 1
c,e condividono e a,c 1
0 1
a,d 1
a c c
b c a a,e 0
c e d b,c 1
d b c b,d 0
Stati prossimi con
e e e ingressi adiacenti b,e 0
a,c stato presente b c,d 0
5 stati: e,d stato presente c c,e 1
3 variabili b,c stato presente d d,e 1
di stato

09/04/2008 - 81 - 2007-
2007-08

Codifica degli stati: Esempio 1

Dalla tabella dei vincoli, si costruisce il


Cardinalità dei vincoli grafo, i cui archi hanno un peso pari alla
Adiacenze Cardinalità cardinalità dei vincoli. Il peso viene usato
se non è possibile soddisfare tutti i vincoli
a,b 1
a,c 1
Grafo dei vincoli con archi pesati
a,d 1
a
a,e 0
d
b,c 1
b,d 0 b
b,e 0
c,d 0
e
c,e 1 c
d,e 1

09/04/2008 - 82 - 2007-
2007-08
Scelta dell’assegnamento

Il grafo dei vincoli, il linea di principio, rappresenta l’insieme dei


sotto-cubi di adiacenza che devono essere riportati nella mappa di
codifica.
Ciò è possibile solo se il grafo ottenuto è costituito da soli n-cubi o
da unioni di sottocubi

Se il grafo non è costituito da soli n-cubi o da unioni di n-cubi, è


necessario “tagliare” alcuni archi
La scelta viene fatta eliminando il minimo numero di archi possibile
e utilizzando il peso come criterio secondario
09/04/2008 - 83 - 2007-
2007-08

Codifica degli stati: Esempio 1

a Grafo iniziale: Mappa per la codifica


d non è un’unione di sotto-cubi (variabili di stato)

00 01 11 10
b
0 a c b
1 d e

e
c

a
Codifica
d a 000
b 100
b
c 010
Grafo ridotto tagliando il
numero minore di archi d 001
e 011
e
c
09/04/2008 - 84 - 2007-
2007-08
Codifica degli stati: Esempio 2
Stati aventi lo stesso stato prossimo

s0,s1 condividono s1
s0,s3 condividono s2
s2,s3 condividono s3
Tabella degli stati Cardinalità dei vincoli
s1,s2 condividono s0
00 01 11 10 Stati prossimi con ingressi adiacenti Adiacenze Cardinalità
S0 S0 S0 S2 S1
s0,s2 stato presente s0 s0,s1 4
S1 S1 S1 S0 S1
s1,s2 stato presente s0
S2 S2 S3 S0 S2 s0,s1 stato presente s0 s1,s2 2
S3 S3 S3 S2 S3 s0,s2 2
s0,s1 stato presente s1
s0,s1 stato presente s1 s2,s3 4
4 stati:
s2,s3 stato presente s2 s0,s3 2
2 variabili s0,s3 stato presente s2
di stato s0,s2 stato presente s2
s2,s3 stato presente s3
s2,s3 stato presente s3

09/04/2008 - 85 - 2007-
2007-08

Codifica degli stati: Esempio 2

Dalla tabella dei vincoli, si costruisce il


Cardinalità dei vincoli grafo, i cui archi hanno un peso pari alla
cardinalità dei vincoli. Il peso viene usato
Adiacenze Cardinalità
se non è possibile soddisfare tutti i vincoli
s0,s1 4
s1,s2 2 Grafo dei vincoli con archi pesati

s0,s2 2 4
s0 s1
s2,s3 4
s3,s0 2 2 2

s2 s3
4

09/04/2008 - 86 - 2007-
2007-08
Codifica degli stati: Esempio 2

Grafo iniziale: Mappa per la codifica


non è un’unione di sotto-cubi (variabili di stato)
4
s0 s1
0 1
2 2 0 s0 s1
1 s3 s2
2

s2 s3
4
4 Codifica
s0 s1
s0 00
2 2 s1 01
Grafo ridotto tagliando il s2 11
numero minore di archi 2
s3 10
s2 s3
4
09/04/2008 - 87 - 2007-
2007-08

Altri criteri di assegnamento

Esistono metodi che hanno lo scopo di individuare per la


funzione una dipendenza ridotta dalle variabili di stato
Il metodo assegna, se possibile, gli stati in modo da
identificare dei moduli che dipendono da un numero di
variabili di stato inferiore a quello totale della FSM
Il metodo partiziona le variabili di stato (i bistabili) e
quindi individua dei moduli costituiti da
– un sottoinsieme di bistabili e una rete combinatoria che realizza
solo per quel sottoinsieme di variabili di stato
– le reti combinatorie risultanti sono in generale localmente meno
complesse perché dipendono, oltre che dagli ingressi, da un
numero ridotto di variabili di stato

09/04/2008 - 88 - 2007-
2007-08
1. Introduzione.
Il VHDL è un linguaggio per la descrizione dell’hardware (un Hardware Description Language),
che può essere utilizzato per la documentazione, la simulazione e la sintesi di sistemi digitali.
Inizialmente, nei primi anni ’80, lo sviluppo del VHDL è stato supportato dal dipartimento della
difesa statunitense, nell’ambito di un progetto denominato VHSIC (Very High Speed Integrated
Circuits).VHDL è infatti un acronimo che sta per VHSIC Hardware Description Language. Nel 1987
il VHDL è stato adottato come standard dalla IEEE (Institution of Electrical and Electronics
Engineers); questa prima release ufficiale del linguaggio è nota come VHDL-87. Nel 1993 lo
standard è stato revisionato dalla IEEE e si è giunti così alla versione attuale del linguaggio, nota
come VHDL-93, che differisce solo in pochi aspetti dal VHDL-87. Gli esempi seguenti, salvo
diversa indicazione, possono essere analizzati con un sistema di sviluppo che supporta uno qualsiasi
dei due standard.
Il VHDL ha la fama (in parte meritata) di essere un linguaggio alquanto complicato. Il VHDL è
stato infatti introdotto come linguaggio standard per la documentazione di sistemi digitali
complessi. Il linguaggio è nato quindi con lo scopo di fornire una descrizione non ambigua di un
sistema digitale, che potesse essere interpretata univocamente dai vari progettisti impegnati nello
sviluppo del sistema stesso. Una delle caratteristiche richieste al VHDL è la possibilità di simulare
il sistema descritto, sia a livello funzionale sia portando in conto i ritardi del circuito.
Negli anni seguenti, oltre che per la documentazione e la simulazione, il VHDL ha assunto un
ruolo sempre più importante nella fase di sintesi dei sistemi digitali. Un programma di sintesi
consente, a partire da una descrizione comportamentale di un sistema, di ottenere automaticamente
un descrizione del circuito a basso livello mediante una netlist in cui il sistema viene descritto come
interconnessione di porte logiche elementari appartenenti ad un’opportuna libreria. A partire dalla
netlist, utilizzando opportuni programmi di piazzamento e collegamento delle celle (place & route), è
possibile completare in maniera quasi del tutto automatica il progetto di un sistema integrato. In
questo modo il ciclo di sviluppo di un sistema integrato diviene simile a quello di un programma
software in cui si parte da una descrizione in un linguaggio di programmazione (C, PASCAL ecc.)
per ottenere, dopo una fase di compilazione, una descrizione in linguaggio macchina.
L’utilizzo di sofisticati programmi di sintesi è stato uno degli strumenti più importanti che ha
consentito lo sviluppo di circuiti integrati sempre più complessi, consentendo al progettista di
concentrarsi sulla descrizione ad alto livello del sistema, esplorando come le diverse scelte
architetturali possano influire sulle prestazioni del circuito, disinteressandosi dai dettagli
implementativi. Il VHDL consente infatti di descrivere efficacemente sistemi complessi cui
corrispondono netlist di centinaia di migliaia o milioni di porte logiche elementari, così come in un
programma software ad alto livello è possibile ottenere facilmente programmi in linguaggio macchina
costituiti da milioni di istruzioni elementari a partire da un listato di poche centinaia di righe. Un
ulteriore vantaggio legato all’utilizzo di programmi di sintesi è legato al fatto che la descrizione di un
sistema digitale in VHDL può essere (quasi del tutto) indipendente dalla particolare tecnologia
prescelta per l’implementazione del circuito, così come un programma descritto in un linguaggio ad
alto livello può essere compilato ed eseguito su piattaforme hardware differenti.
Poichè il VHDL è nato come linguaggio per la documentazione dei sistemi digitali e solo in un
secondo momento sono stati introdotti i programmi di sintesi, non deve stupire il fatto che non tutti i
costrutti del VHDL siano sintetizzabili (ad esempio, le operazioni relative all’accesso su files non
possono avere una diretta corrispondenza hardware).
In queste note vengono descritti alcuni dei costrutti principali del VHDL, enfatizzando quelli
legati alla sintesi dei sistemi integrati. Invece di focalizzarsi sulla sintassi e sulla struttura del
linguaggio, i vari aspetti del VHDL verranno introdotti in maniera semplice ed intuitiva a partire da
alcuni esempi.
Poichè questi appunti sono ben lungi dall’essere esaustivi, si rimanda il lettore interessato ai testi
citati in bibliografia per ulteriori approfondimenti.
2 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

2. Entità ed Architetture

La descrizione VHDL di un sistema deve includere:

• una definizione di entità, in cui si definiscono i terminali di ingresso/uscita del sistema


• una descrizione dell’architettura, in cui si descrive la funzionalità del sistema.

Per introdurre questi elementi base del VHDL si consideri il listato di Figura 1, che descrive un
comparatore a 4-bit. Si noti che i numeri che compaiono nel listato non fanno parte del codice, ma
sono stati inseriti per identificare le varie linee che costituiscono il listato.

1 -- Descrizione VHDL di un comparatore a 4 bit


2 entity eqcomp is
3 port (a, b: in bit_vector (3 downto 0);
4 eq, neq : out bit);
5 end eqcomp;
6
7 architecture dataflow of eqcomp is
8 signal aux1 : bit;
9 begin
10 neq <= not aux1; -- neq attivo basso
11 aux1 <= ‘1’ when (a = b) else ‘0’;
12 eq <= aux1;
13 end dataflow;

Figura 2.1: Descrizione di un comparatore a 4 bit.

I nomi in grassetto in Fig. 2.1 sono parole chiave del VHDL, mentre gli altri nomi sono
identificatori definiti dall’utente. Il VHDL non è case-sensitive, per cui, ad esempio, la linea 7 del
listato di Fig. 2.1 poteva essere scritta in maniera del tutto equivalente nei due modi seguenti:

architecture DATAFLOW OF eqcomp IS


ARCHITecture datafLOW of EQCOMP is
Ogni identificatore inizia con una lettera e può contenere lettere, numeri ed underscore (il simbolo
“_”). Non è possibile utilizzare due underscore consecutivi in un identificatore.

In VHDL non ci sono particolari convenzioni di formattazione per il file di ingresso. Spazi,
caratteri di tabulazione e ritorni a capo sono trattati allo stesso modo. Ad esempio, le linee dalla 10
alla 12 di Fig. 2.1 possono essere riscritte nel modo seguente (del tutto equivalente, anche se
senz’altro meno leggibile):

neq <= not aux1; aux1 <= ‘1’ when (a


= b) else
‘0’; eq <= aux1;

I due trattini (--) in linea 1 introducono un commento. I commenti vengono ignorati dal
compilatore ed hanno lo scopo di migliorare la leggibilità del listato. Un commento inizia con due
trattini consecutivi e termina con la fine della linea. Un commento può iniziare in un punto qualsiasi
di una linea, come mostra la linea 10 del listato di Fig. 2.1.
Le linee da 2 a 5 descrivono l’interfaccia di ingresso/uscita (I/O) del nostro comparatore, e
costituiscono la entity declaration. Il nome della entity, eqcomp in questo esempio, è definito in
linea 2. Le linee 3 e 4 riportano l’elenco ed il tipo dei terminali di I/O (port). Nel nostro caso
3 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

abbiamo due terminali di ingresso, denominati a e b, che sono vettori di bit. Ogni elemento del
vettore, ad esempio a(2), è un bit e può assumere i due valori ‘0’ ed ‘1’ (si noti l’utilizzo degli apici,
vedi, ad esempio, la linea 11 del listato). La entity del listato di figura 2.1 possiede due uscite di tipo
bit, denominate eq ed neq.
Una dichiarazione di entità è analoga ad un simbolo in uno schema a blocchi, in cui si
identificano il nome dell’elemento ed i punti di collegamento con altri elementi dello schema a
blocchi. Ad esempio, la Fig. 2.2 mostra il simbolo schematico corrispondente all’entità del listato 1

a[3 :0] eq
b[3:0 ] ne q

Fig. 2.2 Simbolo corrispondente all’entità eqcomp del listato 1.

Le linee da 7 a 13 nel listato di Fig. 2.1 rappresentano la descrizione dell’architettura, in cui si


descrive il funzionamento della entità. La descrizione dell’architettura inizia in linea 7, in cui si
definiscono il nome dell’architettura (dataflow in questo esempio) e l’entità a cui l’architettura si
riferisce.
La linea 8 del listato di Fig. 2.1 rappresenta la parte dichiarativa dell’architettura, utilizzata, in
questo caso, per definire un segnale interno denominato aux1.
Il funzionamento dell’entità eqcomp viene descritto, dopo la parola chiave begin in linea 8,
mediante tre operazioni di assegnazione (il simbolo di assegnazione è <= ). In linea 11 l’operatore di
comparazione (il simbolo =) e il costrutto when...else vengono utilizzati per assegnare il valore
‘1’ al segnale aux1 quando a è uguale a b, ed il valore ‘0’ altrimenti. Si noti che l’operatore di
comparazione è vettoriale: si comparano fra di loro a(3) e b(3), a(2) e b(2) e così via. In linea 12 il
valore di aux1 viene assegnato all’uscita eq, mentre in linea 10 il negato di aux1 (e quindi il negato di
eq) viene assegnato all’uscita neq.
Per quanto particolarmente semplice, il listato di Fig. 2.1 evidenzia alcune particolarità del VHDL
rispetto ad un comune linguaggio di programmazione, come il C o il PASCAL. In un comune
linguaggio di programmazione i vari statements vengono eseguiti l’uno dopo l’altro, in una sequenza
determinata dall’ordine con il quale gli statements stessi si succedono nel file sorgente. Nel listato
VHDL di figura 2.1, invece, le tre assegnazioni delle linee 10, 11 e 12 sono statements concorrenti.
Per meglio chiarire cosa si intende per statements concorrenti, supponiamo di dover effettuare una
simulazione del listato di Fig. 2.1. Lo statement della linea 10 verrà eseguito ogni volta che si ha una
transizione per il segnale aux1, mentre lo statement della linea 11 verrà eseguito ogni volta che si ha
una transizione per gli ingressi a o b. Il risultato della simulazione non varia se si scambiano di posto
le tre linee 10, 11 e 12 e quindi l’ordine con cui si susseguono gli statements concorrenti nel listato
VHDL è del tutto irrilevante. Se si effettua una sintesi del listato di Fig. 2.1, alla linea 10
corrisponderà un inveritore, mentre alla linea 11 corrisponderà un circuito costituito da altre porte
logiche che effettuano la comparazione fra a e b. Anche in questo caso il circuito sintetizzato non
dipenderà in alcun modo dall’ordine con cui si susseguono gli statements concorrenti nel listato
VHDL.
Come abbiamo visto da questo primo esempio, e come mostra la Fig. 2.3, la struttura di un
programma VHDL si compone di due elementi fondamentali: la dichiarazione di entità in cui viene
descritta l’interfaccia ingresso/uscita del sistema e la definizione dell’architettura, in cui si descrive il
comportamento del sistema. E’ bene osservare che non è indispensabile, ma è senz’altro
consigliabile, inserire in uno stesso file la dichiarazione di entità e la definizione dell’architettura.
Per descrivere un sistema in VHDL molto spesso si utilizza un approccio gerarchico. In questo
caso all’interno della definizione dell’architettura del sistema si fa riferimento ad altre entità di livello
gerarchico inferiore, ognuna delle quali avrà una propria architettura, come evidenzia la Fig. 2.4.
4 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

fi le d i testo

d ic h iara zion e
d i entità

d efinizio ne
d ell’arch itettu ra

Fig. 2.3. Struttura di un programma VHDL.

e n tità A

a rc h itettur a A

e n tità B e n tità C e n tità D

a rc h itettur a B a rc h itettur a C a rc h itettur a D

e n tità E

a rc h itettur a E

Fig. 2.4. Descrizione VHDL gerarchica

Il VHDL consente di definire più architetture per ogni entità. In questo caso è necessario definire
un’opportuna configurazione in cui si specifica la particolare architettura da utilizzare per ogni
entità. Variando la configurazione è possibile valutare come l’utilizzo di un differente approccio
architetturale modifichi le caratteristiche del sistema.
In queste note non ci occuperemo del problema della configurazione di un progetto VHDL, e
faremo pertanto uso di una sola architettura per ogni entità.

3. Dichiarazione di entità.

La dichiarazione di entità, come abbiamo già avuto modo di accennare, ha lo scopo di descrivere
l’interfaccia di I/O del sistema. Nella dichiarazione di entità è inoltre possibile definire dei parametri,
che consentono di rendere più flessibile la descrizione; torneremo su questo punto in seguito.
La sintassi di una dichiarazione di entità è mostrata in Fig. 3.11

entity nome_entita is
port (nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale);
end nome_entita;
Fig. 3.1 Dichiarazione di entità

1
La ripetizione del nome dell’entità dopo end è opzionale. VHDL-93 consente inoltre, facoltativamente, di inserire la
keyword entity fra end ed il nome dell’entità.
5 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

IN OUT

IN B U F FE R

IN IN O U T
IN
IN OUT

Fig. 3.2 Modi dei terminali di ingresso uscita

In Fig. 3.1 entity, is, port ed end sono parole riservate del VHDL, mentre nome_entita,
nome_segnale e tipo_segnale sono identificatori definiti dall’utente.

3.1 Modi. In Fig. 3.1 il modo descrive la direzione con la quale i dati possono essere trasferiti
attraverso un terminale di ingresso o di uscita. I modi previsti dal VHDL sono 4:

IN : Il terminale è un ingresso per l’entità. Il circuito che pilota il terminale


(il driver) è esterno all’entità.
OUT : Il terminale è una uscita per l’entità. Il driver è interno all’entità. Il valore di
un terminale di questo tipo non può essere “letto” all’interno dell’entità.
BUFFER : Il terminale è di uscita, (il driver è interno all’entità) ma il suo valore può
anche essere “letto” all’interno dell’entità.
INOUT : Il segnale può essere utilizzato sia come ingresso che come uscita (il driver
può essere sia interno che esterno all’entità)

Per meglio chiarire i possibili modi previsti dal VHDL, la Fig. 3.2 mostra un semplice circuito
digitale in cui sono presenti terminali di ingresso e di uscita di modo IN, OUT, BUFFER ed INOUT.
Il modo inout è il più generale e può rimpiazzare tutti gli altri modi. Benchè sia possibile definire
tutti i terminali di una entità di modo inout, questa tecnica è senz’altro da sconsigliarsi, sia perchè
riduce la leggibilità del codice, sia perchè non consente di evidenziare immediatamente errori di
progetto dovuti ad un utilizzo scorretto dei terminali di una entità.

3.2 Tipi. Tutti i terminali di ingresso e di uscita di un’entità (così come tutti i segnali, le variabili
e le costanti che introdurremo fra breve) devo avere un opportuno tipo. Il tipo specifica l’insieme di
valori che un segnale può assumere; ad ogni tipo è inoltre generalmente associato un insieme di
operatori. Il VHDL prevede pochi tipi predefiniti. Nel listato di Fig. 2.1. abbiamo incontrato i due
tipi bit e bit_vector. Il tipo bit ha due valori ‘0’ ed ‘1’, mentre il tipo bit_vector rappresenta un array
di bit (un bus). Ad esempio la linea 3 nel listato di Fig. 2.1:
b: in bit_vector (3 downto 0);

definisce un vettore di 4 bit. La keyword downto definisce l’ordinamento dei bit che compongono
il vettore. Se nella descrizione architetturale incontriamo l’assegnazione:
b <= “1100”;

avremo assegnato a b(3) e b(2) il valore ‘1’ ed a b(1) e b(0) il valore ‘0’ (si noti l’utilizzo dei
doppi apici per definire mediante una stringa un valore di tipo bit_vector). In VHDL è possibile
definire sia un ordinamento discendente (con la parola chiave downto) sia un ordinamento
ascendente (con la parola chiave to). Se, ad esempio, avessimo definito il vettore b come:

b: in bit_vector (0 to 3);
6 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

con l’assegnazione:

b <= “1100”;

avremmo assegnato a b(3) e b(2) il valore ‘0’ ed a b(1) e b(0) il valore ‘1’.

Contrariamente a quanto si possa o prima vista immaginare, i tipi bit e bit_vector non sono
comunemente utilizzati in VHDL, in quanto non consentono, ad esempio, di definire che un segnale
sia in condizioni di alta impedenza o che il livello di un segnale sia ottenuto mediante una logica
cablata. In effetti, il VHDL consente di adoperare tipi definiti dall’utente, i più comuni dei quali
sono i tipi enumerati. Se tipi definiti dall’utente vengono utilizzati in più progetti, è opportuno
raggruppare le definizioni dei tipi (ed anche le definizioni di funzioni e procedure comuni) in una
libreria. Gran parte dei simulatori e dei sintetizzatori VHDL supportano alcune librerie standard. Di
uso molto comune è la libreria standard IEEE 1164, al cui interno sono definiti alcuni tipi di
notevole utilità per la sintesi e la simulazione di circuiti digitali. In particolare, di largo impiego è il
tipo std_logic, a nove valori, che utilizzeremo diffusamente nel seguito. Vedremo più avanti alcuni
dettagli relativi all’utilizzo del tipo std_logic; per il momento possiamo pensare di utilizzare i due tipi
std_logic ed std_logic_vector in sostituzione dei tipi bit e bit_vector.
Per poter utilizzare la libreria standard IEEE 1164 è necessario includere due linee all’inizio di
ogni file VHDL in cui specificare il nome della libreria e quali parti della libreria si intende adoperare;
si veda ad esempio la descrizione di Fig. 3.3

4. Definizione dell’architettura.

Se la dichiarazione di entità può essere vista come una “scatola nera”, di cui si specificano gli
ingressi e le uscite, ma non i dettagli interni, la definizione di architettura rappresenta il contenuto
della “scatola nera”.

1 -- Descrizione VHDL di un comparatore a 4 bit


2 library ieee;
3 use ieee.std_logic_1164.all;
4 entity eqcomp is
5 port (a, b: in std_logic_vector (3 downto 0);
6 eq, neq : out std_logic);
7 end eqcomp;
8
9 architecture dataflow of eqcomp is
10 signal aux1 : std_logic;
11 begin
12 neq <= not aux1; -- neq attivo basso
13 aux1 <= ‘1’ when (a = b) else ‘0’;
14 eq <= aux1;
15 end dataflow;

Figura 3.3: Descrizione di un comparatore a 4 bit, utilizzando lo standard IEEE 1164.


7 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

architecture nome_architettura of nome_entita is


dichiarazioni di tipo
dichiarazioni di segnali
dichiarazioni di costanti
definizione di funzioni
definizioni di procedure
dichiarazioni di componenti
begin
statement concorrente
. . . .
statement concorrente
end nome_architettura;

Fig. 4.1 Sintassi della definizione architetturale in VHDL

Il VHDL consente di descrivere un sistema utilizzando costrutti molto diversi. E’ possibile


utilizzare una descrizione algoritmica, ad alto livello, così come è possibile descrivere il sistema come
interconnessione di porte logiche elementari. E’ prassi comune distinguere tre possibili stili di
descrizione architetturale: comportamentale (behavioral), data-flow e strutturale. Come
vedremo più avanti, il VHDL consente in realtà di utilizzare nella definizione dell’architettura una
qualsiasi combinazione dei tre stili.
La Figura 4.1 mostra la sintassi della definizione architetturale in VHDL1.
Il nome dell’architettura è un identificatore definito dall’utente, mentre le dichiarazioni e le
definizioni in Fig. 4.1 possono apparire in un qualsiasi ordine.
La più semplice definizione è quella relativa ai segnali, che è molto simile a quella relativa ai port
di una entità, con la differenza che non si deve specificare il modo:

signal nome_segnale : tipo_segnale;

Un segnale definito all’interno di una architettura corrisponde, grosso modo, ad una linea
di collegamento in un diagramma logico.

Le costanti vengono utilizzate in VHDL per rendere più leggibile e più facilmente modificabile il
codice. La definizione di una costante è la seguente:

constant nome_costante : tipo_costante := valore;

Nel seguito analizzeremo i tre stili di descrizione architetturale (comportamentale, data-flow e


strutturale), rimandando ad un paragrafo successivo lo studio delle dichiarazioni di tipo e di funzioni.

1
Il VHDL-93 consente, facoltativamente, di inserire la keyword architecture fra end ed il nome
dell’architettura.
8 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

5 Descrizione VHDL strutturale.

La descrizione strutturale è la più semplice descrizione architetturale VHDL. Il sistema viene


infatti descritto mediante un’interconnessione di opportuni componenti, in maniera del tutto
analoga ad una rappresentazione del circuito mediante schema a blocchi. In una descrizione
strutturale avremo dunque dei componenti, che sono utilizzati o istanziati più volte, e sono fra loro
collegati utilizzando dei segnali.

component nome_componente is
port ( nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale);
end component;

Fig. 5.1 Dichiarazione di componente

nome_label : nome_componente
port map(segnale1, segnale2, ..., segnaleN);

nome_label : nome_componente
port map(port1 => segnale1, ..., portN => segnaleN);

Fig. 5.2 Sintassi per l’istanza di un componente

I componenti utilizzati nella descrizione strutturale devono essere dichiarati all’interno


dell’architettura. Come mostra la Fig. 5.1, la dichiarazione di un componente è molto simile alla port
declaration di un’entità, in quanto elenca per ogni terminale: il nome, il modo ed il tipo1.
Ogni volta che si utilizza (si istanzia) un componente in una descrizione strutturale si deve
utilizzare la sintassi di Fig 5.2. Ogni istanza richiede una etichetta (label) differente.
La parola chiave port map introduce una lista che associa i port della entità con i segnali utilizzati
nell’architettura. La lista può essere scritta in due stili diversi. Il primo è di tipo posizionale: il primo
segnale elencato all’interno del port map corrisponde al primo port del componente, il secondo
segnale al secondo port e così via. In VHDL è possibile utilizzare una corrispondenza per nome: in
questo caso ogni port dell’entità è collegato ad un segnale utilizzando l’operatore “=>”; l’ordine con
cui compaiono le coppie port-segnale è in questo caso ininfluente.

Come esempio di descrizione VHDL consideriamo un circuito combinatorio caratterizzato da un


bus di ingresso a quattro bit, denominato a, e da un bus di uscita a tre bit, che chiameremo y. Nel
nostro circuito y dovrà fornire un valore binario corrispondente al numero di bit ‘1’ dell’ingresso. Ad
esempio, all’ingresso “1101” (tre bit ‘1’) dovrà corrispondere l’uscita “011” (il valore 3 espresso in
binario), all’ingresso “1001” l’uscita “010” ecc.
Uno schema a blocchi del nostro contatore di bit ‘1’ prevede l’utilizzo di un full-adder e di due
half-adders, secondo lo schema di Fig. 5.3 (pagina seguente). Le linee di collegamento che
compaiono in questo schema a blocchi e che corrisponderanno a dei segnali nell’architettura VHDL
sono state denominate: s0, c0, x0.
Allo schema di Fig. 5.3 corrisponde la descrizione VHDL strutturale di Fig. 5.4.

1
Il VHDL-93 consente, facoltativamente, di ripetere il nome del componente dopo le keywords end component.
9 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

a3 a2

a1
c0
s0

x0 a0

y2 y1 y0

Fig. 5.3. Schema logico per il contatore di bit ‘1’.

1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity one_counter is
4 port (a : in std_logic_vector (3 downto 0);
5 y : out std_logic_vector (2 downto 0));
6 end one_counter;
7 architecture strutturale of one_counter is
8
9 signal s0, c0, x0 : std_logic;
10
11 component full_add
12 port (i2, i1, i0 : in std_logic;
13 s,c : out std_logic);
14 end component;
15
16 component half_add
17 port (i1, i0 : in std_logic;
18 s,c : out std_logic);
19 end component;
20
21
22 begin
23 f1 : full_add
24 port map(i2=> a2, i1 => a1, i0 => a3, s => s0, c => c0);
25
26 h1 : half_add
27 port map (i1 => a0, i0 => s0, s => y0, c => x0);
28
29 h2 : half_add
30 port map (c0, x0, y1, y2);
31
32 end strutturale;
33
34 end one_counter;

Fig. 5.4. Descrizione VHDL strutturale del contatore di bit ‘1’.

Le linee da 1 a 6 riportano la dichiarazione di entità, mentre in linea 9 si definiscono i segnali


interni. Le linee da 11 a 19 corrispondono alla dichiarazione dei due componenti utilizzati. Le linee
da 23 a 30 sono le tre istanze che compongono il circuito. In linea 24 ed in linea 27 si è utilizzata una
corrispondenza port-segnale per nome, mentre in linea 30 la corrispondenza è di tipo posizionale.
10 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity full_add is
port (i2, i1, i0 : in std_logic;
s,c : out std_logic);
end full_add;
architecture strutturale of full_add is
component half_add
port (i1, i0 : in std_logic;
s, c : out std_logic);
end component;
component OR2
port (I0 : in std_logic; I1 : in std_logic;
O : out std_logic );
end component;
signal aux1, aux2, aux3 : std_logic;

begin
h1 : half_add
port map(i1 => i2, i0 => i1, s => aux1, c => aux2);
h2 : half_add
port map(i1 => aux1, i0 => i0, s => s, c => aux3);
o1 : or2
port map (i0 => aux2, i1 => aux3, o => c);
end strutturale;

Fig. 5.5. Descrizione VHDL strutturale del full adder.

Le descrizioni VHDL strutturali possono essere gerarchiche. Nel nostro esempio, un full-adder
può essere realizzato utilizzando due half adders ed una porta OR a due ingressi. A sua volta un half
adder può essere realizzato mediante da una porta XOR a due ingressi e da una AND a due ingressi.
Le descrizioni VHDL strutturali del full-adder e dell’half-adder sono riportate rispettivamente in Fig.
5.5 ed in Fig. 5.6

library ieee;
use ieee.std_logic_1164.all;
entity half_add is
port (i1, i0 : in std_logic;
s,c : out std_logic);
end half_add;

architecture strutturale of half_add is


component AND2
port ( I0 : in std_logic; I1 : in std_logic;
O : out std_logic );
end component;
component XOR2
port (I0 : in std_logic; I1 : in std_logic;
O : out std_logic );
end component;
begin
xx1 : xor2 port map(i1, i0, s);
aa1 : and2 port map (i1, i0, c);
end strutturale;

Fig. 5.6 Descrizione VHDL strutturale del half adder.


11 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Dai listati di Fig. 5.5 e 5.6 osserviamo che il nostro progetto utilizza, all’ultimo livello della
gerarchia, le sole celle XOR2, AND2 ed OR2 che fanno parte della libreria standard del sistema di
sviluppo con il quale questo progetto è stato creato e compilato (XILINX web pack).

In molte applicazioni è necessario istanziare molte copie di uno stesso componente all’interno di
un’architettura. Il VHDL include a tal fine lo statement generate, che consente di descrivere in
maniera compatta strutture ripetitive. La sintassi semplificata dello statement generate è mostrata in
Fig. 5.7, mentre un esempio di aplicazione dello statement generate è fornito in Fig. 5.8, che mostra
una descrizione VHDL strutturale di un circuito costituito da un banco di 16 invertitori.

nome_label : for identificatore in range generate


istanza_componente;
end generate;

Fig. 5.7. Sintassi semplificata dello statement VHDL generate

library ieee;
use ieee.std_logic_1164.all;
entity inv16 is
port (a : in std_logic_vector (15 downto 0);
b : out std_logic_vector (15 downto 0));
end;
architecture structure of inv16 is
component INV
port (I : in std_logic; O : out std_logic);
end component;

begin
g1 : for k in 15 downto 0 generate
n1 : INV port map(I => a(k), O => b(k));
end generate;
end structure;

Fig. 5.8. Descrizione VHDL di un invertitore a 16-bit.

Per rendere più versatile la descrizione VHDL di un sistema è possibile definire entità ed
architetture parametrizzate. Ad esempio, possiamo generalizzare la descrizione dell’insieme di
invertitori del listato di Fig. 5.8, in modo tale che la dimensione del set di inveritori sia generica.
A tal fine è necessario introdurre delle costanti generiche all’interno della dichiarazione di entità,
secondo la sintassi mostrata in Fig. 5.9.

entity nome_entita is
generic (nome_costante : tipo;
. . . .
nome_costante : tipo);
port (nome_segnale : modo tipo_segnale;
. . . .
nome_segnale : modo tipo_segnale);
end nome_entita;

Fig. 5.9. Dichiarazione di entità con costanti generiche


12 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity invx is
generic (size : integer);
port (a : in std_logic_vector (size-1 downto 0);
b : out std_logic_vector (size-1 downto 0));
end;
architecture structure of invx is
component INV
port (I : in std_logic; O : out std_logic);
end component;
begin
g1 : for k in size-1 downto 0 generate
n1 : INV port map(I => a(k), O => b(k));
end generate;
end structure;

Fig. 5.10. Descrizione VHDL di un array di invertitore di dimensioni generiche.

Ognuna delle costanti generiche può essere utilizzata nella descrizione dell’architettura. Ad
esempio consideriamo il listato di Fig. 5.10, che, combinando generic e generate, riporta una
descrizione strutturale parametrizzabile di un array di invertitori. Si noti in Fig. 5.10 che la costante
generica size è di tipo integer. Il tipo integer è un tipo predefinito in VHDL.
Il valore delle costanti generiche viene stabilito quando l’entità è istanziata come componente,
mediante una clausola generic map. La Fig 5.11 mostra l’utilizzo del componente invx del listato
di Fig. 5.10 per realizzare tre banchi di invertitori, a 4, 8 e 12 bit. Si noti, in linea 13, che alla
costante generica size è stato assegnato un valore di default pari ad 8. Pertanto in linea 22 non è
stato necessario utilizzare un generic map.

1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity invy is
4 port (a8 : in std_logic_vector (7 downto 0);
5 b8 : out std_logic_vector (7 downto 0);
6 a4 : in std_logic_vector (3 downto 0);
7 b4 : out std_logic_vector (3 downto 0);
8 a12 : in std_logic_vector (11 downto 0);
9 b12 : out std_logic_vector (11 downto 0) );
10 end;
11 architecture structure of invy is
12 component invx
13 generic (size : integer := 8);
14 port (a : in std_logic_vector (size-1 downto 0);
15 b : out std_logic_vector (size-1 downto 0));
16 end component;
17
18 begin
19
20 g1: invx generic map (size =>4) port map(a=>a4, b=>b4);
21 g2: invx generic map (size =>12) port map(a=>a12, b=>b12);
22 g3: invx port map(a=>a8, b=>b8);
23
24 end structure;

Fig. 5.11. Entità ed architettura VHDL che utilizza l’invertitore di dimensioni generiche del
listato di Fig. 5.10.
13 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity one_counter is
4 port (a : in std_logic_vector (3 downto 0);
5 y : out std_logic_vector (2 downto 0));
6 end one_counter;
7 architecture data_flow of one_counter is
8
9 signal s0, c0, x0 : std_logic;
10
11 begin
12 y(0) <= s0 xor a(3);
13 y(1) <= x0 xor c0;
14 y(2) <= c0 and x0;
15 s0 <= a(2) xor a(1) xor a(0);
16 c0 <= (a(2) and a(1)) or (a(2) and a(0)) or (a(1) and a(0));
17 x0 <= s0 and a(3);
19 end data_flow;

Fig. 6.1. Descrizione VHDL data flow del contatore di bit ‘1’.

and, or, nand, nor, xor, xnor, not

Fig. 6.2. Operatori predefiniti per i tipi boolean, bit e bit_vector.

6. Descrizione dataflow.

Il listato di Fig. 6.1 mostra una descrizione di tipo data-flow del contatore di bit ‘1’. Nel listato si
utilizzano gli operatori xor ed and, che sono due degli operatori predefiniti del VHDL per per i tipi
bit e bit_vector; l’elenco degli operatori predefiniti per questi tipi è riportato in Fig. 6.2. Nella libreria
IEEE gli operatori di Fig. 6.2 vengono definiti anche per i tipi std_logic ed std_logic_vector.
Si noti che per utilizzare questi operatori con vettori i due operandi devono essere della stessa
lunghezza. Inoltre gli operatori di Fig. 6.2, ad eccezione dell’operatore not, non hanno un ordine di
precedenza. Ad esempio, l’assegnazione:

x <= w and q or p;

fornisce un errore in fase di compilazione. E’ necessario utilizzare le parentesi, vedi ad esempio la


Fig. 6.1, qualora vi sia una possibile ambiguità.
La descrizione di Fig. 6.1 è di tipo dataflow in quanto descrive il modo con il quale i dati
“fluiscono” dai terminali di ingresso, attraverso i segnali interni, fino ai terminali di uscita
dell’entità. Come notato in precedenza gli statement VHDL sono concorrenti, per cui l’ordine con il
quale compaiono nel listato VHDL è del tutto irrilevante.

6.1 Simulazione funzionale. Per discutere in maggior dettaglio il concetto di statement


concorrente è necessario introdurre alcuni aspetti relativi alla simulazione di una descrizione VHDL;
a tale scopo consideriamo come esempio la descrizione data-flow di Fig. 6.1, riportata per comodità
anche in Fig. 6.3 a pagina seguente. Dal listato si ricava immediatamente una relazione di dipendenza
fra i segnali. I segnali che si trovano a sinistra dell’operatore di assegnazione <= dipendono dai
segnali che appaiono a secondo membro. Ad esempio, dalla linea 12 del listato di Fig. 6.1 otteniamo
che y(0) dipende da s0 e da a(3), mentre dalla linea 13 si ricava che y(1) dipende da x0 e da c0.
14 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

12 y(0) <= s0 xor a(3);


13 y(1) <= x0 xor c0;
14 y(2) <= c0 and x0;
15 s0 <= a(2) xor a(1) xor a(0);
16 c0 <= (a(2) and a(1)) or (a(2) and a(0)) or (a(1) and a(0));
17 x0 <= s0 and a(3);

Fig. 6.3. Descrizione VHDL data flow del contatore di bit ‘1’.

Tempo a eventi schedulati s0 c0 x0 y


0: 0000 a(2)=’1’ @ t0 0 0 0 000
t0: 0100 s0=’1’ @ t0+δ 0 0 0 000
to+δ 0100 y(0)=’1’ @ t0+2δ 1 0 0 000
to+2δ 0100 1 0 0 001
... ... .. .. .. ...
t1 0101 s0=’0’ @ t1+δ 1 0 0 001
c0=’1’ @ t1+δ
0101 y(0)=’0’ @ t1+2δ 0 1 0 001
t1+δ
y(1)=’1’ @ t1+2δ
t1+2δ 0101 0 1 0 010
... ... .. .. .. ...

Fig. 6.4. Simulazione del listato di Fig. 6.3.

Supponiamo che inizialmente sia: a=”0000”, s0=c0=x0=’0’. Al tempo t0 il valore del segnale a(2)
cambia e si ha: a(2)=’1’. Quando il valore di un segnale cambia, diremo che per quel segnale si è
verificato un evento. Poichè s0 e c0 dipendono da a(2), le due linee 15 e 16 del listato vengono
valutate. Il risultato della valutazione della riga 15 è ‘1’; poichè il valore attuale di s0 è pari a ‘0’, il
simulatore inserirà in una lista degli eventi pendenti, l’evento: s0=’1’. Questo evento viene previsto
(schedulato) per il tempo: t=t0+δ. Possiamo pensare a δ (il delta delay) come ad un ritardo
infinitesimale, dopo il quale s0 potrà assumere il valore ‘1’. La valutazione della linea 16 fornisce
‘0’; dato che c0 è già pari a ‘0’ nessun evento viene schedulato per questo segnale.
Il simulatore passa ora al tempo t=t0+δ, quando si ha l’evento: s0=’1’. Poichè x0 ed y(0)
dipendono da s0 vengono valutate le due linee 17 e 12. La valutazione della linea 12 consente di
inserire l’evento y(0)=’1’ al tempo t=t0+2δ nella lista degli eventi; la valutazione delle linea 17 non
comporta la schedulazione di nessun evento per il segnale x0.
Il simulatore passa ora al tempo t0+2δ quando si ha l’evento: y(0)=’1’. Poichè nessun segnale
dipende da y(0) il ciclo di simulazione è completato. Il risultato complessivo di questo ciclo di
simulazione è stato quello di portare s0 ed y(0) ad ‘1’.
Supponiamo ora che al tempo t1 si abbia l’evento: a(0) =’0’. Poichè s0 e c0 dipendono da a(0)
vengono valutate le linee 15 e 16: come risultato vengono schedulati gli’evento: s0=’0’ e c0=’1’ per
t=t1+δ. Si passa così al tempo t=t1+δ e si valutano le linee 12,13,14 e 17, schedulando gli eventi
y(0)=’0’ ed y(1)=’1’ per t=t1+2δ. Al tempo t=t1+2δ si completa il ciclo di simulazione.
La Fig. 6.4 mostra il susseguirsi degli eventi durante la simulazione.

La simulazione appena descritta è di tipo funzionale in quanto non porta in conto i ritardi del
sistema. Vedremo in seguito come il VHDL consenta di modellare i tempi di propagazione.
15 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

1 architecture data_flow of one_counter is


2 begin
3 with a select
4 y <= "100" when "1111",
5 "011" when "0111"|"1011"|"1101"|"1110",
6 "010" when "0011"|"0110"|"0101"|"1010"|"1001"|"1100",
7 "000" when "0000",
8 "001" when others;
9 end data_flow;

Fig. 6.5. Descrizione del contatore di bit ‘1’ con assegnazione selezionata.

with segnale_selezione select


segnale <= valore_a when caso_a,
valore_b when caso_b,
. . . . . . . . . . .
valore_n when caso_n;

Fig. 6.6. Assegnazione selezionata

6.2 Assegnazione selezionata e condizionale. Oltre all’operazione di assegnazione


incondizionata, contraddistinta dal simbolo “<=”, il VHDL introduce i costrutti di assegnazione
selezionata ed assegnazione condizionale.
Un esempio di assegnazione selezionata, sempre riferito al contatore di bit ‘1’, è mostrato in Fig.
6.5. La sintassi dell’assegnazione selezionata è mostrata in Fig. 6.6. In questa figura, caso_a, caso_b
ecc. possono essere o un valore del segnale di selezione (vedi la riga 4 e la riga 7 del listato di Fig.
6.3) o un elenco di possibili valori del segnale di selezione, separati dal carattere ‘|’ (vedi le righe 5 e
6 del listato di Fig. 6.5). Ci sono due regole importanti da ricordare per l’assegnazione selezionata:

• i casi elencati devono essere tutti mutuamente esclusivi


• i casi elencati devono prevedere tutti i possibili valori che può assumere
il segnale di selezione

Molto spesso, per soddisfare la seconda regola, viene utilizzata la parola chiave others
nell’ultima clausola when, che sta ad indicare tutti i possibili valori del segnale di selezione non
considerati nei precedenti when (vedi ad esempio il rigo 8 del listato di Fig. 6.5)

La sintassi dell’assegnazione condizionata è mostrata in Fig. 6.7. A differenza dell’assegnazione


selezionata, le condizioni elencate dopo il when sono non devono necessariamente essere
mutuamente esclusive. Il valore è assegnato al segnale in base alla prima condizione elencata ad
essere verificata. Ad esempio, se condizione1 è falsa mentre sono vere sia condizione2 che
condizione3, al segnale verrà assegnato il valore_b. Se le varie condizioni elencate dopo i
when sono mutuamente esclusive, il costrutto when-else diviene semplicemente una versione più
verbosa del costrutto with-select-when.

segnale <= valore_a when condizione1 else


valore_b when condizione2 else
valore_c when condizione3 else
. . . . . . .
valore_n;

Fig. 6.7. Assegnazione condizionata


16 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

= /= > >= < <=

Fig. 6.8. Operatori relazionali.

1 architecture data_flow of one_counter is


2 signal tmp : boolean;
3 begin
4 y(2) <= '1' when tmp else '0';
5
6 y(1 downto 0) <=
7 "00" when (a="0000") else
8 "11" when (a="0111") or (a="1011") or (a="1101") or
9 (a="1110") else
10 "01" when (a="0001") or (a="0010") or (a="0100") or
11 (a="1000") else
12 "10";
13
14 tmp <= (a="1111");
15
16 end data_flow;

Fig. 6.9. Descrizione del contatore di bit ‘1’ con assegnazione condizionata.

Le condizioni che compaiono dopo il when sono valori booleani ottenuti come risultato di
operazioni relazionali o direttamente segnali di tipo boolean. Il tipo boolean è uno dei tipi predefiniti
del VHDL ed ha i due valori true e false. Gli operatori relazionali del VHDL sono riportati in Fig.
6.8; si noti che gli operatori predefiniti per segnali di tipo boolean sono quelli che abbiamo già visto
in Fig. 6.2 per i tipi bit e bit_vector.
La Fig. 6.9 mostra l’utilizzo dell’assegnazione condizionata per descrivere il contatore di bit ‘1’.
In questo caso si è preferito calcolare a parte il bit y(2) (in linea 4). In linea 2 si introduce un segnale
booleano, utilizzato come condizione nel when-else di linea 4. Il when-else delle linee da 6 a 12 è
utilizzato per calcolare i bit y(1) ed y(0).

Per meglio evidenziare le differenze fra assegnazione selezionata ed assegnazione condizionata


consideriamo i due listati di Fig. 6.10.

library ieee; library ieee;


use ieee.std_logic_1164.all; use ieee.std_logic_1164.all;
entity sel is entity sel is
port (a, b, c : in std_logic; port (a, b, c : in std_logic;
sa, sb, sc: in std_logic; sa, sb, sc: in std_logic;
y : out std_logic); y : out std_logic);
end sel; end sel;
architecture data_flow of sel is architecture data_flow of sel is
signal selez: std_logic_vector(2 downto 0);
begin begin
selez <= (sa, sb, sc); y <= a when sa='1' else
with selez select b when sb='1' else
y <= a when "100", c when sc='1' else
b when "010", '0';
c when "001",
'0' when others;
end data_flow; end data_flow;

Fig. 6.10. Esempi di assegnazione selezionata (a sinistra) e condizionata (a destra).


17 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

In entrambi i listati i segnali di controllo sa, sb ed sc definiscono quale dei tre ingressi a, b o c
deve essere inviato sull’uscita y.
Nel listato di destra, che utilizza un’assegnazione condizionata, le tre condizioni sa=’1’, sb=’1’ ed
sc=’1’ non sono mutuamente esclusive. Poichè il primo when si riferisce all’ingresso sa,
quest’ingresso avrà maggiore priorità rispetto agli altri due (se sono contemporaneamente alti sa ed
sc, ad esempio, l’uscita sarà uguale ad a). Analogamente, l’ingresso sb avrà maggiore priorità
rispetto ad sc.
Nel listato di sinistra, che utilizza un’assegnazione selezionata, i tre ingressi a, b e c hanno la
medesima priorità. Il when others assicura che se sono alti due o più ingressi di selezione l’uscita
sarà pari a ‘0’. Si noti l’utilizzo di uno statement di aggregazione:

selez <= (sa, sb, sc);

per assegnare i valori sa, sb ed sc ad un segnale ausiliario di tipo std_logic_vector. L’unica linea
precedente è equivalente alle tre assegnazioni:

selez(2) <= sa; selez(1) <= sb; selez(0) <= sc;

Il circuito sintetizzato a partire dal listato di destra (assegnazione condizionata) è mostrato in


Figura 6.11, mentre la Fig. 6.12 riporta il circuito sintetizzato dalla descrizione che utilizza
l’assegnazione selezionata. Si lascia al lettore il compito di scrivere le equazioni booleane dei due
circuiti e di verificarne la congruenza con i due listati VHDL.

SA
SA A INV
AND2 AND2 Y
A
OR2
B B

SB AND2
SB
INV
C OR2
SC SC
AND3

Fig. 6.11. Circuito sintetizzato dal listato a destra in fig. 6.10 ( assegnazione condizionata).

SC
SB INV
SA AND2
Y
SA AND3
OR2 OR2
SB
B B
AND3
A A
AND4
SC
C C

Fig. 6.12. Circuito sintetizzato dal listato a sinistra in fig. 6.10 ( assegnazione selezionata).
18 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity latch is
port(d,c : in std_logic;
q : buffer std_logic);
end;
architecture beh of latch is
begin

q <= d when c='1' else q;

end beh;

Fig. 6.13. D-latch descritto con un costrutto when..else.

6.3 Sintesi di latch e flip-flop. Gli esempi precedenti si riferiscono a sistemi combinatori. Il
costrutto di assegnazione condizionale (when...else) consente di descrivere facilmente sistemi con
memoria (latch e flip-flop).
Il listato di Fig. 6.13 mostra la descrizione di un D-latch: quando c=’1’ q è pari all’ingresso d,
mentre q mantiene il proprio valore quando c=’0’. Si noti che q è definito con modo buffer in quanto
compare anche a secondo membro dell’operatore di assegnazione; si lascia al lettore la semplice
modifica al listato, con l’introduzione di un segnale ausiliario, che consente di definire q con modo
out.
Per descrivere un flip-flop è necessario utilizzare un’espressione booleana che sia vera in
corrispondenza del fronte di salita o di discesa del clock. A tale scopo è necessario utilizzare
l’attributo ‘event oppure l’attributo ‘stable. Come vedremo anche in seguito in maggior dettaglio, in
VHDL un attributo fornisce informazioni relativamente a segnali, tipi, sottotipi ecc. In particolare,
l’attributo ‘event applicato ad un segnale fornisce il valore true se il segnale ha avuto un evento
nell’ultimo delta time; analogamente l’attributo ‘stable fornisce il valore true se il segnale è rimasto
stabile nell’ultimo delta time (l’attributo ‘stable è in realtà più generale, e consente di stabilire se un
segnale è rimasto stabile per uno specificato intervallo di tempo). Utilizzando uno dei due attributi
‘event oppure ‘stable possiamo facilmente scrivere due espressioni booleane che sono vere in
corrispondenza del fronte di salita del clock:

c=’1’ and c’event


c=’1’ and not c’stable

Analogamente, in corrispondenza del fronte di discesa del clock saranno vere le due espressioni
seguenti:

c=’0’ and c’event


c=’0’ and not c’stable

La Fig. 6.14 (pagina seguente) mostra, ad esempio, la descrizione VHDL di un banco di 16


registri, attivo sul fronte di salita del clock.
19 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity ff is
port(d : in std_logic_vector (15 downto 0);
clk : in std_logic;
q : buffer std_logic_vector (15 downto 0));
end;
architecture beh of ff is
begin

q <= d when (clk='1'and clk’event) else q;

end beh;

Fig. 6.14. Flip-flop attivi sul fronte di salita del clock.

library ieee;
use ieee.std_logic_1164.all;
entity ff is
port(d : in std_logic_vector (15 downto 0);
clk : in std_logic;
q : buffer std_logic_vector (15 downto 0));
end;
architecture beh of ff is
begin

q <= d when rising_edge(clk) else q;

end beh;

Fig. 6.15. Uso della funzione rising_edge per identificare il fronte di salita del clock.

La libreria standard IEEE 1164 introduce due funzioni: rising_edge() e falling_edge() che
assumono valore true quando il segnale utilizzato come argomento delle funzioni effettua un fronte
di salita o di discesa. La Fig. 6.15 mostra l’utilizzo della funzione rising_edge per descrivere lo stesso
banco di registri di Fig. 6.14.
La Fig. 6.16 mostra la descrizione dell’architettura VHDL di un flip-flop con reset asincrono. Il
reset è in questo caso il segnale con maggiore priorità: quando il reset è alto l’uscita q si porta a ‘0’,
indipendentemente dal segnale di clock. Quando il reset è basso il funzionamento è dettato dal fronte
di salita del clock.

library ieee;
use ieee.std_logic_1164.all;
entity ff_rst is
port(d,clk, rst : in std_logic;
q : buffer std_logic);
end;
architecture beh of ff_rst is
begin
q <= ‘0’ when (rst=’1’)
else d when rising_edge(clk)
else q;
end beh;

Fig. 6.16. Flip-flop con reset asincrono.


20 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

architecture beh of ff_rst is


begin
q <= (d and rst) when rising_edge(clk)
else q;
end beh;
Fig. 6.17. Flip-flop con reset sincrono.

library ieee; library ieee;


use ieee.std_logic_1164.all; use ieee.std_logic_1164.all;
entity latch is entity latch is
port(d,c,f : in std_logic; port(d,c : in std_logic;
q : out std_logic); q : buffer std_logic);
end; end;
architecture beh of latch is architecture beh of latch is
begin begin

q <= d when rising_edge(c) q <= d when (rising_edge(c) or falling_edge(c))


else f; else q;

end beh; end beh;


Fig. 6.18. Due descrizioni VHDL non sintetizzabili.

La Fig. 6.17 mostra la descrizione dell’architettura VHDL di un flip-flop con reset sincrono. In
questo caso il reset è efficace solo in corrispondenza del fronte attivo del clock.
Come abbiamo accennato nell’introduzione, il VHDL è stato originariamente introdotto come
linguaggio per la descrizione e la simulazione di sistemi digitali e solo in un secondo momento è stato
utilizzato per la sintesi. Non deve pertanto stupire che il linguaggio abbia molti costrutti che non
possono essere sintetizzati. La Fig. 6.18 mostra due esempi di descrizioni VHDL sintaticcamente
corrette e simulabili, ma non sintetizzabili. Il listato di sinistra descrive un sistema che fornisce
un’uscita q sempre uguale all’ingresso f, tranne in corrispondenza dei fronti di salita di c, quando
l’uscita deve invece assumere il valore del segnale d. Il listato di destra descrive invece un flip-flop
comandato da entrambi i fronti del clock.

7. Descrizione comportamentale.

L’elemento fondamentale di una descrizione VHDL comportamentale è il “processo”. Un


processo è introdotto dalla parola chiave process (eventalmente preceduta da una etichetta) e
termina con un end process, secondo la sintassi di Fig. 7.1. Poichè un processo compare all’interno
di un’architettura, ha accesso a tutti segnali, i tipi le costanti dichiarate nell’architettura stessa.
Peraltro, all’interno di un processo è possibile definire dei tipi, delle costanti, delle variabili che sono
locali al processo e sono quindi invisibili ad altri processi o statements concorrenti dell’architettura.

label : process(segnale_a, segnale_b, ...., segnale_n);


dichiarazioni di tipo;
dichiarazioni di variabili;
dichiarazioni di costanti;
begin
statement sequenziale;
statement sequenziale;
. . . .
statement sequenziale;
end process;

Fig. 7.1. Sintassi di un processo.


21 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Come evidenzia la Fig. 7.1, in un processo non è possibile definire dei segnali, ma delle
variabili. Una variabile in VHDL viene utilizzata come elemento di “appoggio” per descrivere un
algoritmo e spesso può non avere un elemento corrispondente nel circuito sintetizzato. La sintassi
per definire una variabile è molto simile a quella utilizzata per definire un segnale in un’architettura:

variable nome_variabile : tipo;

Vedremo fra breve la differenza che intercorre fra un segnale ed una variabile.
I segnali elencati dopo la parola chiave process rappresentano la lista di sensibilità (sensitivity
list) del processo. Durante la simulazione di una descrizione VHDL, un processo può essere in uno
dei due stati: in esecuzione oppure sospeso. Un processo entra in esecuzione quando uno dei segnali
elencati nella sensitivity list cambia il proprio valore (ha un evento). A seguito di questo evento, gli
statements che compaiono dopo il begin vengono eseguiti in sequenza partendo dal primo fino a
giungere all’end process. Eseguito l’ultimo statement, il processo viene sospeso. Se, a seguito
dell’esecuzione del processo, qualche segnale della sensitivity list cambia il proprio valore, il
processo viene eseguito nuovamente, fin quando tutti i segnali della sensitivity list non hanno
raggiunto uno stato stazionario.
E’ possibile sospendere un processo mediante un’istruzione di wait. Di questa possibilità ci
occuperemo in seguito.
In definitiva, un processo è nel suo complesso uno statement concorrente, che si attiva ogni volta
che uno dei segnali della sensitivity list cambia, così come lo statement:

y <= a and b;

si attiva ogni volta che uno dei due segnali a o b cambia il proprio valore. L’algoritmo
implementato da un processo, peraltro, è descritto mediante statement sequenziali, che vengono
eseguiti l’uno dopo l’altro, come in C o in pascal.
L’introduzione dei processi e degli statements sequenziali all’interno dei processi consente di
realizzare delle descrizioni ad alto livello, in cui l’enfasi è sull’algoritmo implementato
dall’architettura e non sui dettagli realizzativi. Un esempio è il listato di Fig. 7.2 (pagina seguente)
che si riferisce all’ormai ben noto contatore di bit ‘1’ di cui abbiamo già visto molteplici descrizioni
VHDL.
All’interno della nuova architettura abbiamo un solo statement concorrente: il processo
denominato ‘conta’. La lista di sensibilità del processo contiene il vettore a, l’ingresso del nostro
sistema combinatorio. In linea 10 viene definita una variabile di appoggio, denominata cnt, di tipo
integer. Alla variabile viene assegnato valore una prima volta in linea 13, mediante lo statement di
assegnazione := Si noti che l’operatore di assegnazione per le variabili “:=” è diverso dall’operatore
di assegnazione per i segnali “<=”.
Nelle linee da 14 a 18 si utilizza un ciclo di for per contare il numero di bit ‘1’ del vettore a; il
valore del conteggio è immagazinato nella variabile cnt.
Nelle linee da 19 a 28 uno statement if..then consente di assegnare un opportuno valore
all’uscita y, a seconda del valore della variabile cnt.
22 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

1 library ieee;
2 use ieee.std_logic_1164.all;
3 entity cnt1 is
4 port (a : in std_logic_vector (3 downto 0);
5 y : out std_logic_vector (2 downto 0));
6 end cnt1;
7 architecture behavioral of cnt1 is
8 begin
9
10 conta: process (a)
11 variable cnt : integer;
12 begin
13 cnt := 0;
14 for i in 3 downto 0 loop
15 if ( a(i)='1' ) then
16 cnt := cnt + 1;
17 end if;
18 end loop;
19 if cnt=0
20 then y <= "000";
21 elsif cnt=1
22 then y <= "001";
23 elsif cnt=2
24 then y <= "010";
25 elsif cnt=3
26 then y <= "011";
27 else y <= "100";
28 end if;
29 end process;
30 end behavioral;

Fig. 7.2. Descrizione comportamentale del contatore di bit ‘1’.

conta: process (a)


variable cnt : integer;
begin
cnt := 0;
if cnt=0
then y <= "000";
elsif cnt=1
then y <= "001";
elsif cnt=2
then y <= "010";
elsif cnt=3
then y <= "011";
else y <= "100";
end if;
for i in 3 downto 0 loop
if ( a(i)='1' ) then
cnt := cnt + 1;
end if;
end loop;
end process;
end behavioral;

Fig. 7.3. Modificando l’ordine degli statements nel listato di Fig. 7.2 si ottiene un’uscita
identicamente nulla.
23 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

L’ordine con cui si susseguono gli statement sequenziali nel processo di Fig. 7.2 è fondamentale
per descrivere il funzionamento del sistema. Per meglio evidenziare questo aspetto, si consideri il
listato di Fig. 7.3, in cui sono stati scambiati di posto il case ed il for. Nel nuovo processo dapprima
si inizializza cnt a zero, quindi si esegue lo statement case, che assegnerà sempre il valore “000”
all’uscita y. Il ciclo di for modifica quindi il valore della variabile cnt, che, peraltro, non viene più
utilizzata nel processo. In definitiva, Il listato di Fig. 7.3 fornirà un’uscita costante, pari a “000”,
indipendentemente dal valore dell’ingresso a.

7.1 Segnali e variabili. Esiste una differenza sostanziale fra un segnale ed una variabile. Per un
segnale uno statement di assegnazione <= non ha un effetto immediato, ma comporta,
eventualmente, la schedulazione di un evento che avrà luogo il delta time successivo. Per una
variabile, invece, lo statement di assegnazione := ha effetto immediato e non comporta lo
scheduling di nessun evento.
Consideriamo le due descrizioni VHDL di Fig 7.4. Apparentemente le due descrizioni sono
identiche. In realtà nel listato di sinistra si utilizzano due segnali x e z (dichiarati nell’architettura cui
il processo appartiene), mentre nel listato di destra si utilizzano due variabili.
Analizziamo dapprima il listato di sinistra. Supponiamo che tutti i segnali siano inizialmente al
valore ‘0’ e che si abbia un evento per il segnale y: la commutazione da ‘0’ ad ‘1’ al tempo t0. Il
processo si attiva all’istante t0 ed i due statements vengono eseguiti in sequenza. Quando si incontra
lo statement: x <= y viene schedulato l’evento: x=’1’ per: t=t0+δ. Quando si incontra lo statement:
z <= not x viene schedulato un evento per z, che deve divenire il negato di x. Poichè siamo al
tempo t0, il valore di x è ancora ‘0’. Pertanto l’evento schedulato è: z=’1’ per: t=t0+δ. Avendo
raggiunto l’end process, il processo si sospende.
Consideriamo ora il listato di destra, ipotizzando sempre che al tempo t0 per il segnale y si abbia
l’evento: commutazione da ‘0’ ad ‘1’. Anche in questo caso il processo si attiva all’istante t0.
Quando si incontra lo statement: x := y il valore di y viene assegnato immediatamente ad x che
diviene pertanto pari ad ‘1’. Si esegue quindi lo statement: z := not x; anche in questo caso il
valore not x viene assegnato immediatamente a z, che diviene pari a ‘0’. Avendo raggiunto l’end
process, il processo viene sospeso.
In definitiva, benchè i due listati di Fig. 7.4 sembrino a prima vista identici, il valore finale assunto
dal segnale z (listato di sinistra) è diverso da quello della variabile z (listato di destra).
Un semplice modo per interpretare le operazioni di assegnazione di valore a segnali all’interno di
un processo è basato sulle tre regole seguenti (valide se non vi sono clausole after nel processo):
- tutte le espressioni sono calcolate utilizzando per i segnali che si trovano a destra del simbolo
di assegnazione <= lo stesso valore che avevano quando il processo è stato attivato
- ogni segnale per cui si abbiano più operazioni di assegnazione all’interno del processo viene
aggiornato in base all’ultima operazione di assegnazione incontrata prima dell’end process.
- tutti i segnali vengono aggiornati alla fine del processo, quando il processo stesso viene
sospeso.

..... .....
signal x,y,z : std_logic; signal y : std_logic;
..... .....
process (y); process (y);
variable x,z : std_logic;
begin begin
x <= y; x := y;
z <= not x; z := not x;
end process; end process;

Fig. 7.4. Segnali e variabili.


24 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity and8 is
port(a : in std_logic_vector(7 downto 0);
y : buffer std_logic);
end;
architecture beh of and8 is
begin
process(a)
begin
y <='1';
for i in 7 downto 0 loop
y <= a(i) and y;
end loop;
end process;
end beh;

Fig. 7.5. Modello errato di una AND ad 8 ingressi.

Per concludere il discorso relativo a variabili e segnali consideriamo il listato di Fig 7.5. A prima
vista, il codice dovrebbe implementare la funzione and degli otto ingressi a(i); in effetti il listato di
Fig. 7.5 descrive un sistema la cui uscita y è identicamente nulla. Per spiegare questo comportamento
bisogna dapprima descrivere la fase di inizializzazione, che ha luogo in simulazione al tempo t=0.
Al tempo t=0 tutti i segnali e le variabili vengono inizializzati ad un valore di default (le variabili
ed i segnali di tipo bit sono inizializzati a ‘0’, mentre quelle di tipo std_logic ad ‘U’). Quindi tutti i
processi vengono eseguiti, fin quando non raggiungono lo stato di sospensione. Si noti che ogni
statement concorrente può essere visto come un processo avente come sensitivity list tutti i segnali
che compaiono a destra del simbolo di assegnazione; pertanto anche gli statements vengono eseguiti
una prima volta in fase di inizializzazione. Completata l’inizializzazione, la simulazione ha luogo
utilizzando l’approccio “pilotato dagli eventi” (event-driven) illustrato in precedenza.
Torniamo ora al listato di Fig. 7.5. Completata la fase di inizializzazione avremo y=’0’ ed
a=”00000000”. Supponiamo che, in seguito, al tempo t=t0, il vettore a abbia una transizione al
valore “11111111”. Il processo viene attivato ed i suoi statements eseguiti in sequenza. Il primo
statement y<=’1’ comporta lo scheduling dell’evento y=’1’ per il tempo t=t0+δ; il valore corrente
di y rimane ‘0’. Gli statements successivi del ciclo di for effettuano delle funzioni and fra gli
elementi a(i) ed y. Poichè il valore corrente di y è ‘0’ il risultato delle funzioni and è sempre ‘0’.
Pertanto, la transizione y=’1’ per il tempo t=t0+δ viene cancellata dalla lista degli eventi sospesi ed
y rimane a ‘0’. Seguendo le tre semplici regole enunciate poc’anzi si ha:

- Le espressioni devono essere calcolate utilizzando per y il valore all’istante in cui il processo
è stato attivato: y=’0’.
- Il segnale y viene aggiornato in base all’ultima operazione di assegnazione incontrata prima
dell’end process: y <= a(0) and y, che fornisce come risultato ‘0’.
- Il segnale y viene aggiornato alla fine del processo, quando il processo stesso viene sospeso.
In questo caso y rimane al valore ‘0’.

Lasciamo al lettore la riscrittura del listato di Fig. 7.5, per realizzare effettivamente la and fra gli
otto bit di ingresso, utilizzando una variabile di appoggio all’interno del processo.
25 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

7.2 Statements sequenziali. Gli statements sequenziali che possono apparire in un processo
sono:
- assegnazioni di valore a variabili o segnali
- if ... then
- case ... when
- for ... loop
- while ... loop

La Fig. 7.6 mostra la sintassi dello statement if ... then. Nella prima e più semplice forma, viene
controllato il valore dell’espressione booleana e vengono eseguiti degli statements sequenziali se il
valore dell’espressione è true. Nella seconda forma si aggiunge una clausola else con altri statements
sequenziali che vengono eseguiti nel caso in cui il valore dell’espressione è false. Invece di creare
degli statements if..then..else innestati, il VHDL consente di utilizzare la parola chiave elsif. Gli
statements sequenziali che seguono un elsif vengono eseguiti quando l’espressione booleana che
segue elsif è true ed inoltre tutte le espressioni booleane precedenti sono false. Infine, è possibile
specificare un’ultima clausola else, con degli statements sequenziali da eseguire nel caso in cui tutte
le espressioni booleane precedenti sono false.

if espressione_booleana
then
statements_sequenziali;
endif;

if espressione_booleana
then
statements_sequenziali;
else
statements_sequenziali;
endif;

if espressione_booleana
then
statements_sequenziali;
elsif espressione_booleana
then
statements_sequenziali;
elsif espressione_booleana
then
statements_sequenziali;
....
endif;

if espressione_booleana
then
statements_sequenziali;
elsif espressione_booleana
then
statements_sequenziali;
elsif espressione_booleana
then
statements_sequenziali;
....
else
statements_sequenziali;
endif;
Fig. 7.6. Sinstassi dello statement if
26 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

case segnale_o_variabile_di_selezione is
when caso_a =>
statements_sequenziali;
when caso_b =>
statements_sequenziali;
when caso_c =>
statements_sequenziali;
end case;

Fig. 7.7. Sinstassi dello statement case..when

Abbiamo visto un esempio di utilizzo dello statement if ... then nel listato di Fig. 7.2. In questo
esempio le espressioni booleane sono mutuamente esclusive, poichè non possono mai essere vere
contemporaneamente due o più condizioni. In applicazioni di questo tipo non è strettamente
necessario utilizzare degli statements if..then, che presuppongono una priorità fra le varie espressioni
booleane (quella che segue la parola chiave if ha maggiore priorità rispetto a quella che segue che
segue il primo then, ecc.). Quando è necessario effettuare delle scelte ed i casi possibili sono tutti
mutuamente esclusivi è più semplice utilizzare il costrutto case ... when, la cui sintassi è riportata in
Fig. 7.7.
Lo statement case..when prevede che i casi elencati debbano prevedere tutti i possibili valori
che può assumere il segnale o la variabile utilizzata per la selezione. Molto spesso, per soddisfare la
questa regola, viene utilizzata la parola chiave others nell’ultimo when, in modo simile a quanto
visto per il costrutto di assegnazione selezionata.
Un esempio di utilizzo del costrutto case..when è riportato in Fig. 7.8, che mostra una versione
alternativa dell’architettura del contatore di bit ‘1’ di Fig. 7.2.

conta: process (a)


variable cnt : integer;
begin
cnt := 0;
for i in 3 downto 0 loop
if ( a(i)='1' ) then
cnt := cnt + 1;
end if;
end loop;
case cnt is
when 0 => y <= "000";
when 1 => y <= "001";
when 2 => y <= "010";
when 3 => y <= "011";
when others => y <= "100";
end case;
end process;
end behavioral;

Fig. 7.8. Descrizione comportamentale del contatore di bit ‘1’utilizzando il costrutto case..when
27 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

for identificatore in range loop


statements_sequenziali;
end loop;
Fig. 7.9. Sinstassi del costrutto for..loop

while espressione_booleana loop


statements_sequenziali;
end loop;

Fig. 7.10. Sinstassi del costrutto for..loop

La sintassi del for...loop è mostrata in Fig. 7.9. La variabile utilizzata come indice del ciclo viene
dichiarata implicitamente nel for ed ha il tipo corrispondente al range. Per ogni iterazione del loop, la
variabile utilizzata come indice assume tutti i valori previsti dal range. Un esempio di costrutto
for..loop è mostrato in Fig. 7.8.
La Fig. 7.10 mostra la sintassi del while..loop. In questo caso l’espressione booleana è testata ad
ogni iterazione del loop ed il loop viene eseguito solo se il valore dell’espressione booleana è true.
Due statement sequenziali che possono apparire all’interno di un loop sono exit e next. Quando
viene eseguito, exit trasferisce il controllo allo statement che segue end loop. Quando viene
eseguito lo statement next, invece, vengono saltati tutti i rimanenti statements del loop e viene
iniziata una nuova iterazione del ciclo.

7.3 Sistemi con memoria. Gli esempi precedenti si riferiscono all’utilizzo di processi per
descrivere sistemi combinatori.
Il listato di Fig. 7.11 mostra la descrizione di un D-latch. Mediante un costrutto if..then si assegna
all’uscita q il valore dell’ingresso d solo quando clk=’1’. Quando clk=’0’ il segnale q mantiene il
valore precedente.
Il listato di Fig. 7.12 mostra la descrizione di un flip-flop comandato dal fronte di salita del clock,
mentre i due listati in Fig. 7.13 evidenziano le diferenze fra le descrizioni di un flip-flop con reset
asincrono e sincrono. Negli esempi di Fig. 7.12 e di Fig. 7.13 (pagina seguente) si sono utilizzate le
funzioni standard rising_edge e falling_edge; ovviamente è possibile, in alternativa, sfruttare gli
attributi ‘event e ‘stable che abbiamo incontrato nel Paragrafo 6.3.
Il listato di Fig. 7.14 mostra la descrizione di un registro ad 8 bit caricabile selettivamente e con
ingressi sia di set che di reset. Da notare il costrutto: q <= ( others => ‘0’) che consente di
inizializzare al valore ‘0’ tutti gli elementi dell’array q.

library ieee; use ieee.std_logic_1164.all;


entity d_latch is
port(d,clk : in std_logic;
q : out std_logic);
end;
architecture beh of d_latch is
begin
process (d,clk)
begin
if (clk = ‘1’) then
q <= d;
end if;
end process;
end beh;
Fig. 7.11. D latch descritto mediante un processo.
28 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


entity d_ff is
port(d,clk : in std_logic;
q : out std_logic);
end;
architecture beh of d_ff is
begin
process (clk)
begin
if rising_edge(clk) then
q <= d;
end if;
end process;
end beh;
Fig. 7.12. Flip-flop attivo sul fronte di salita del clock.

library ieee; library ieee;


use ieee.std_logic_1164.all; use ieee.std_logic_1164.all;
entity ff1 is entity ff2 is
port(d,rst,ck : in std_logic; port(d,rst,ck : in std_logic;
q : out std_logic); q : out std_logic);
end; end;
architecture beh of ff1 is architecture beh of ff2 is
begin begin
process(rst,ck) process(ck)
begin begin
if (rst=’1’) then if rising_edge(ck) then
q <= ‘0’; q <= d and rst;
elsif rising_edge(ck) then end if;
q <= d; end process;
end if;
end process;
end beh; end beh;
Fig. 7.13. Flip-flop con reset asincrono (listato di sinistra) e sincrono (listato di destra).

library ieee; use ieee.std_logic_1164.all;


entity reg16 is
port(clk, reset, set, en : in std_logic;
d : in std_logic_vector(15 downto 0);
q : out std_logic_vector(15 downto 0) );
end;
architecture beh of reg16 is
begin
process (clk,reset,set)
begin
if (reset=’1’) then
q <= ( others => ‘0’);
elsif (set=’1’) then
q <= ( others => ‘1’);
elsif rising_edge(clk) then
if (en=’1’) then
q <= d;
end if;
end if;
end process;
end beh;
Fig. 7.14. Registro ad 8 bit caricabile selettivamente e con ingressi sia di set che di reset.
29 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

process (t,g,c,d)
begin
if (g = ‘1’) then
y <= t;
elsif (c = ‘1’) then
y <= d;
end if;
end process;
end beh;

Fig. 7.15. Descrizione VHDL che implica la sintesi di un latch.

process (t,g,c,d)
begin
if (g = ‘1’) then
y <= t;
elsif rising_edge(c) then
y <= d;
end if;
end process;

Fig. 7.16. Descrizione VHDL che implica la sintesi di un flip-flop.

Fig. 7.17. Circuito sintetizzato a partire dalla descrizione VHDL di Fig. 7.16.

Una descrizione VHDL comportamentale implica una forma di memoria, e quindi comporta la
sintesi di latch o flip-flop, ogni volta che si utilizza un costrutto in cui, per qualche combinazione dei
segnali di ingresso, non si assegna valore all’uscita.
In generale, quando si devono descrivere sistemi sincroni, è buona norma utilizzare dei
semplici templates come quelli di Fig. 7.11-7.14, per rendere chiaro ed immediato il
funzionamento del sistema.
Si consideri, ad esempio, il listato di Fig. 7.15. In questo caso non si assegna valore all’uscita y
quando sia g che c sono entrambi ’0’. Si lascia al lettore la verifica che il listato può essere
sintetizzato utilizzando un multiplexer avente come segnale di selezione g ed un latch in cui il
segnale di clock è c.
Nel listato di Fig. 7.16 la condizione: (c = ‘1’) viene sostituita da: rising_edge(c). In
questo caso il circuito potrebbe essere sintetizzato mediante un multiplexer ed un flip-flop. Lo
schema di Fig. 7.17 mostra una implementazione alternativa, in cui si utilizza un flip-flop con ingressi
asincroni di preset e di clear.
30 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

1 process (a,b,c)
2 begin
3 if (c=’1’) then
4 if (a=’1’) or (b=’1’) then
5 y <=’1’;
6 else
7 y <=’0’;
8 end if;
9
10 else
11 y <=’0’;
12 end if;
13 end process;

Fig. 7.18. Descrizione comportamentale di un semplice sistema combinatorio.

Fig. 7.19. Circuitisintetizzati dal listato di Fig. 7.18. A sinistra: listato originale; Al centro:
listato in cui sono eliminate le righe 10 e 11; A Destra: listato in cui sono eliminate le righe da 6 a
11

Come ulteriore esempio, si consideri il circuito descritto dal listato di Fig. 7.18. Il circuito
corrispondente, di tipo combinatorio, è mostrato a sinistra in Fig. 7.19. Se si eliminano le due righe
10 e 11 l’uscita del sistema viene aggiornata solo quando c=1. Quando c=0 l’uscita permane al
valore precedente. In questo caso, il circuito sintetizzato è quello mostrato al centro in figura 7.19 e
prevede un latch pilotato dal segnale c. Se si eliminano dal listato di Fig. 7.18 tutte le righe dalla 6
alla 11 si assegna 1 ad y se si verifica la condizione: c and (a or b) =1, dopodiché l’uscita y resta
sempre pari ad 1. Il circuito sintetizzato in questo caso è quello mostrato a destra in figura 7.19.
Un ultimo esempio, che evidenzia nuovamente il ruolo dei segnali all’interno dei processi di cui
abbiamo già parlato nel paragrafo 7.1, è mostrato dal listato di Fig 7.20. Si lascia al lettore la verifica
della corrispondenza fra il listato ed il circuito sintetizzato mostrato in Fig. 7.21.

library ieee; use ieee.std_logic_1164.all;


entity infer2 is
port(j,k,c,clk : in std_logic;
a,h : out std_logic);
end;
architecture beh of infer2 is
signal b,i : std_logic;
begin
process (clk)
begin
if rising_edge(clk) then
b <= c;
a <= b;
h <= i;
i <= j or k;
end if;
end process;
end beh;

Fig. 7.20.
31 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Fig. 7.21. Circuito corrispondente al listato di Fig. 7.20.

Concludiamo questo paragrafo ribadendo che per descrivere sistemi sincroni è buona norma
utilizzare dei semplici templates come quelli di Fig. 7.11-7.14, evitando descrizioni VHDL meno
chiare e di interpretazione meno immediata, come quelle mostrate in Fig. 7.15 e 7.16. I programmi di
sintesi, comunque, danno delle informazioni che indicano, per ognuno dei processi, se sono sintetizzati dei
latch (o dei flip-flop) ed il numero dei dispositivi sintetizzati. Queste informazioni sono di grande aiuto per
verificare la correttezza della descrizione del sistema.

7.4 Sensitivity list incomplte. Abbiamo visto nel Paragrafo 7.1 che ogni processo entra in
esecuzione quando si ha una transizione per uno dei segnali inclusi nella sensitivity list. Diremo che
un processo ha una sensitivity list completa quando la sensitivity list include tutti i segnali che si
trovano a secondo membro di statements di assegnazione; negli altri casi si parla di sensitivity list
incompleta.
Si consideri ad esempio il listato di Fig 7.22. Il processo p1 ha una sensitivity list completa e
rappresenta una porta or a tre ingressi. Per il processo p2, invece, la sensitivity list è incompleta,
poichè il segnale c non è incluso nella sensitivity list, pur comparendo a secondo membro nello
statement di assegnazione per il segnale y. Il processo p2 non è sintetizzabile, in quanto non è chiaro
come realizzare un circuito per il quale una transizione del segnale c non non modifica l’uscita y,
mentre una transizione di a o di b comporta che l’uscita y sia uguale alla or dei tre segnali a, b, c.
E’ da notare che alcuni programmi di sintesi effettuano un controllo della sensitivity list dei
processi e forniscono pertanto un messaggio di errore quando si incontra un processo come quello di
Fig. 7.22; altri programmi di sintesi, invece, non effettuano il controllo della sensitivity list,
assumendo che ogni processo abbia sempre una sensitivity list completa. In questo caso, la sintesi del
processo p2 del listato di Fig. 7.22 fornisce anch’essa una or a tre ingressi, come per il processo p1.

p1 : process (a,b,c)
begin
y <= a or b or c;
end process;

p2 : process (a,b)
begin
y <= a or b or c;
end process;

Fig. 7.22. Sensitivity list incompleta.


32 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

p1 : process
begin
y <= a or b or c;
wait on a,b;
z <= a xor b xor c;
wait on a,b,c;
end process;

Fig. 7.23. Wait statement.

7.5 Lo statement wait. L’esecuzione di un processo può essere sospesa utilizzando uno
statement wait. Si consideri ad esempio il listato di Fig. 7.23. Il processo non ha alcuna sensitivity
list. In fase di inizializzazione il processo viene eseguito una prima volta, per poi sospendersi quando
viene incontrato il primo statement wait on. Quando uno dei segnali elencati dopo il wait on ha un
evento, il processo riprende la sua esecuzione, eseguendo la prima istruzione successiva al wait che
aveva causato la sospensione.
Un processo che utilizza lo statement wait non può avere una sensitivity list. La presenza di una
sensitivity list, infatti, implica che l’esecuzione del processo parta dalla prima istruzione successiva al
begin, mentre la presenza di un wait implica la ripresa dell’esecuzione di un processo in un punto
predeterminato.
I programmi di sintesi non supportano lo statement wait, se non in alcune forme molto
semplificate. Si consideri ad esempio la Fig. 7.24 che mostra l’utilizzo dello statement wait per
descrivere un flip-flop D. In questo caso si utilizza il costrutto wait until per sospendere
l’esecuzione del processo fin quando la condizione che segue il wait until non è verificata.

ff : process
begin
wait until rising_edge(clk)
q <= d;
end process;

Fig. 7.24. Flip-flop D descritto mediante uno statement wait.

8. Descrizioni architetturali “miste”: strutturali, data-flow e comportamentali.

Abbiamo finora studiato separatamente i tre stili di descrizione: comportamentale, data-flow e


strutturale. In effetti il VHDL è un linguaggio estememente flessibile, e consente di utilizzare
liberamente tutti e tre gli stili in una stessa architettura. Ad esempio si consideri il listato di Fig. 8.1
che mostra un’ennesima versione del contatore di bit ‘1’ in cui si utilizza un componente (and4,
definito nelle linee da 2 a 5 ed istanziato nelle linee da 8 a 11), il costrutto when..else per calcolare il
bit y(1) ed un processo per il bit y(0).
33 Corso di Architettura dei Sistemi Integrati. Note sul VHDL
1 architecture mix of one_counter is
2 component AND4
3 port (I0, I1,I2,I3 : in std_logic;
4 O : out std_logic);
5 end component;
6 signal tmp : boolean;
7 begin
8 gate1 : AND4 port map(
9 I0 => a(0), I1 => a(1),
10 I2 => a(2), I3 => a(3),
11 O => y(2) );
12
13 y(1) <= ‘1’ when (a="0111") or (a="1011")
or (a="1101") or (a="1110")
14 else ‘0’;
15 dispari : process (a)
16 variable d : boolean;
17 begin
18 d := false;
19 for i in 3 downto 0 loop
20 if ( a(i)=’1’ )
21 then
22 d := not d;
23 end if;
24 end loop;
25 if (d) then
26 y(0) <= ‘1’;
27 else
28 y(0) <= ‘0’;
29 end if;
30 end process;
31 end mix;

Fig. 8.1. Descrizione del contatore di bit ‘1’ con utilizzo di costrutti strutturali, data-flow e
comportamentali.
34 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

9. Tipi, sottotipi ed attributi.

Negli esempi precedenti sono stati introdotti alcuni dei tipi predefiniti in VHDL: bit, bit_vector,
boolean ed integer. Il VHDL consente di introdurre tipi definiti dall’utente.

9.1. Tipi enumerati. Un tipo enumerato è definito elencandone tutti i possibili elementi, come
mostra la Fig. 9.1. Gli elementi possono essere sia degli identificatori definiti dall’utente, sia singoli
caratteri racchiusi fra apici. Un esempio di tipo enumerato è il seguente:

type stato_processore is (reset, stop, wait, go);

Un tipo enumerato è ordinato. Il primo elemento è il più piccolo, mentre l’ultimo è il più grande.
Nell’esempio precedente abbiamo: reset < stop < wait < go.
Lo standard IEEE 1164 definisce due tipi enumerati di utilizzo comune. La Fig. 9.2 riporta la
definizione del tipo std_ulogic. Il tipo include oltre a i due valori ‘0’ ed ‘1’ altri sette valori che
possono essere molto utili per descrivere e simulare sistemi digitali. Per la sintesi di circuiti digitali
i valori utilizzabili sono ‘0, ‘1’, ‘-‘ e ‘Z’.
Il valore ‘-‘ consente di specificare condizioni dont’care, permettendo al sintetizzatore di
ottimizzare il circuito.
Il valore ‘Z’ consente invece di specificare una condizione di alta impedenza, in modo da poter
sintetizzare circuiti tristate. Un esempio è dato dal listato di Fig. 9.3, cui corrisponde il circuito di
Fig. 9.4.

type nome_tipo is (elemento, elemento, ....elemento);

Fig. 9.1. Sintassi della definizione di tipi enumeratii.

TYPE std_ulogic IS ( 'U', -- Uninitialized


'X', -- Forcing Unknown
'0', -- Forcing 0
'1', -- Forcing 1
'Z', -- High Impedance
'W', -- Weak Unknown
'L', -- Weak 0
'H', -- Weak 1
'-' -- Don't care );

Fig. 9.2. Definizione del tipo std_ulogic della libreria standard IEEE 1164 .

library ieee;
use ieee.std_logic_1164.all;
entity dc2 is
port (a, b, en : in std_ulogic;
y : inout std_ulogic);
end dc2;
architecture data_flow of dc2 is
begin
y <= (a and b) when en ='1'
else 'Z';
end;

Fig. 9.3. Utilizzo del tipo std_ulogic per sintetizzare circuiti tristate.
35 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

EN
AND2
B Y
A BUFE

Fig. 9.4. Circuito sintetizzato a partire dal listato di Fig. 9.3.

a b driver 1 driver 2 y
a AND b a OR b
0 0 0 0 0
0 1 0 1 X
1 0 0 1 X
1 1 1 1 1

Fig. 9.5. Esempio di Tabella della verità per risolvere segnali con due driver

Negli esempi del capitolo precedente abbiamo utilizzato il tipo std_logic della libreria IEEE,
invece del tipo std_ulogic. Il tipo std_logic unisce una opportuna funzione di risoluzione (resolution
function) al tipo std_ulogic. Per introdurre il concetto di funzione di risoluzione, si consideri il
seguente frammento di codice:

architecture esempio of my_design is


begin
y <= a and b;
y <= a or b;
end esempio;

Due statements concorrenti di assegnazione definiscono in questo caso due drivers per il segnale
y. Per stabilire il valore da assegnare all’uscita y è necessario definire una funzione di risoluzione che,
a partire dai valori dei due drivers, assegni un opportuno valore al segnale y. Ad esempio, la funzione
di risoluzione può essere definita in accordo alla Tabella di Fig. 9.5, secondo cui il valore di y è pari a
‘X’ (indefinito) se i due drivers forniscono uscite contrastanti.
In definitiva, l’introduzione di una funzione di risoluzione consente di simulare descrizioni VHDL
in cui più drivers pilotano lo stesso segnale. D’altro canto, se l’obiettivo è quello di sintetizzare un
circuito, l’utilizzo del tipo std_logic è del tutto equivalente a quello del tipo std_ulogic. Si noti che,
nell’ottica della sintesi, il frammento di listato precedente è errato, in quanto presuppone la
possibilità di poter pilotare uno stesso segnale di uscita con due porte logiche differenti.
36 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

type nome_tipo is array (start to end) of tipo_elemento;


type nome_tipo is array (start downto end) of tipo_elemento;
type nome_tipo is array (tipo_indice) of tipo_elemento;
type nome_tipo is array (tipo_indice range start to end) of tipo_elemento;
type nome_tipo is array (tipo_indice range start downto end) of tipo_elemento;
Fig. 9.6. Sintassi della definizione di array.

constant size : integer := 4;


type word is array (size-1 downto 0) of std_logic;
type memoria is array (63 downto 0) of word;
Fig. 9.7. Esempi di array.

9.2. Array. Il VHDL consente di definire degli array che, come abbiamo già avuto modo di
vedere, sono un insieme ordinato di elementi cui è possibile accedere mediante indici. La sintassi di
una dichiarazione di array è mostrata in Fig. 9.6.
Nei primi due casi di Fig. 9.6 l’indice è implicitamente di tipo intero. Nelle altre tre versioni
l’indice deve appartenere ad un tipo enumerato o ad un suo sottoinsieme.
Due esempi di array sono riportati in Fig. 9.7. Si noti nel secondo esempio la preventiva
definizione di una costante, di tipo intero, utilizzata per rendere più leggibile e più facilmente
modificabile il codice. Il terzo esempio mostra un esempio di array bidimensionale.
Per accedere agli elementi di un segnale o di una variabile di tipo array, come abbiamo già visto in
molteplici esempi, è sufficiente utilizzare il nome del segnale, o della variabile, con gli indici indicati
fra parentesi. Ad esempio, se a è un segnale del tipo byte definito in Fig. 9.7 è possibile utilizzare le
assegnazioni seguenti:

a(7) <= ‘0’; a(6) <= ‘1’; a(5) <= ‘1’; a(4) <= ‘1’;
a(3) <= ‘0’; a(2) <= ‘1’; a(1) <= ‘1’; a(0) <= ‘1’;

In alternativa, gli elementi di a possono essere elencati fra parentesi:


a <= (‘0’, ‘1’, ‘1’, ‘1’, ‘1’, ‘1’, ‘1’, ‘0’);

E’ inoltre possibile effettuare un’assegnazione specificando i gli indici ed i corrispondenti valori


come nell’esempio seguente:
a <= (7 => ‘0’, 0 => ‘0’, others => ‘1’);

Infine, è possibile utilizzare delle stringhe:


a <= “01111110”;

Per le stringhe, oltre alla notazione binaria, è possibile utilizzare la notazione esadecimale:
a <= X“77”;

E’ possibile far riferimento ad una parte (slice) di un array specificando il valore iniziale e finale
degli indici:

a (7 downto 4) <= X“7”;

Da notare che la direzione (downto oppure to) della slice deve essere la stessa utilizzata nella
definizione dell’array.
La Fig. 9.8 (pagina seguente) mostra un esempio di utilizzo di un array bidimensionale per
realizzare una funzione combinatoria assegnata mediante una tabella.
37 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;

entity tabella is
port( x : in std_logic_vector(2 downto 0);
y : out std_logic_vector(1 downto 0) );
end;

architecture beh of tabella is

type tab is array (7 downto 0)


of std_logic_vector (4 downto 0);

constant tt : tab := (
"00000",
"00101",
"01001",
"01110",
"10001",
"10110",
"11010",
"11111" );

begin

lookup : process(x)
begin

for i in 0 to 7 loop
if ( x = tt(i)(4 downto 2) ) then
y <= tt(i)(1 downto 0);
end if;
end loop;

end process;

end beh;

Fig. 9.8. Utilizzo di un array bidimensionale per realizzare una funzione combinatoria.

9.3. Array uncostrained. Il VHDL consente di definire dei vettori in cui dimensioni non sono
specificate (array uncostrained). Un esempio è il seguente, tratto dalla libreria ieee 1164:

type std_logic_vector is array (natural range <>) of std_logic;

in cui il tipo natural comprende tutti gli interi maggiori o uguali a zero. Array di dimensioni non
specificate sono utilizzati, ad esempio, per definire funzioni e procedure in grado di operare con
parametri di dimensioni generiche. Vedremo fra breve un esempio applicativo.
38 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

9.4. Sottotipi. In VHDL è necessario utilizzare delle funzioni di conversione di tipo ogni volta
che si effettuano operazioni su argomenti di tipo differente. Si consideri, ad esempio, il listato di Fig.
9.9. In linea 8 viene definito un tipo denominato nibble ed in linea 9 sono introdotti due segnali
di tipo nibble. Il tentativo di compilazione del listato fornisce due errori in corrispondenza delle linee
13 e 14, in quanto il tipo dei segnali h ed l è differente dal tipo del segnale address.
Gli errori di compilazione per il listato di Fig. 9.9 possono essere evitati definendo nibble come
un sottotipo. Un sottotipo rapresenta un opportuno sottoinsieme di un tipo base; la sintassi della
definizione di un sottotipo è mostrata in Fig. 9.10. Con un oggetto di un determinato sottotipo è
possibile effettuare tutte le operazioni previste per il tipo base ed è inoltre possibile definire
ulteriori proprietà. Il VHDL prevede che sia possibile definire un sottotipo o come un sottoinsieme
dei possibili valori di un tipo scalare oppure per specificare un range di un array uncostrained.
Gli errori del listato di Fig. 9.9 possono quindi essere evitati sostituendo la linea 8 con la
seguente:
subtype nibble is array (3 downto 0) of std_logic;

Come ulteriore esempio di sottotipi, si consideri il listato di Fig. 9.11, che riprende la descrizione
di Fig. 7.8 per il contatore di bit ‘1’. In questo caso, la variabile cnt è stata definita nell’ambito del
sottoinsieme dei numeri interi compresi fra 0 e 3.

1 library ieee; use ieee.std_logic_1164.all;


2 entity compare is
3 port( address : in std_logic_vector(7 downto 0);
4 x1,x2 : out std_logic);
5 end;
6 architecture beh of compare is
7
8 type nibble is array (3 downto 0) of std_logic;
9 signal h,l : nibble;
10
11 begin
12
13 h <= address(7 downto 4);
14 l <= address(3 downto 0);
15
16 x1 <= '1' when h < l else '0';
17 x2 <= '1' when h = l else '0';
18
19 end beh;

Fig. 9.9. Utilizzo scorretto di tipi differenti (nibble ed std_logic_vector).

subtype nome_sottotipo is nome_tipo_scalare start to end;


subtype nome_sottotipo is nome_tipo_scalare start downto end;
subtype nome_sottotipo is nome_tipo_array_uncostrained (start to end);
subtype nome_sottotipo is nome_tipo_array_uncostrained (start downto end);

Fig. 9.10. Sintassi della definizione di sottotipi.


39 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

1 conta: process (a)


2 subtype conteggio is integer range 0 to 7
3 variable cnt : conteggio;
4 begin
5 cnt := 0;
6 for i in 3 downto 0 loop
7 if ( a(i)='1' ) then
8 cnt := cnt + 1;
9 end if;
10 end loop;
11 case cnt is
12 when 0 => y <= "000";
13 when 1 => y <= "001";
14 when 2 => y <= "010";
15 when 3 => y <= "011";
16 when others => y <= "100";
17 end case;
18 end process;
19 end behavioral;

Fig. 9.11. Utilizzo di un sottotipo nel contatore di bit ‘1’ di Fig. 7.8.

9.5. Il tipo intero. Il tipo integer ed i corrispondenti operatori relazionali ed aritmetici sono
predefiniti in VHDL; esso include tutti i numeri interi compresi fra -(231-1) e 231-1.
In una descrizione VHDL orientata alla sintesi, i segnali e le variabili di tipo integer dovrebbe
sempre essere costretti all’interno di un range predefinito, come mostrato nel listato di Fig. 9.11. Si
noti che in questo esempio le due linee 2 e 3 possono essere ricondotte ad una sola:
variable cnt : integer range 0 to 3;

Il VHDL ha due sottotipi interi predefiniti:

subtype natural is integer range 0 to higest_integer;


subtype positive is integer range 1 to higest_integer;

dove higest_integer è pari a: 231-1 = 2.147483647.


40 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

type count is integer range 0 to 127;


type word is array (15 downto 0) of std_logic;

count’left 0
word’left 15
count’right 127
word’right 0
count’high 127
word’high 15
count’low 0
word’low 0
count’length 128
word’ length 16
count’range 0 to 127
word’range 15 downto 0

Fig. 9.12. Attributi.

9.6. Attributi. In VHDL un attributo fornisce dati relativi a tipi, sottotipi, segnali ecc. Due
importanti attributi di un segnale sono ‘event e ‘stable, che abbiamo già utilizzato nei capitoli 6.3 e
7.3 per individuare i fronti del segnale di clock.
Il VHDL consente di utilizzare altri attributi al fine di ottenere la lunghezza di un array o il valore
minimo e massimo dell’indice. In particolare: l’attribito ‘left fornisce l’elemento più a sinistra (il
primo di un tipo enumerato), mentre ‘right fornisce l’elemento più a destra (l’ultimo di un tipo
enumerato). Per sottotipi integer range l’attribito ‘high fornisce l’intero più grande del range, ‘low
fornisce l’intero più piccolo e ‘range il range dell’indice. Infine l’attributo ‘length fornisce il numero
di elementi di un array. La Fig. 9.12 mostra la definizione di due tipi ed i valori corrispondenti forniti
dagli attributi.

10. Funzioni e Procedure.

10.1. Funzioni. Una funzione in VHDL, come in qualsiasi altro linguaggio di programmazione,
accetta in ingresso uno o più argomenti e fornisce in uscita un risultato. La sintassi di una definizione
di funzione è mostrata in Fig 10.1 (pagina seguente)1. I parametri formali della funzione sono
segnali di un tipo specificato. I parametri non possono essere modificati quando viene eseguita la
funzione, pertanto il modo dei parametri formali di una funzione deve essere in. La funzione viene
chiamata specificando i valori effettivi dei parametri, che devono ovviamente essere dello stesso tipo
dei parametri formali. Come mostra la Figura 10.1, all’interno di una funzione è possibile definire
tipi, costanti, variabili ed altre funzioni. Le grandezze definite all’interno di una funzione sono tutte
locali.
Si noti che ogni funzione può fornire in uscita un solo argomento.
L’algoritmo implementato dalla funzione è descritto mediante statements sequenziali, gli stessi
che abbiamo incontrato in precedenza quando abbiamo parlato dei processi VHDL. L’unico
statement aggiuntivo è:

return (valore);

che ha lo scopo di stabilire il valore fornito in uscita dalla funzione.


1
Il VHDL-93 consente, facoltativamente, di di inserire la keyword function fra end ed il nome della funzione.
41 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

function nome_funzione (
nome_parametro : in tipo_ parametro;
.....
nome_ parametro : in tipo_ parametro)
return tipo_risultato is

dichiarazioni_di_tipo
dichiarazioni_di_costanti
dichiarazioni_di_variabili
dichiarazioni_di_funzioni
begin
statement_sequenziale;
....
statement_sequenziale;
end nome_funzione;
Fig. 10.1. Sintassi di una funzione VHDL.

La Fig. 10.2 mostra l’utilizzo di una funzione di uso generale per il calcolo della somma di due
vettori di tipo std_logic. Si noti che i parametri formali della funzione sono array uncostrained;
all’interno della funzione si utilizzano gli attributi ‘low, ‘high, ‘range per accedere agli elementi dei
vettori. La funzione viene richiamata due volte in due statements concorrenti.

library ieee; use ieee.std_logic_1164.all;


entity s is
port(p1,q1 : in std_logic_vector (7 downto 0);
p2,q2 : in std_logic_vector (3 downto 0);
y1 : out std_logic_vector (7 downto 0);
y2 : out std_logic_vector (3 downto 0));
end;

architecture beh of s is

function add (a,b : in std_logic_vector)


return std_logic_vector is
variable s : std_logic_vector(a'range);
variable carry : std_logic;
begin
carry:='0';
for i in a'low to a'high loop
s(i) := a(i) xor b(i) xor carry;
carry := (a(i) and b(i)) or
(carry and a(i)) or
(carry and b(i));
end loop;
return s;
end add;

begin
y1 <= add(p1,q1);
y2 <= add(p2,q2);
end beh;

Fig. 10.2. Utilizzo di una funzione in VHDL.


42 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

procedure somma (
a,b : in std_logic_vector;
y : out std_logic_vector;
cout : out std_logic) is
variable carry : std_logic;
begin
carry:='0';
for i in a'low to a'high loop
y(i) := a(i) xor b(i) xor carry;
carry := (a(i) and b(i)) or (carry and a(i))
or (carry and b(i));
end loop;
cout := carry;
end somma;

Fig. 10.3. Esempio di procedura VHDL.

10.2. Procedure. Una procedura in VHDL è molto simile ad una funzione. La differenza
principale consiste nel fatto che una procedura può fornire in uscita più di un valore; a tal fine ai
parametri formali di una procedura è possibile assegnare un modo out oppure inout oltre che in.
Una procedura può essere richiamata sia all’interno di un processo, sia con uno statement
concorrente.
Il VHDL impone che i parametri formali di modo out ed inout di una procedura debbano avere
la stessa classe dei parametri effettivi. In altre parole, se il parametro effettivo è una variabile, anche
il parametro formale della procedura deve essere definito come variable; analogamente, se il
parametro effettivo è un segnale, anche il parametro formale della procedura deve essere definito
come signal. La classe di default, assunta se non c’è una dichiarazione esplicita nella procedura, è
variable.
La Fig. 10.3 mostra un esempio di procedura che calcola la somma di due addendi ed inoltre
fornisce, come ulteriore uscita, un bit di riporto.
Si noti che nel listato di Fig. 10.3 i parametri formali di uscita y e cout non siano definiti come
signal, e sono dunque variabili. All'interno della procedura il simbolo di assegnazione è, pertanto,
:=
La procedura di Fig. 10.3 può essere utilimente richiamata all'interno di un processo, con
parametri effettivi di classe variabile, e non signal.
43 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

11. Librerie e packages.

Le librerie ed i packages sono utilizzati per dichiarare e memorizzare tipi, funzioni, componenti
ecc. che possono essere adoperati in una descrizione VHDL.

11.1. Librerie. Quando viene compilato un listato VHDL i files prodotti (che verranno utilizzati
in fase di simulazione o di sintesi) vengono memorizzati in una opportuna libreria. Un elemento di
progetto (ad esempio un componente) compilato in una libreria può essere utilizzato in altri listati
VHDL che fanno riferimento alla stessa libreria. In questo modo un intero progetto VHDL può
essere facilmente suddiviso in più listati che fanno riferimento ad una libreria comune. Molto spesso
(ma non necessariamente) il nome della libreria corrisponde con il nome di una directory in cui
vengono salvati i file prodotti in fase di compilazione. Se il nome della libreria non viene specificato
esplicitamente, il compilatore crea ed utilizza una libreria di default chiamata work.
Ad esempio, la descrizione strutturale del contatore di bit ‘1’ del Capitolo 5 può essere suddivisa
in tre files: il primo contiene la descrizione strutturale di un half-adder (listato di Fig. 4.7), il secondo
la descrizione strutturale di un full-adder (listato di Fig. 4.6), mentre il terzo file contiene la
descrizione del contatore di bit ‘1’.
Per specificare le librerie che vengono utilizzate in un listato VHDL è necessario utilizzare la
clausola library. Ad esempio, specificando in un listato VHDL la clausola:

library ieee;

si informa il compilatore che si utilizzeranno degli elementi di progetto compilati nella libreria di
nome ieee (che contiene le definizioni dei tipi std_logic, std_ulogic e dei relativi operatori). La
clausola: library work; viene inclusa implicitamente all’inizio di ogni listato VHDL.

11.2. Packages. Dopo aver specificato una libreria è possibile accedere alle entità ed alle
architetture compilate nella libreria, ma non alle definizioni di tipi, costanti, funzioni ecc. Per questo
scopo è necessario utilizzare dei packages e la clausola use.
Un package è un file che contiene descrizioni di oggetti che possono essere utilizzati in altri
programmi. Gli oggetti che possono essere definiti in un package sono: segnali, tipi, costanti,
funzioni, componenti e procedure. Ogni oggetto definito in un package è globale, nel senso che
può essere usato in ogni entità VHDL che utilizza quel package. In un listato VHDL, per utilizzare
gli oggetti definiti in un package è necessario introdurre una clausola use all’inizio del listato; ad
esempio:
use ieee.std_1164.all;

In generale, per utilizzare un oggetto definito in un package compilato in una data libreria è
necessario utilizzare la sintassi seguente:

use nome_libreria.nome_package.nome_oggetto;

La parola chiave all consente di rendere visibili tutti gli oggetti definiti nel packge.
Come mostra la Fig. 11.1 (pagina seguente), un package è costituito da due parti: una parte
dichiarativa ed un package body1. La parte dichiarativa è utilizzata per dichiarare quali sono gli
oggetti contenuti nel package; il package body descrive le funzioni e le procedure presenti nel
package.
Un esempio di package è riportato nei listati di Figura 11.2(a) e 11.2(b) (pagine seguenti).

1
Il VHDL-93 consente, facoltativamente, di di inserire la keyword package fra end ed il nome del package.
44 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

package nome_package is

dichiarazioni_di_tipo
dichiarazioni_di_sottotipo
dichiarazioni_di_costanti
dichiarazioni_di_segnali
dichiarazioni_di_funzioni
dichiarazioni_di_procedure
dichiarazioni_di_componenti

end nome_package;
package body nome_package is

definizioni_di_funzioni
definizioni_di_procedure

end nome_package;
Fig. 11.1. Struttura di un package VHDL.
45 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all; package body ArithFunz is


package ArithFunz is function add (a,b: in std_logic_vector)
return std_logic_vector is
constant size : integer := 8; variable s : std_logic_vector(a'range);
subtype byte is std_logic_vector variable carry : std_logic;
(size - 1 downto 0); begin
subtype nibble is std_logic_vector carry:='0';
(size/2 - 1 downto 0); for i in a'low to a'high loop
s(i) := a(i) xor b(i) xor carry;
function add (a,b:in std_logic_vector) carry := (a(i) and b(i)) or
return std_logic_vector; (carry and a(i)) or
(carry and b(i));
function add (a : in std_logic_vector; end loop;
b:in std_logic) return s;
return std_logic_vector; end add;
function inc (a : in std_logic_vector) function add (a : in std_logic_vector;
return std_logic_vector; b : in std_logic)
function sub (a,b: in std_logic_vector) return std_logic_vector is
return std_logic_vector; variable s : std_logic_vector(a'range);
variable carry : std_logic;
function sub (a : in std_logic_vector; begin
b:in std_logic) carry:=b;
return std_logic_vector; for i in a'low to a'high loop
s(i) := a(i) xor carry;
function dec (a : in std_logic_vector) carry := carry and a(i);
return std_logic_vector; end loop;
function "+" (a,b : in std_logic_vector) return s;
return std_logic_vector; end add;
function inc (a : in std_logic_vector)
end ArithFunz; return std_logic_vector is
begin
Fig. 11.2 (a). Parte dichiarativa di return add (a,'1');
un package VHDL. end inc;
function sub (a,b:in std_logic_vector)
return std_logic_vector is
variable tmp : std_logic_vector (a'range);
begin
tmp := add(a, not(b));
return inc (tmp);
end sub;
function sub (a: in std_logic_vector;
b : in std_logic)
return std_logic_vector is
variable tmp : std_logic_vector (a'range);
begin
if (b='0')
then return a;
else
tmp := (others => '1');
return add(a,tmp);
end if;
end sub;
function dec (a : in std_logic_vector)
return std_logic_vector is
variable tmp : std_logic_vector (a'range);
begin
return sub (a,'1');
end dec;
function "+" (a, b : in std_logic_vector)
return std_logic_vector is
begin
return add (a,b);
end "+";
end ArithFunz;
Fig. 11.2 (b). Package body.
46 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Analizziamo in maggior dettaglio il package denominato ArithFunz, riportato in Fig. 11.2.


Nalla parte dichiarativa del package vengono definiti: una costante di tipo integer e due sottotipi,
denominati byte e nibble. Vengono inoltre definite le funzioni denominate: add, inc, sub, dec, “+”.
Si puo’ notare che la funzione add è in realtà definita due volte; una prima volta con operandi di
tipo std_logic_vector; una seconda volta con un operando std_logic_vector ed un operando
std_logic. In questo caso si effettua un overloading della funzione: il compilatore utilizzera l’una o
l’altra definizione della funzione add, a seconda dei parametri effettivi con cui la funzione verrà
richiamata. Il lettore noterà come nel listato di Fig. 11.2 sia “overloaded” anche la funzione sub.
Una nota particolare merita la funzione “+”. L’operatore “+” è infatti un operatore predefinito del
VHDL per i tipi integer e real. Il VHDL consente di effettuare un overloading anche dei suoi
operatori predefiniti: in questo modo sarà possibile, con l’ausilio del package di Fig. 11.2, utilizzare
l’operatore “+” non solo per sommare numeri interi o reali, ma anche segnali o variabili di tipo
std_logic_vector.
Il package body, riportato in Fig. 11.2(b), descrive le funzioni introdotte nella parte dichiarativa
del package.
La prima versione della funzione add (che opera con due parametri di tipo std_logic_vector)
è quella già utilizzata nel listato di Fig. 10.2. La seconda versione della funzione add è più sempice,
in quanto il secondo operando non è un vettore ma un singolo elemento di tipo std_logic.
Come mostra la Fig. 11.2(b), le altre funzioni del package body sono estremamente semplici, in
quanto utilizzano le due funzioni add definite in precedenza.

Una volta compilato in una opportuna libreria, il package ArithFunz di Fig. 11.2 consente di
descrivere in maniera semplice e compatta operazioni aritmetiche fra operandi di tipo
std_logic_vector. Un esempio di utilizzo è dato dal listato di Fig. 11.3, in cui si suppone che il
package ArithFunz sia stato compilato in una libreria denominata mat.
Le due linee:
library mat;
use mat.ArithFunz.all;

consentono di accedere a tutti gli oggetti (costanti, tipi, funzioni) definiti nel package di Fig. 11.2.
Nella entity del listato di Fig. 11.3 è così possibile utilizzare i sottotipi byte e nibble per i ports
di ingresso e di uscita, mentre operazioni di sottrazione incremento e decremento vengono effettuate
richiamando le funzioni sub, inc, dec. Per l’addizione, oltre alla funzione add è possibile
utilizzare anche l’operatore “+”.

library ieee; use ieee.std_logic_1164.all;

library mat;
use mat.ArithFunz.all;

entity test_funz is port (a1,a2 : in nibble;


a3,a4 : in byte; y1,y2,y3,y4 : out nibble;
q : out byte);
end test_funz;
architecture bb of test_funz is
begin
y1 <= add (a1,a2);
y2 <= sub (a1,a2);
y3 <= inc (a1);
y4 <= dec (a1);
q <= a3 + a4;
end bb;
Fig. 11.3. Utilizzo del package descritto in Fig. 11.2.
47 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

12. Librerie standard.

Da quanto abbiamo visto nel capitolo precedente, è facile intuire come sia utile disporre packages
con definizioni di funzioni e tipi di uso generale, da poter riutilizzare numerose volte in diversi
progetti.
La quasi totalità dei simulatori e compilatori VHDL supportano dei packages standard, il cui
utilizzo consente di semplificare notevolmente la descrizione e la sintesi di sistemi digitali. L’utilizzo
di packages stadard, invece di packages “fatti in casa” come quello mostato nel listato di Fig. 11.2, è
fortemente consigliabile per due motivi. In primo luogo, nella definizione di funzioni e procedure di
uso generale si deve porre particolare attenzione nel considerare tutti i possibili casi che possono
verificarsi per i parametri di ingresso (cosa accade all’operatore “+” del listato di Fig. 11.2 se il
secondo operando è un vettore di dimensioni differenti rispetto al primo?), cosa che consiglia
senz’altro l’utilizzo di procedure ben documentate e testate. In secondo luogo, i tools di sintesi sono
in grado di identificare, all’interno di una descrizione VHDL che utilizza dei package standard, dei
costrutti noti cui corrispondono dei sottosistemi ben definiti (contatori up/down, addizionatori,
moltiplicatori con e senza segno ecc.). Il sintetizzatore può così far corrispondere a questi costrutti
delle macro hardware ottimizzate in termini di area occupata o di velocità. Ad esempio, ad un
operatore che effettua la somma di due vettori potrà corrispondere nel circuito sintetizzato una
macrocella carry-ripple oppure carry-lookahead. Viceversa, dalla sintesi di una descrizione come
quella dei listati di Fig. 11.2 e 11.3 corrisponderanno un insieme di porte logiche elementari
opportunamente interconnesse che, pur realizzando la stessa operazione di somma, non saranno
ottimizzate nè per quanto riguarda il tempo di propagazione, nè per l’occupazione di area.

Nel seguito si riportata una sommaria disamina dei tipi, delle funzioni e degli operatori definiti in
alcuni dei packages che compongono la libreria standard ieee_1164 che è di largo uso nella pratica.

12.1. Il package std_logic_1164. Negli esempi dei capitoli precedenti abbiamo già utilizzato un
package standard della libreria IEEE: il package std_logic_1164 in cui, oltre ad introdurre i tipi
std_ulogic ed std_logic, si definiscono una serie di funzioni e procedure standard.
La Fig. 12.1 mostra le dichiarazioni di due funzioni appartenenti al package std_logic_1164, utili
per convertire un segnale nel tipo std_logic_vector. Si noti che la funzione To_StdLogicVector è
overloaded con parametri formali di tipo bit_vector e std_ulogic_vector.

function To_StdLogicVector ( b : bit_vector )


return std_logic_vector is
....

function To_StdLogicVector ( b : std_ulogic_vector )


return std_logic_vector is
....

Fig. 12.1. Una funzione di conversione della libreria IEEE 1164.


48 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Oltre alla funzione To_StdLogicVector, nel package std_logic_1164 sono definite altre funzioni
di conversione: To_BitVector (che consente di convertire da sdt_logic_vector e sdt_ulogic_vector
in bit_vector), To_StdUlogicVector (che consente di convertire da sdt_logic_vector e bit_vector in
sdt_ulogic_vector), To_Bit (che consente di convertire da sdt_logic e sdt_ulogic in bit),
To_StdUlogic (che consente di convertire da bit in sdt_ulogic).
Il package std_logic_1164 include inoltre le funzioni rising_edge e falling_edge, che abbiamo
incontrato in precedenza, e la funzione Is_X che, applicata ad un argomento di tipo sdt_logic_vector
o sdt_ulogic_vector, fornisce il valore true se almeno uno degli elementi del vettore è indefinito.
Nel package sono inoltre overloaded gli operatori not, and, or ecc. che possono pertanto essere
adoperati indifferentemente con tipi bit, std_logic e std_ulogic.

12.2. Il package std_logic_arith. Un altro standard della libreria IEEE è il package


std_logic_arith, che definisce due tipi: signed ed unsigned ed un insieme di funzioni aritmetiche, di
comparazione e di conversione per i due tipi.
La definizione dei due tipi è la seguente:

type unsigend is array (natural range <>) of STD_LOGIC;


type signed is array (natural range <>) of STD_LOGIC;

Sia il tipo signed che l’unsigned, come il tipo std_logic_vector sono quindi array uncostrained di
std_logic.
Il tipo unsigned viene introdotto per rappresentare numeri positivi in base 2; il bit più significativo
per una variabile o un segnale di tipo unsigned è quello più a sinistra.
Il tipo signed prevede una rappresentazione in complementi alla base. Il bit più a sinistra per una
variabile o un segnale di tipo unsigned rappresenta il bit segno.

Gli operatori relazionali (>, <, <=, >=, =, \=) sono definiti differentemente per i tipi
unsigned, signed ed std_logic_vector. La Tabella di Fig. 12.2, ad esempio, mostra il risultato
dell’applicazione di operatori relazionali a argomenti di tipo diverso.
Nel caso di argomenti di tipo std_logic_vector se i due argomenti hanno uguale numero di bit il
risultato dell’operazione di comparazione è quello che si avrebbe considerando i due operandi come
interi senza segno. Se le lunghezze dei due operandi sono differenti la comparazione viene fatta
elemento per elemento, partendo da quello più a sinistra.
Nel caso di argomenti di tipo signed od unsigned il risultato della comparazione è di tipo
aritmetico e rispecchia il valore rappresentato dai due operandi. Nel caso di operandi unsigned viene
effettuata una estensione del segno se uno dei due operandi ha un numero di elementi inferiore
all’altro.

Argomento 1 operatore Argomento 2 Unsigned Signed std_logic_vector


“000” = “000” true true true
“00” = “000” true true false
“100” = “0100” true false false
“00” < “000” false false true
“100” < “0100” false true false

Fig. 12.2. Operatori relazionali applicati a argomenti di tipo unsigned, signed ed


std_logic_vector.
49 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity cp is
port( s : in std_logic_vector (7 downto 0);
s_big, s_neg : out std_logic );
end;
architecture beh of cp is
begin
s_big <= '1' when ( unsigned(s) > 200 ) else '0';
s_neg <= '1' when ( signed(s) < 0 ) else '0';
end beh;

Fig. 12.3. Utilizzo dei tipi signed ed unsigned per effettuare operazioni di comparazione.

function CONV_UNSIGNED(ARG: INTEGER; SIZE: INTEGER) return UNSIGNED;


function CONV_UNSIGNED(ARG: UNSIGNED; SIZE: INTEGER) return UNSIGNED;
function CONV_UNSIGNED(ARG: SIGNED; SIZE: INTEGER) return UNSIGNED;

Fig. 12.4. Funzioni di conversione del package std_logic_arith, con risultato di


tipo unsigned. L’argomento SIZE definisce il numero di bit che compongono il
risultato

Gli operatori relazionali sono inoltre overloaded per i tipi unsigned e signed, in modo che uno dei
due operandi può essere di tipo intero.
Come esempio applicativo, si consideri il listato di Fig. 12.3. L’ uscita s_big si alza quando il
vettore di ingresso, considerato come un intero senza segno, ha un valore maggiore di 200; l’uscita
s_neg, invece, è pari ad uno quando il vettore di ingresso, considerato come un intero rappresentato
in complementi alla base, ha un valore negativo. Si noti che, utilizzando le due funzioni signed ed
unsigned, lo stesso segnale s può essere interpretato sia come come intero senza segno, sia come
intero rappresentato in complementi alla base.
Il package std_logic_arith definisce un insieme di funzioni di conversione per i tipi signed,
unsigned ed std_logic_vector. Alcune delle dichiarazioni di queste funzioni, per la conversione al
tipo unsigned, sono riportate in Fig. 12.4. La funzione conv_unsigned richiede due operandi: il
primo è il valore da convertire (che può essere di tipo integer, signed o unsigned) il secondo è un
intero che specifica il numero di bit del risultato. Si noti che il package definisce funzioni di
conversione al tipo signed ed al tipo std_logic_vector (conv_signed e conv_std_logic_vector) del
tutto analoghe a quelle mostrate in Fig. 12.4. Il package definisce inoltre la funzione di conversione
conv_integer che converte un argomento di tipo signed o unsigned in un risultato di tipo intero.

Gli operatori aritmetici predefiniti in VHDL (come la somma +, la sottrazione -, il prodotto *)


sono definiti per argomenti di tipo integer ma non per argomenti di tipo bit_vector, nè per argomenti
di tipo std_logic_vector. Utilizzando le funzioni di conversione di tipo, nel package std_logic_arith
gli operatori aritmetici (+, -, *, abs) sono overloaded e possono essere applicati
indifferentemente non solo ad operandi di tipo integer, ma anche ad operandi di tipo signed
ed unsigned.
Gli operatori aritmetici, come gli operatori relazionali di Fig. 12.2, sono definiti differentemente
per i due tipi signed ed unsigned, si veda ad esempio la Fig. 12.5 che mostra il risultato
dell’applicazione di operatori aritmetici ad argomenti di tipo diverso.
50 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Argomento 1 operatore Argomento 2 Unsigned Signed


“0111” + “0011” “1010” “1010”
“0100” + “100” “1000” “0000”
“1100” + “0100” “0000” “0000”
“0100” - “100” “0000” “1000”
“0111” * “0011” “00010101” “00010101”
“0100” * “100” “00010000” “11110000”

Fig. 12.5. Operatori aritmetici applicati a argomenti di tipo unsigned e signed.

Poichè uno stesso operatore (ad esempio “+”) può essere applicato ad operandi di tipi e di
lunghezze diverse, non è ovvio stabilire il tipo e la lunghezza del risultato.
In generale, se uno degli operandi è di tipo signed il risultato sarà anch’esso di tipo signed,
altrimenti il risultato è di tipo unsigned. Il risultato di un’operazione aritmetica (sia esso di tipo
signed o unsigned) viene automaticamente convertito al tipo std_logic_vector se è assegnato ad una
variabile o ad un segnale di tipo std_logic_vector.
Per le operazioni di somma e di sottrazione la lunghezza del risultato è normalmente pari a quella
dell’operando più lungo; peraltro, se un operando unsigned è combinato con un signed o con un
integer, la sua lunghezza è incrementata di una unità, per accomodare un bit segno pari a ‘0’ e
bisogna tener conto di questo fatto per valutare la lunghezza del risultato.

La Fig. 12.6 mostra un esempio di utilizzo di operatori aritmetici con il package std_logic_arith.
I due ingressi a1 e b1 vengono sommati fra di loro producendo l’uscita y1. I segnali a1, b1 ed y1
rappresentano degli interi rappresentati in complementi alla base.
La somma degli altri due ingressi a2 e b2, a 4-bit, è disponibile all’uscita y2. In questo caso a2, b2
ed y2 sono di tipo std_logic_vector e rappresentano degli interi positivi; il segnale di carry si alza in
caso di overflow. Per determinare la condizione di overflow i due addendi a2 e b2 vengono
rappresentati su 5 bit, utilizzando l’operatore di concatenazione & ed i due segnali interni tmp1 e
tmp2. La somma di tmp1 e tmp2, anch’essa rappresentata su 5-bit, è disponibile con il segnale
interno tmp3. In questo modo il segnale di overflow corrisponde con il bit più significativo di tmp3.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
entity sum is
port( a1,b1 : in signed (3 downto 0);
a2,b2 : in std_logic_vector (3 downto 0);
y1 : out signed (3 downto 0);
y2 : out std_logic_vector (3 downto 0);
carry : out std_logic );
end;
architecture beh of sum is
signal tmp1,tmp2,tmp3 : std_logic_vector (4 downto 0);
begin
y1 <= a1 + b1;
tmp1 <= ('0' & a2);
tmp2 <= ('0' & b2);
tmp3 <= unsigned(tmp1) + unsigned(tmp2);

y2 <= tmp3(3 downto 0);


carry <= tmp3 (4);
end beh;

Fig. 12.6. Utilizzo di operatori aritmetici con il package std_logic_arith.


51 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Il package std_logic_arith fornisce due funzioni per effettuare shift dei bit di numeri signed ed
unsigned:

function SHL(ARG: UNSIGNED; COUNT: UNSIGNED) return UNSIGNED;


function SHL(ARG: SIGNED; COUNT: UNSIGNED) return SIGNED;
function SHR(ARG: UNSIGNED; COUNT: UNSIGNED) return UNSIGNED;
function SHR(ARG: SIGNED; COUNT: UNSIGNED) return SIGNED;

La funzione SHL effettua uno shift dei bit dell’argomento ARG a sinistra, di un numero di bit pari
a COUNT; analogamente la funzione SHR effettua uno shift a destra. Entrambe le funzioni sono
overloaded, in modo da operare con argomenti sia signed che unsigned.
La funzione SHL opera allo stesso modo, sia se l’argomento è di tipo signed, sia se l’argomento è
di tipo unsigned.
La funzione SHR tratta differentemente operandi di tipo signed ed unsigned. Se l’argomento è
unsigned, i bit più a sinistra del risultato sono riempiti con ‘0’; viceversa, se l’argomento è signed, i
bit più a sinistra del risultato sono riempiti con il bit segno dell’argomento.

12.3. I packages std_logic_signed e std_logic_unsigned. Come abbiamo visto negli esempi dei
capitoli precedenti, molto spesso viene utilizzato il tipo standard std_logic per la descrizione VHDL
di sistemi digitali. Volendo effettuare delle operazioni aritmetiche su operandi di tipo std_logic è
necessario far ricorso molto spesso ad operazioni di conversione di tipo, si veda ad esempio il listato
di Fig. 12.6.
Con il package std_logic_unsigned è possibile applicare gli operatori aritmetici e di comparazione
ad argomenti di tipo std_logic_vector, che verranno trattati come interi unsigned. Analogamente,
con il package std_logic_signed è possibile applicare gli operatori aritmetici e di comparazione ad
argomenti di tipo std_logic_vector, che verranno trattati come interi signed.

12.4. Sono davvero standard i packages standard? I packages std_logic_arith,


std_logic_signed ed std_logic_unsigned sono (quasi) sempre compilati in una libreria denominata
IEEE. In effetti, però, questi packages non sono standard proposti dalla associazione IEEE ma sono
copyright della Synopsys (una delle aziende leader nello sviluppo di sintetizzatori HDL) e pertanto
possono in alcuni casi essere disponibili in una libreria denominata SYNOPSYS.
Le librerie adottate come standard dalla associazione IEEE sono denominate: numeric_bit (in cui
l’elemento base per i tipi signed ed unsigned è il tipo bit, invece che il tipo std_logic) e numeric_std
(in cui l’elemento base per i tipi signed ed unsigned è il tipo std_logic). Nei due packages gli
operatori aritmetici (+, -, *, abs) sono overloaded e possono quindi essere applicati ma anche ad
operandi di tipo signed ed unsigned. Nel package numeric_std, a differenza del package
std_logic_arith, il risultato di una operazione aritmetica non può essere assegnato direttamente ad
una variabile o ad un segnale di tipo std_logic_vector. Pertanto, poichè il tipo di uso più comune per
la simulazione e la sintesi è std_logic_vector, in un listato VHDL che utilizza il package numeric_std
è necessario utilizzare frequentemente delle funzioni di conversione di tipo. In definitiva, appare più
comodo l’utilizzo delle librerie std_logic_signed ed std_logic_unsigned rispetto ai packages proposti
dalla IEEE.
A rendere le cose ancora meno standard, si deve osservare che esistono ulteriori packages
proposti da altri produttori di programmi di sintesi e compilati (quasi) sempre nella libreria IEEE. In
particolare si segnalano i packages numeric_signed e numeric_unsigned (sviluppati dalla Mentor
Graphics) che sono quasi del tutto equivalenti ai due packages std_logic_signed ed
std_logic_unsigned.
52 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


use ieee.std_logic_unsigned.all;
entity conta16 is
port( reset, clk : in std_logic;
y : buffer std_logic_vector (3 downto 0) );
end;
architecture beh of conta16 is
begin
process (reset,clk)
begin
if (reset =’1’) then
y <= (others => ‘0’);
elsif rising_edge(clk) then
y <= y+1;
end if;
end process;
end beh;
Fig. 12.7. Contatore modulo 16 con reset asincrono.

12.5. Esempi. Il listato di Fig. 12.7 mostra la descrizione di un contatore modulo 16. L’utilizzo
della libreria std_logic_unsigned consente di descrivere l’operazione di conteggio con la semplice
istruzione: y <= y+1; con la quale si somma un intero ad un std_logic_vector. Si noti che il
contatore è provvisto di un reset asincrono.
Il listato di Fig. 12.8 mostra un’altra descrizione VHDL di un contatore a 4-bit. Si noti che in
questo caso si è utilizzata una variabile di conteggio di tipo integer. Le funzioni di conversione di
tipo consentono di avere ingressi ed uscite di tipo std_logic_vector. Si lascia al lettore l’analisi del
listato per individuare le funzioni dell’entità conta2. Si lascia al lettore, inoltre, la riscrittura
semplificata del listato utilizzando la libreria std_logic_unsigned.

library ieee; use ieee.std_logic_1164.all;


use ieee.std_logic_arith.all;
entity conta2 is
port( reset, clk : in std_logic;
ce, load, dir: in std_logic;
din : in std_logic_vector (3 downto 0);
y : out std_logic_vector (3 downto 0) );
end;
architecture beh of conta2 is
begin
process (clk, reset)
variable count : INTEGER range 0 to 15;
begin
if reset='1' then
count := 0;
elsif clk='1' and clk'event then
if load='1' then
count := conv_integer( unsigned(din) );
else
if ce='1' then
if dir='1' then
if (count < 15) then
count := count + 1;
else
count := 0;
end if;
else
if (count > 0) then
count := count - 1;
else
count := 15;
end if;
end if;
end if;
end if;
end if;
y<= conv_std_logic_vector(count, y'length);
end process;
end beh;
Fig. 12.8. Descrizione comportamentale di un contatore.
53 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


use ieee.std_logic_unsigned.all;
entity counter_10 is
port( din : in std_logic_vector (7 downto 0);
mode: in std_logic;
y : out std_logic_vector (3 downto 0) );
end;
architecture beh of counter_10 is
begin
process (din, mode)
variable conta : std_logic_vector (y'range);
begin
conta := (others => '0');
for i in din'low to din'high loop
if ( (mode ='1') and din(i) ='1' ) or
( (mode ='0') and din(i) ='0' ) then
conta := conta + 1;
end if;
end loop;
y <= conta;
end process;
end beh;

Fig. 12.9. Contatore di bit ‘1’ e ‘0’ in una word di ingresso.

Il listato di Fig. 12.9 mostra una ennesima versione del contatore di bit ‘1’ che abbiamo incontrato
nei capitoli precedenti. In questo caso la funzionalità del circuito è stata ampliata, permettendo il
conteggio dei bit ‘1’ oppure dei bit ‘0’ della word di ingresso. La modalità di funzionamento è
stabilita dal segnale di ingresso mode. Il listato di Fig. 12.9 evidenzia come il VHDL consenta di
descrivere in maniera molto succinta ed immediatamente comprensibile il funzionamento di sistemi
digitali.
Si deve peraltro sottolineare che, in generale, quanto più di alto livello è la descrizione di un
sistema, tanto più il risultato della sintesi è dipendente dalla bontà del sistema di sviluppo. Spesso, ad
una descrizione più semplice (del tipo di quella di Fig. 6.1) corrisponde una implementazione più
efficace del circuito.
Come ultimo esempio, la Fig. 12.10 riporta una descrzione VHDL comportamentale di una unità
logico-aritmentica (ALU) ad 8-bit. Il sistema dispone di due bus di ingresso ad 8-bit (denominati a e
b) e di un codice operativo a tre bit. In uscita oltre al risultato y dell’operazione eseguita dalla ALU
viene calcolato un segnale di overflow.
I codici operativi aritmetici consentono il calcolo della somma e della sottrazione dei due segnali
di ingresso a e b e dell’incremento e decremento unitario di a.
Le operazioni logiche prevedono l’effettuazione della and fra i due ingressi a e b, la possibilità di
portare in uscita il valore dell’ingresso b ed infine la possibilità di effettuare uno shift di una
posizione di a, sia a destra che a sinistra.
Nell’architettura della ALU i codici operativi sono definiti mediante opportune costanti, per
semplificare la leggibilità del listato. Il bit più significativo del codice operativo determina se
l’operazione da effettuare è di tipo aritmetico o logico. Si noti l’utilizzo di un alias per riferirsi a
questo bit con il nome mode.
La ALU è descritta mediante due processi e due statements concorrenti.
Il primo processo realizza le funzioni aritmetiche, utilizzando il package std_logic_signed, mentre
il secondo esegue le operazioni logiche. Si noti il modo con sui è stato effettuato il calcolo
dell’overflow nel primo processo e l’utilizzo delle funzioni di conversione di tipo nel secondo
processo. I due statements concorrenti consentono, in base al codice operativo, di portare in uscita il
risultato della parte aritmetica o di quella logica della ALU ed inoltre azzerano il bit di overflow nel
caso in cui l’operazione della ALU sia di tipo logico.
54 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


use ieee.std_logic_arith.all; use ieee.std_logic_signed.all;
entity alu is
port( a,b : in std_logic_vector (7 downto 0);
codop: in std_logic_vector (2 downto 0);
y : out std_logic_vector (7 downto 0);
ovflo: out std_logic);
end;
architecture beh of alu is
alias mode : std_logic is codop (2);
constant add : std_logic_vector (1 downto 0) := "00";
constant sub : std_logic_vector (1 downto 0) := "01";
constant inc : std_logic_vector (1 downto 0) := "10";
constant dec : std_logic_vector (1 downto 0) := "11";
constant passa_b : std_logic_vector (1 downto 0) := "00";
constant bit_and : std_logic_vector (1 downto 0) := "01";
constant shift_l : std_logic_vector (1 downto 0) := "10";
constant shift_r : std_logic_vector (1 downto 0) := "11";
signal y1, y2 : std_logic_vector (7 downto 0);
signal ov : std_logic;
begin

aritm: process (a,b,codop)


variable tmp : std_logic_vector (7 downto 0);
begin
case codop(1 downto 0) is
when add => tmp := b;
when sub => tmp := -b;
when inc => tmp := conv_std_logic_vector(1,8);
when others => tmp := conv_std_logic_vector(-1,8);
end case;
y1 <= a + tmp;
if ( (a>0) and (tmp>0) and (y1<0) ) or
( (a<0) and (tmp<0) and (y1>0) )
then
ov <= '1';
else
ov <= '0';
end if;
end process aritm;

logic: process (a,b,codop)


begin
case codop(1 downto 0) is
when passa_b => y2 <= b;
when bit_and => y2 <= b and a;
when shift_l => y2 <= conv_Std_Logic_Vector(
shl( signed(a),"1"), 8);
when others => y2 <= conv_Std_Logic_Vector(
shr( signed(a),"1"), 8);
end case;
end process logic;

y <= y1 when mode ='1' else y2;


ovflo <= ov when mode ='1' else '0';
end beh;

Fig. 12.10. Descrizione VHDL di una unità logico-aritmetica.


55 Corso di Architettura dei Sistemi Integrati. Note sul VHDL
re set
*
id le
b us_id
re ady
re ady re ady
scelta
re ad re ad
bus_id
leg gi scriv i
reset
oe
ready M acchina re ady
m emoria re ady
a stati
read_w rite SR AM
we u scite
clock sta to oe we
id le 0 0

scelta 0 0

leg gi 1 0

scriv i 0 1

Fig. 13.1. Semplice macchina a stati.

13. Macchine a stati finiti.

Il VHDL consente di descrivere facilmente il funzionamento di macchine a stati. Per introdurre


l’argomento con un esempio, si consideri il sistema mostrato in Fig. 13.1. Il circuito da progettare è
un semplice controllore che deve abilitare e disabilitare i segnali di write enable e di output enable di
un banco di memoria, al fine di consentire l’effettuazione delle operazioni di lettura e scrittura da
parte di un microprocessore.
Il controllore si attiva quando il microprocessore invia il segnale di controllo ready ed inoltre
viene riconosciuto un opportuno indirizzo (A2 in esadecimale) sul bus. Il colpo di clock successivo
ha inizio il ciclo di lettura o di scrittura, individuato dal segnale read attivato dal processore. Sia il
ciclo di lettura che quello di scrittura terminano quando si abbassa il segnale ready. In ogni momento
il controllore può essere inizializzato grazie ad un segnale di reset.
La Fig. 13.1 riporta il diagramma a stati, che evidenzia la presenza di tre stati distinti denominati:
idle, scelta, leggi, scrivi. Le due uscite write enable (we) ed output enable (oe) sono funzione del
solo stato della macchina e non anche degli ingressi: si tratta pertanto di una machina di Moore.
La Fig. 13.2 mostra la struttura di una generica macchina a stati sincrona, in cui il banco di registri
immagazina lo stato attuale del sistema, mentre il sistema combinatorio valuta lo stato futuro e le
uscite del sistema.

13.1. Descrizione con due processi. I Il modo più immediato per descrivere in VHDL una
macchina a stati è quello di utilizzare due processi: uno per il sistema combinatorio ed uno per il
banco di registri. Lo stato attuale X e lo stato futuro Y del sistema sono rappresentati da due segnali,
appartenenti ad un tipo enumerato che include tutti i possibili stati del sistema.

Fig. 13.2. Struttura di una macchina a stati sincrona.


56 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


entity controller is
port( bus_id : in std_logic_vector (7 downto 0);
clock, reset, ready, read: in std_logic;
oe, we : out std_logic);
end;
architecture fsm of controller is

type stato is (idle, scelta, leggi, scrivi);


signal x,y : stato;

begin

registri: process (clock,reset)


begin
if (reset='1') then
x <= idle;
elsif rising_edge(clock) then
x <= y;
end if;
end process registri;

comb: process (bus_id, ready, read, x)


begin
case x is
when idle =>
oe <= '0';
we <= '0';
if ( read='1' and bus_id=X"A2" ) then
y <= scelta;
else
y <= idle;
end if;
when scelta =>
oe <= '0';
we <= '0';
if ( read='1') then
y <= leggi;
else
y <= scrivi;
end if;
when leggi =>
oe <= '1';
we <= '0';
if ( ready='1') then
y <= idle;
else
y <= leggi;
end if;
when scrivi =>
oe <= '0';
we <= '1';
if ( ready='1') then
y <= idle;
else
y <= scrivi;
end if;
end case;
end process comb;

end fsm;

Fig. 13.3. Descrizione VHDL della macchina a stati di Fig 13.1.


57 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

La Fig. 13.3 mostra la descrizione VHDL della macchina a stati di Fig. 13.1.
Con le due righe:

type stato is (idle, scelta, leggi, scrivi);


signal x,y : stato;

si definiscono un tipo enumerato per lo stato della macchina e due segnali per lo stato attuale e lo
stato futuro.
Il processo registri descrive il banco di registri, in cui si utilizza un reset asincrono.
Il processo comb descrive il sistema combinatorio. Si noti l’utilizzo di uno statement case..when
per calcolare lo stato futuro e le uscite del sistema, in funzione degli ingressi e per ogni possibile
stato x.
Il listato di Fig. 13.3 evidenzia la semplicità con la quale il VHDL consente di descrivere delle
macchine a stati. Si noti che in fase di sintesi non è necessario definire manualmente una codifica
degli stati, che può essere affidata al sistema di sviluppo. Peraltro, utilizzando delle opportune
opzioni, i sintetizzatori VHDL consentono all’utente di definire eventualmente una codifica ad hoc.
Vediamo ora alcune varianti rispetto al listato di Fig. 13.3.
La Fig. 13.4 mostra l’utilizzo di un ingresso di reset sincrono, anziché asincrono. Si utilizza un
if-then-else all’interno del del processo registri per assicurare che la macchina si porti nello
stato idle a seguito dell’attivazione del segnale di reset, indipendentemente dal valore dello stato
currente. Il processo comb rimane invariato rispetto al listato di Fig. 13.3.

registri: process (clock)


begin
if rising_edge(clock) then
if (reset='1') then
x <= idle;
else
x <= y;
end if;
end if;
end process registri;
end fsm;

Fig. 13.4. Macchina a stati con reset sincrono.

end process registri;


comb: process (bus_id, ready, read, x, reset)
begin
if (reset='1')
then
y <= idle;
oe <= '0';
we <= '0';
else
case x is
when idle =>
. . . . . .
. . . . . .
end case;
end if;
end process comb;

Fig. 13.5. Macchina a stati con reset sincrono.


58 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Una versione alternativa di macchina a stati con reset sincrono è riportata in Fig. 13.5. In questo
caso si include un if-then-else all’interno del processo comb, per assicurare che la macchina si
porti nello stato idle a seguito dell’attivazione del segnale di reset. In questo caso il processo
registri rimane invariato rispetto al listato di Fig. 13.3.

13.2. Descrizione con un solo processo. La Fig. 13.6 mostra che non è indispensabile utilizzare
una descrizione con due processi per rappresentare una macchina a stati. Il listato di Fig. 13.6 riporta
infatti una descrizione basata su di un unico processo per descrivere le transizioni di stato. Due
statements concorrenti consentono di ottenere i segnali di uscita in funzione dello stato del sistema.
In questo tipo di descrizione è sufficiente un solo segnale (denominato st) di tipo stato.

library ieee; use ieee.std_logic_1164.all;


entity controller is
port( bus_id : in std_logic_vector (7 downto 0);
clock, reset, ready, read: in std_logic;
oe, we : out std_logic);
end;
architecture fsm of controller is
type stato is (idle, scelta, leggi, scrivi);
signal st : stato;
begin
f: process (clock,reset)
begin
if (reset='1')
then
st <= idle;
elsif rising_edge(clock) then
case st is
when idle =>
if ( read='1' and bus_id=X"A2" ) then
st <= scelta;
else
st <= idle;
end if;
when scelta =>
if ( read='1') then
st <= leggi;
else
st <= scrivi;
end if;
when leggi =>
if ( ready='1') then
st <= idle;
else
st <= leggi;
end if;
when scrivi =>
if ( ready='1') then
st <= idle;
else
st <= scrivi;
end if;
end case;
end if;
end process f;

oe <= '1' when (st = leggi) else '0';


we <= '1' when (st = scrivi) else '0';

Fig. 13.6. Descrizione di una macchina a stati con un solo processo.


59 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Stato Codifica Binaria


idle 00
scelta 01
leggi 10
scrivi 11

Fig. 13.7. Possibile codifica dello stato (codifica “sequenziale”).

13.3. Codifica dello stato. Negli esempi precedenti, lo stato del sistema è definito mediante un
tipo enumerato. Ad esempio, nel listato di Fig. 13.3 si definisce:

type stato is (idle, scelta, leggi, scrivi);


signal x,y : stato;

In fase di sintesi, ai due segnali x ed y corrisponderanno dei vettori binari. Ognuno degli elementi
del tipo enumerato verrà codificato con un opportuno insieme di bit.
Un possibile esempio di codifica dello stato è mostrato in Fig. 13.7. Una codifica come quella di
Fig. 13.7, in cui al primo stato viene fatta corrispondere la stringa 000, la secondo stato la stringa
001 e così via (seguendo quindi la numerazione binaria), viene spesso chiamata codifica
“sequenziale”.
La codifica dello stato ha un notevole influenza sulle prestazioni (area occupata e tempo di
propagazione) della macchina a stati.
La codifica dello stato, sintetizzando un listato VHDL come quello di Fig. 13.3, viene affidata al
programma di sintesi. Spesso, mediante opportune opzioni che variano a seconda del programma di
sintesi, il progettista può specificare una una codifica ad hoc.
Un esempio (tratto dal manuale del sintetizzatore xst della xilinx) è riportato in Fig. 13.8. In
questo caso, si utilizza un attributo (di nome enum_encoding) associato al tipo enumerato per
stabilire la codifica. Si sottolinea che il nome dell’attributo (e l’utilizzo stesso di attributi per stabilire
la codifica di un tipo enumerato) è strettamente dipendente dal programma di sintesi.

. . . .
architecture fsm of controller is

type stato is (idle, scelta, leggi, scrivi);


attribute enum_encoding of stato : type is
"01 00 11 10";

signal x,y : stato;


. . . .

Fig. 13.8. Utilizzo di attributi per specificare la codifica dello stato.


60 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

subtype stato is std_logic_vector( 1 downto 0);


signal x,y : stato;
constant idle : stato := "01";
constant scelta : stato := "00";
constant leggi : stato := "11";
constant scrivi : stato := "10";
Fig. 13.9. Codifica esplicita dello stato.

subtype stato is std_logic_vector( 1 downto 0);


signal x,y : stato;
constant idle : stato := "0001";
constant scelta : stato := "0010";
constant leggi : stato := "0100";
constant scrivi : stato := "1000";
Fig. 13.10. Codifica “one-hot”.

Un approccio più generale per stabilire esplicitamente la codifica dello stato è riportato in Fig.
13.9. Le uniche modifiche da apportare, rispetto agli esempi visti in precedenza, sono l’utilizzo di un
sottotipo di std_logic_vector (invece di un tipo enumerato) per definire lo stato e l’introduzione di
opportune costanti per determinarne la codifica.
La Fig. 13.10 mostra l’utilizzo di una codifica esplicita di tipo “one-hot” dello stato. In una
macchina a stati con codifica “one-hot” si utilizzano tanti flip-flop quanti sono gli stati del sistema. In
ogni stato uno ed uno solo dei flip-flop ha uscita alta, mentre tutti gli altri hanno uscita bassa. La
codifica “one-hot” consente spesso di semplificare la logica combinatoria necessaria per il calcolo
dello stato futura del sistema, al costo di un aumento del numero di flip-flop utilizzati.
L’utilizzo di una codifica come quella di Fig. 13.10 nella descrizione di Fig. 13.3 richiede richiede
una ulteriore modifica al listato. Nel processo che descrive il sistema combinatorio, infatti, il
costrutto case-when non considera più tutti i casi possibili per il segnale x, che sono ora divenuti 24.
Come mostra il listato di Fig. 13.11, la modifica da apportare consiste nell’aggiunta di un when
others finale.

. . . .
subtype stato is std_logic_vector( 1 downto 0);
signal x,y : stato;
constant idle : stato := "0001";
constant scelta : stato := "0010";
constant leggi : stato := "0100";
constant scrivi : stato := "1000";
. . . .
comb: process (bus_id, ready, read, x)
begin
case x is
when idle =>
. . . .
when scelta =>
. . . .
when leggi =>
. . . .
when scrivi =>
. . . .
when others =>
oe <= '-';
we <= '-';
y <= (others => '-');
end case;
end process comb;

Fig. 13.11. Descrizione della macchina a stati di Fig 13.1, utilizzando una codifica one-hot..
61 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

ingressi stato stato


L ogica per futuro Registri corrente L ogica per uscite
lo stato di stato le uscite
futuro

Fig. 13.12.Calcolo delle uscite a partire dallo stato corrente (macchina di Moore)

Registri uscite
Logica per
le uscite di stato

ingressi stato stato


Logica per futuro Registri corrente
lo stato di stato
futuro

Fig. 13.13. Calcolo delle uscite a partire dallo stato futuro.

13.4. Ottimizzazione dei ritardi. Nei listati di Fig. 13.3-13.6 l’uscita viene calcolata con un
sistema combinatorio a partire dallo stato attuale del sistema, in analogia allo schema a blocchi di
Fig. 13.12. In questo modo, assumendo una macchina a stati di Moore, in cui le uscite sono funzioni
solo dello stato del sistema, si può avere un ritardo significativo fra il fronte attivo del clock e le
uscite (ritardo clock-to-output).
Per ridurre il ritardo clock-to-output, al costo di un accresciuto numero di elementi di memoria, è
possibile utilizzare la struttura di Fig 13.13. In questo schema, invece di utilizzare lo stato presente
per calcolare i valori delle uscite, si utilizza lo stato futuro per determinare i valori che le uscite
assumeranno il successivo colpo di clock. In questo modo le uscite sono disponibili direttamente al
terminale Q di un banco di registri ed il ritardo clock-to-output diviene uguale al delay clock-to-q dei
flip-flop.
La Fig. 13.14 riporta una descrizione VHDL della macchina a stati considerata come esempio nei
paragrafi precedenti, corrispondente allo schema di Fig. 13.13. Il listato di Fig. 13.14 mostra, inoltre,
un esempio di descrizione di una macchina a stati mediante tre processi.
62 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

registri: process (clock,reset)


begin
if (reset='1') then
x <= idle;
oe <=’0’; we <= ‘0’;
elsif rising_edge(clock) then
x <= y;
oe <=oe_d; we <= we_d;
end if;
end process registri;

comb: process (bus_id, ready, read, x)


begin
case x is
when idle =>
if ( read='1' and bus_id=X"A2" )
then y <= scelta;
else y <= idle;
end if;
when scelta =>
if ( read='1')
then y <= leggi;
else y <= scrivi;
end if;
when leggi =>
if ( ready='1')
then y <= idle;
else y <= leggi;
end if;
when scrivi =>
if ( ready='1')
then y <= idle;
else y <= scrivi;
end if;
end case;
end process comb;

comb_out: process (y)


begin
case y is
when idle =>
oe_d <= '0'; we_d <= '0';
when scelta =>
oe_d <= '0'; we_d <= '0';
when leggi =>
oe_d <= '1'; we_d <= '0';
when scrivi =>
oe_d <= '0'; we_d <= '1';
end case;
end process comb_out;
Fig. 13.14.Descrizione della macchina a stati di Fig. 13.1 in cui le uscite sono ottenute
a partire dallo stato futuro.
63 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

ingressi stato uscite


L ogica per futuro Registri
lo stato di stato
futuro
stato
corrente

Fig. 13.15. Riduzione del ritardo clock-output. Le uscite corrispondano ad alcuni dei bit di
stato.

Stato Uscite Codifica


oe we
idle 0 0 000
scelta 0 0 001
leggi 1 0 010
scrivi 0 1 100

Fig. 13.16. Codifica in cui le uscite concidono con alcuni dei bit di stato.

Un’altra tecnica utile per ridurre il ritardo clock-output consiste nel codificare opportunamente lo
stato del sistema, in modo tale che alcuni dei bit di stato possano essere essi stessi utilizzati come
uscite. Questo approccio è mostrato schematicamente in Fig. 13.15 e richiede spesso l’introduzione
di registri aggiuntivi rispetto ad una codifica minima (come quella di tipo “sequenziale”).
La tabella di Fig 13.16 mostra una codifica dello stato su tre bit della macchina considerata come
esempio nei paragrafi precedenti. Con la codifica prescelta l’uscita oe corrisponde al secondo dei bit
dello stato corrente, mentre l’uscita we corrisponde al primo dei bit dello stato. Una descrizione in
VHDL della macchina a stati utilizzando la codifica di Fig. 13.16 è riportata in Fig. 13.17.

subype stato is std_logic_vector( 3 downto 0);


signal x,y : stato;
constant idle : stato := “000”;
constant scelta : stato := “001”;
constant leggi : stato := “010”;
constant scrivi : stato := “100”;
begin
oe <= x(2); -- stato = leggi
we <= x(3); -- stato = scrivi
comb: process(bus_id, ready, read, x)
begin
case x is
when idle =>
. . . . . . . . .
. . . . . . . . .
when others =>
y <= (others => ‘-‘);
end case;
end process comb;
registri: process (clock,reset)
begin
. . . . . . . . .
. . . . . . . . .
end process registri;

Fig. 13.17. Descrizione VHDL di una macchina a stati in cui le uscite concidono
con alcuni dei bit di stato.
64 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

14. Simulazione in VHDL e test-bench.

Nei paragrafi precedenti ci siamo occupati diffusamente dell’utilizzo del VHDL per la sintesi di
sistemi digitali. In quest’ultimo paragrafo vedremo alcuni aspetti relativi all’utilizzo del VHDL come
strumento per la simulazione e per la verifica progettuale.

14.1. Modelli VHDL per simulazione. La Fig. 14.1 mostra una descrizione VHDL di un
multiplexer 2/1. Rispetto ai costrutti visti in precedenza, abbiamo aggiunto la parola chiave after in
corrispondenza delle operazioni di assegnazione. In questo modo si è definito un ritardo di 1.2ns fra
ognuno degli ingressi del multiplexer e l’uscita. Si noti inoltre il when others che assegna un valore
indeterminato ‘X’ all’uscita se il segnale di selezione ha un valore diverso da ‘0’ e da ‘1’ (si ricordi
che il tipo std_logic è a nove valori).
La Fig. 14.2 mostra il comportamento del multiplexer descritto nel listato di Fig. 14.1, assumendo
che sia: i1=s=’0’. L’uscita y segue l’ingresso i0 con un ritardo pari ad 1.2ns. Si noti che ogni impulso
sull’ingresso di durata inferiore al ritardo viene rigettato (ritardo “inerziale”: una sollecitazione in
ingresso deve avere una durata sufficiente per influenzare l’uscita).

library ieee;
use ieee.std_logic_1164.all;
entity mux2 is
port (i1, i0, sel : in std_logic;
y : out std_logic);
end mux2;

architecture simple of mux2 is


begin
m : process (i1,i0,sel)
begin
case sel is
when ‘0’ =>
y <= i0 after 1.2 ns;
when ‘1’ =>
y <= i1 after 1.2 ns;
when others =>
y <= ‘X’ after 1.2 ns;
end case;
end process;
end simple;

Fig. 14.1 Descrizione di un multiplexer 2/1.

i0
tp=1 .2n s du ra ta m in ore
di 1.2 ns

Fig. 14.2 Comportamento del multiplexer descritto nel listato di Fig. 14.1(per: s=i1=’0’).
Si noti che un impulso su i0 di durata inferiore al ritardo di 1.2ns non viene riportato in uscita.
65 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Il VHDL consente di definire un ritardo “non-inerziale”; a tale scopo è necessario introdurre una
clausola transport, ad esempio:

y <= transport i0 after 1.2 ns;

I tempi di propagazione delle porte logiche e quelli dovuti alle linee di interconnessione sono
meglio modellati con ritardi di tipo “inerziale”, per cui in seguito non utilizzaremo più la clausola
transport.
Spesso è utile poter disporre di un modello con informazioni di temporizzazione più accurate
rispetto all’esempio di Fig. 14.1. Ad esempio, possiamo voler specificare differenti tempi di
propagazione per le transizioni 0-1 ed 1-0 dei segnali, o anche differenti tempi di propagazione a
seconda dell’ingresso che commuta. Una tecnica per ottenere questo risultato consiste creare
dapprima un modello idealizzato, con ritardi nulli, che descrive la funzionalità del circuito, per poi
aggiungere gli opportuni costrutti che portano in conto i differenti ritardi.
Si consideri, a titolo di esempio, il multiplexer 2/1 considerato in precedenza. Una descrizione
VHDL che porta in conto due ritardi differenti fra l’ingresso di selezione e l’uscita (tps) e fra gli
ingressi i1 ed i2 e l’uscita (tpi) è riportata in Fig. 14.3. Si noti che i ritardi sono definiti mediante un
costrutto generic, in modo che possono eventualmente essere variati quando si utilizza il
componente mux2 mediante un generic map. All’uscita si assegna l’uno o l’altro dei due possibili
ritardi, a seconda che si sia o meno manifestata una commutazione sull’ingresso di selezione; ciò
viene controllato utilizzando l’attributo ‘event applicato al segnale s.

library ieee;
use ieee.std_logic_1164.all;
entity mux2 is
generic (tpi : time := 1.2 ns; tps : time := 1.5ns);
port (i1, i0, sel : in std_logic;
y : out std_logic);
end mux2;
architecture mx of mux2 is
begin
m : process (i1,i0,sel)
variable y1 : std_logic;
begin
case sel is
-- modello idealizzato
when ‘0’ => y1 := i0;
when ‘1’ => y1 := i1;
when others => y1 := ‘X’;
end case;
-- ritardi
if (sel’event)
then y <= y1 after tps;
else y <= y1 after tpi;
end if;
end process;
end mx;

Fig. 14.3 Descrizione di un multiplexer 2/1 con descrizione più accurata dei ritardi.
66 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

. . . . . . . . . . . .
-- ritardi
if (sel’event)
then
if (y1 = ‘1’)
then -- transizione 0->1
y <= y1 after tps01;
else -- transizione 1->0
y <= y1 after tps10;
end if;
else
if (y1 = ‘1’)
then -- transizione 0->1
y <= y1 after tpi01;
else -- transizione 1->0
y <= y1 after tpi10;
end if;
end if;

Fig. 14.4 Introduzione di ritardi differenti per le commutazioni 0-1 ed 1-0 dell’uscita.

Volendo introdurre dei ritardi differenti fra le commutazioni 0-1 ed 1-0 dell’uscita, è possibile
modificare il listato di Fig. 14.3 come mostrato in Fig. 14.4. In questo caso si controlla il valore della
variabile y1 per stabilire il tipo di transizione che dovrà effettuare l’uscita ed assegnare di conseguenza il
ritardo opportuno.

14.2. Aggiunta di messaggi di controllo ad una descrizione VHDL. Il costrutto assert


consente di stampare dei messaggi di controllo a video durante la simulazione di un listato VHDL,
quando viene a verificarsi una determinata condizione. Il messaggio del report viene visualizzato se
la condzione dopo assert è falsa. Al messaggio di controllo viene inoltre associato un “livello di
severità” che può variare da note (il messaggio è una semplice informazione di debug) fino a
failure (al messaggio è associata una condizione di malfunzionamento del circuito). Gli altri “livelli
di severità” previsti dal VHDL sono warning ed error.
Per illustrare l’utilizzo del costrutto assert, consideriamo il listato di Fig. 14.5 (pagina seguente)
che si riferisce ad un flip-flop di tipo D, in cui, oltre ad un ritardo clock-q è stato aggiunto un
contollo sui tempi di setup e di hold.
Alcune osservazioni sul listato.
Come prima osservazione si noti che in caso di errore si è utilizzato un “livello di severità”
warning.
Seconda osservazione: il processo che descrive il flip-flop è sensibile non solo al clock ma anche
al segnale d; ciò è dovuto alla necessità di attivare il processo in corrispondenza delle variazioni di d,
al fine di verificare i vincoli sui tempi di setup e di hold.
Terza osservazione: per il controllo sul tempo di setup si è utilizzato l’attributo ‘last_event
applicato al segnale d, che fornisce il tempo trascorso dall’ultima transizione del segnale.
Analogamente, per il controllo del tempo di hold si utilizza l’attributo ‘last_event applicato questa
volta al segnale di clock. Inoltre, per il contollo del tempo di hold si utilizzano gli attributi ‘stable
ed ‘event (che è sono veri, rispettivamente, se il segnale è rimasto stabile o ha avuto una transizione
nell’ultimo delta-time). Si lascia al lettore il compito di verificare la correttezza (e le limitazioni) del
controllo operato sul tempo di hold.
Da notare che il VHDL-93 consente di utilizzare lo statement report da solo (senza assert) per
inviare dei messaggi sulla console di simulazione.
67 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee;
use ieee.std_logic_1164.all;
entity dff is
generic (tq : time := 1.0 ns;
ts : time := 0.4ns;
th : time := 0.3ns);
port (d, clk : in std_logic;
q : out std_logic);
end dff;
architecture cplx of mux2 is
begin
m : process (d,clk)
variable q1 : std_logic;
begin
-- modello idealizzato
if rising_edge(clk) then q1 := d;
end if;
-- ritardo
q <= q1 after tq;
-- controllo tempo di setup
if rising_edge(clk)
then
assert (d'last_event > ts)
report "Violazione del tempo di setup"
severity warning;
end if;

-- controllo tempo di hold


if (d'event and clk'stable and clk='1')
then
assert (clk'last_event > th)
report "Violazione del tempo di hold"
severity warning;
end if;
end process;
end cplx;

Fig. 14.5 Flip-flop con controllo dei tempi di setup e di hold.

14.3. Test bench. Per effettuare una simulazione di un sistema descritto VHDL è necessario
applicare delle opportune sequenze di ingresso (che siano in grado di testare adeguatamente la
funzionalità del sistema sotto esame) e verificare la corrispondenza delle uscite con le specifiche
assegnate. In VHDL, questa fase di simulazione e di verifica è affidata ad un test-bench.
Come requisito minimo, un test bench deve essere in grado di applicare gli opportuni stimoli al
sistema sotto esame. L’uscita del circuito può essere salvata su di un file o visualizzata in forma
grafica. E’ in seguito necessaria un’analisi “manuale” dei risultati prodotti dalla simulazione per
verificare il corretto funzionamento del sistema. Questo approccio, che consente di descrivere
facilmente il test-bench, può essere accettabile per sistemi di ridotte dimensioni ma è sconsigliabile
per circuiti complessi in cui la mole dei dati di uscita diviene rilevante.
Un test-bench più accurato deve quindi prevedere non solo l’applicazione degli stimoli di ingresso
al sistema sotto esame, ma anche il controllo della correttezza delle uscite.
68 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

entity tb_one_counter is
end tb_one_counter;

architecture tb of tb_one_counter is

component one_counter
port( din : in std_logic_vector (7 downto 0);
. . . . . . . .
);
end component;

signal my_din : std_logic_vector (7 downto 0);


. . . . . . . . .

begin

dut : one_counter port map(


din => my_din,
. . . . . . . . .
);

test: process
begin
. . . . . . . . .
. . . . . . . . .

end process test;


end tb;

Fig. 14.6 Struttura di un test-bench.

La Fig. 14.6 mostra la struttura di un tipico test-bench. Poichè il test-bench non deve collegarsi con
altre entità esterne, la dichiarazione di entità di un test-bench non comprende la presenza di port.
All’interno della parte dichiarativa dell’architettura del test-bench troviamo (oltre alla definizione di
segnali, procedure ecc. locali al test-bench) la dichiarazione di componente relativa all’entità di cui
vogliamo simulare il funzionamento. Nella descrizione architetturale del test-bench viene istanziato il
dispositivo sotto test . Inoltre sono definiti processi e/o statements concorrenti per produrre gli stimoli di
ingresso al sistema sotto esame e controllarne le uscite.

Un esempio concreto di test-bench è riportato in Fig. 14.7 (pagina seguente). L’entità da simulare è il
contatore di bit ‘1’ di Fig. 12.9. Il processo test utilizza dei semplici statements di assegnazione per
generare i segnali di ingresso. Dopo le assegnazioni, uno statement:
wait for 20 ns;
sospende l’esecuzione del processo per una durata prestabilita (20 ns in questo esempio). In questo
modo si da tempo alle uscite dell’entità sotto test per commutare (ipotizzando, in altre parole, che il
ritardo di propagazione dell’entità che si sta simulando sia inferiore a 20ns).
Si effettua quindi il controllo del valore assunto dall’uscita, mediante il costrutto: assert. Da notare
che l’ultimo dei vettori applicati comporta, volutamente, la stampa di un messaggio di errore. Il wait finale
sospende per sempre il processo test, comportando la fine della simulazione.
69 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;

entity tb_one_counter is
end tb_one_counter;

architecture tb of tb_one_counter is

component one_counter
port( din : in std_logic_vector (7 downto 0);
mode: in std_logic;
y : out std_logic_vector (3 downto 0) );
end component;

signal my_din : std_logic_vector (7 downto 0);


signal my_mode: std_logic;
signal my_y : std_logic_vector (3 downto 0);

begin

dut : one_counter port map(


din => my_din,
mode => my_mode,
y => my_y);

test: process
begin
my_mode <= '1';
my_din <= "00111010";
wait for 20 ns;
assert (my_y = "0100" )
report "Errore per t=20 ns"
severity warning;

my_mode <= '1';


my_din <= "10111110";
wait for 20 ns;
assert (my_y = "0110")
report "Errore per t=40 ns"
severity warning;

my_mode <= '0';


my_din <= "10111010";
wait for 20 ns;
assert (my_y = "0011")
report "Errore per t=60 ns"
severity warning;

my_mode <= '0';


my_din <= "0010101X"; -- valore indefinito al bit 0
wait for 20 ns;
assert (my_y = "0101") -- produce un errore
report "Errore per t=80 ns"
severity warning;

wait; -- sospende per sempre il processo

end process test;


end tb;

Fig. 14.7 Test-bench per il contatore di bit ‘1’ di Fig.12.9 .


70 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Il test-bench di Fig. 14.7 non è facilmente modificabile: volendo includere ulteriori vettori di test è
necessario aggiungere ulteriori statements al processo che li genera. Un approccio più flessibile è mostrato
in Fig. 14.8. In questo caso i valori di ingresso ed il valore atteso dell’uscita sono memorizzati in un
array. Ogni elemento dell’array è un record.
In VHDL, come in altri linguaggi di programmazione, un record è caratterizzato dalla presenza di
elementi individuali, di tipo differente. Nell’esempio di Fig. 14.7, ogni recod contiene tre campi: i primi
due rappresentano gli ingressi dell’entità da simulare, mentre il terzo è l’uscita attesa.
Per scandire tutti i vettori di test, si utilizza un for loop nel processo che applica gli stimoli di
ingresso al sistema sotto esame e ne controlla le uscite. Volendo modificare o aggiungere dei vettori
di test nel test bench di Fig. 14.7 è sufficiente estendere il range del tipo test_vectors e modificare
la definizione della costante tabl.
Si noti che utilizzando lo statement assert è in questo caso possibile soltanto segnalare la presenza di
discordanze fra le uscite prodotte dall’entità sotto test e le uscite attese, ma non è possibile segnalare
qual’è stato il particolare vettore di test che ha prodotto l’errore.

architecture tb of tb1_one_counter is
component one_counter
port( din : in std_logic_vector (7 downto 0);
mode: in std_logic;
y : out std_logic_vector (3 downto 0) );
end component;
signal my_din : std_logic_vector (7 downto 0);
signal my_mode: std_logic;
signal my_y : std_logic_vector (3 downto 0);

type tk is record
d : std_logic_vector (7 downto 0);
m : std_logic;
ris : std_logic_vector (3 downto 0);
end record;

type test_vectors is array (3 downto 0) of tk;

constant tabl : test_vectors := (


(d => "00111010", m => '1', ris => "0100"),
(d => "10111110", m => '1', ris => "0110"),
(d => "10111010", m => '0', ris => "0011"),
(d => "0010101X", m => '0', ris => "0101")
);

constant PropDelay : time := 20 ns;


begin
dut : one_counter port map(din => my_din,
mode => my_mode, y => my_y);

test: process
variable vec : tk;
begin
for i in tabl'range
loop
vec := tabl (i);
my_mode <= vec.m;
my_din <= vec.d;
wait for PropDelay;
assert (my_y = vec.ris )
report "Errore "
severity warning;
end loop;

wait; -- sospende per sempre il processo

end process test;


end tb;

Fig. 14.8 Test-bench in cui i valori di ingresso e le uscite attese sono memorizzati in un array.
71 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

14.4. Operazioni su file di testo. Il VHDL fornisce delle routine standard per effettuare
operazioni su file di testo. In particolare, queste routine consentono di accedere in maniera molto più
flessibile alla console di simulazione, rispetto al semplice statement assert utilizzato in precedenza.
Le procedure standard per accedere ai files di testo sono presenti nella libreria std, nel package
textio (la clausola: library std; viene inclusa implicitamente all’inizio di ogni listato VHDL, e
pertanto non è indispensabile aggiungerla esplicitamente). Queste procedure sono overloaded per i
tipi std_logic ed std_logic_vector nel package io1164 che risiede nella libreria denominata
utils.
In VHDL un file deve essere definito all’interno di una parte dichiarativa, come nell’esempio
seguente1:
file test_vectors : text;
Il file viene in seguito aperto con una chiamata alla procedura FILE_OPEN:
FILE_OPEN (test_vectors, "test_vectors.txt", READ_MODE);
Il terzo argomento della procedura FILE_OPEN definisce la modalità di accesso al file (READ_MODE
oppure WRITE_MODE). Dopo il suo utilizzo, il file può essere chiuso con la procedura FILE_CLOSE:
FILE_CLOSE (test_vectors);
Il VHDL definisce due files speciali, denominati input ed output, che rappresentano l’ingresso e
l’uscita standard (la tastiera e la console di simulazione). Per operare su i files input ed output non
è necessaria nessuna definizione di files, nè alcuna operazione di apertura o chiusura.

Il package textio definisce un tipo denominato line che viene utilizzato per tutte le operazioni
di lettura e di scrittura su file. Le procedure definite nel package per operare sui files sono:
readline, read, writeline, write. Inoltre è definita una funzione denominata endfile per
controllare il raggiungimento delle fine di un file.

Come mostra la Fig. 14.9, la procedura readline legge una linea da un file e la memorizza in una
variabile di tipo line. Dopo aver effettuato la lettura di una linea di testo, è possibile estrarre i dati
dalla linea utilizzando la procedura read. Il primo parametro della procedura read si riferisce alla
variabile in cui è memorizzata la linea, mentre il secondo parametro è una variabile in cui viene
trasferito l’elemento letto dalla linea. Il terzo parametro, opzionale, è un valore booleano che viene
posto a true se il valore letto dalla linea è valido.

readline (InFile, InLine);


↑ ↑
file linea

read (InLine, MyData, good);


↑ ↑ ↑
linea dato da leggere variabile booleana opzionale

Fig. 14.9 Utilizzo delle procedure readline e read del package textio.

1
E’ possibile non solo definire, ma anche aprire un file in una parte dichiarativa. La sintassi è leggermente differente
fra il VHDL-87 ed il VHDL-93:
file test_vectors : text is in "test_vectors.txt"; -- vhdl 87
file test_vectors : text open READ_MODE is "test_vectors.txt"; -- vhdl 93
72 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

write (OutLine, MyData);


↑ ↑
linea dato da scrivere

writeline (OutFile, OutLine);


↑ ↑
file linea

Fig. 14.10 Utilizzo delle procedure writeline e write del package textio.

Per scrivere una linea di testo in un file dati si utilizzano le procedure write e writeline che,
come mostra la Fig. 14.10, sono molto simili alle procedure read e readline.

La Fig. 14.11 mostra un esempio concreto di utilizzo delle funzioni write e writeline
all’interno del pocesso denominato test in Fig. 14.8. Quando si individua una discordanza fra le
uscite prodotte dall’entità sotto test e le uscite attese, viene segnalato su di una prima riga qual’è il vettore
di test che ha prodotto l’errore e qual’è il tempo in cui l’errore si è verificato. Nella riga successiva
vengono riportate l’uscita attesa e l’uscita prodotta dal circuito.
Si noti che il file di uscita è output, per cui i messaggi compariranno sulla console di simulazione.
Si noti, inoltre, l’utilizzo della funzione now che indica quanto tempo è passato dall’inizio della
simulazione.
Un ultimo messaggio sulla console riporta il numero degli errori individuati.

use STD.textio.all; use ieee.std_logic_textio.all;


. . . . . . .
test: process
variable vec : tk;
variable MsgLine : line;
variable err_cnt : integer := 0;
begin
for i in tabl'range
loop
vec := tabl (i);
my_mode <= vec.m; my_din <= vec.d;
wait for PropDelay;
if (my_y /= vec.ris )
then
write (MsgLine, "Errore test vector numero: ");
write (MsgLine, i);
write (MsgLine, " al tempo: ");
write (MsgLine, now);
writeline (output, MsgLine);

write (MsgLine, "Uscita attesa: ");


write (MsgLine, vec.ris);
write (MsgLine, " Uscita prodotta dal circuito: ");
write (MsgLine, my_y);
writeline (output, MsgLine);
err_cnt := err_cnt+1;
end if;
end loop;
write (MsgLine, "Fine della simulazione. ");
write (MsgLine, "Errori individuati: "); write (MsgLine, err_cnt);
writeline (output, MsgLine);
wait; -- sospende per sempre il processo
end process test;
end tb2;

Fig. 14.11 Utilizzo delle funzioni write e writeline nel processo test di Fig. 14.8.
73 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

14.5. Testbench con vettori di test in un file ASCII. L’utilizzo di una tabella per memorizzare i
valori con cui stimolare l’entità sotto test ed in cui memorizzare i valori attesi dell’uscita diviene
rapidamente impraticabile al crescere del numero dei vettori di test. Inoltre, ogni modifica dei vettori di
test richiede una nuova ricompilazione del test-bech.
Un approccio più generale consiste nel memorizzare i vettori di test in un file separato; i valori
verranno letti dal file utilizzando le precedure viste nel paragrafo precedente. Un esempio, che si riferisce
sempre al contatore di bit ‘1’, è riportato in Fig. 14.12 (pagina seguente).
Per accedere al file di ingresso si utilizza un while loop, che termina quando si individua la fine del
file.
Durante la lettura dei dati vengono effettuati alcuni controlli. Utilizzando il costrutto next when, che
consente di saltare alla fine del loop, si saltano le linee vuote del file di ingresso e si saltano inoltre le linee
il cui primo campo non sia di tipo congruente con la variabile d. Ciò consente di introdurre facilmente dei
commenti all’interno del file di ingresso. La presenza di altri errori nel file di ingresso viene inoltre
individuata e segnalata sulla console di simulazione.
La parte di controllo dei risultati della simulazione è simile al listato di Fig. 14.11

La figura 14.13 mostra un possibile file di ingresso per il test-bench. In fase di lettura del file di
ingresso, le prime tre righe (non avendo come primo campo un std_logic_vector) non vengono
considerate mentre la riga 6 produce un messaggio di errore. Un ulteriore messaggio di errore viene
prodotto dalla linea 8, poichè il valore presente nel file come “risultato atteso”, ovvero 01010, è in
effetti errato ed in disaccordo con il risultato fornito dall’entità sotto test.

La Fig. 14.14 mostra il risultato della simulazione del test bench di Fig. 14.12 con il file di
ingresso di Fig. 14.13.

# input test vector file


# formato:
# d m ris
00111010 1 0100
10111110 1 0110
10111110 questa scritta produce un errore (linea 6)
10111010 0 0011
0010101X 0 0101
Fig. 14.13 Vettori di test per il test bench di Fig. 14.12.

Errore nel file di ingresso alla linea: 6


Errore al tempo: 80 ns
Uscita attesa: 0101 Uscita prodotta dal circuito: 0100
Fine della simulazione. Errori individuati: 1
Simulation stopped at: 80 ns

Fig. 14.14. Risultato della simulazione del test bench di Fig. 14.12
con il file di ingresso di Fig. 14.13.
74 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

library ieee; use ieee.std_logic_1164.all;


use STD.textio.all; use ieee.std_logic_textio.all;

entity tb3_one_counter is end tb3_one_counter;

architecture tb3 of tb3_one_counter is

component one_counter
port( din : in std_logic_vector (7 downto 0); mode: in std_logic;
y : out std_logic_vector (3 downto 0) );
end component;

signal my_din : std_logic_vector (7 downto 0);


signal my_mode: std_logic;
signal my_y : std_logic_vector (3 downto 0);
constant PropDelay : time := 20 ns;
begin
dut : one_counter port map(din => my_din,
mode => my_mode, y => my_y);
test: process
file test_vectors : text;
variable LineNumber : integer :=0;
variable InLine, MsgLine : line;
variable err_cnt : integer := 0;
variable good : boolean;
variable d : std_logic_vector (7 downto 0);
variable m : std_logic;
variable ris : std_logic_vector (3 downto 0);

begin

FILE_OPEN (test_vectors, "test_vectors.txt", READ_MODE);

while not endfile (test_vectors)


loop
readline (test_vectors, InLine);
LineNumber := LineNumber + 1;
next when InLine'length = 0; -- Skip empty lines
read (InLine, d, good);
next when not good;
read (InLine, m, good);
if (not good) then
write (MsgLine, "Errore nel file di ingresso alla linea: ");
write (MsgLine, LineNumber); writeline (output, MsgLine);
next;
end if;
read (InLine, ris, good);
if not good then
write (MsgLine, "Errore nel file di ingresso alla linea: ");
write (MsgLine, LineNumber); writeline (output, MsgLine);
next;
end if;
my_din <= d; my_mode <= m; wait for PropDelay;
if (my_y /= ris ) then
write (MsgLine, "Errore al tempo: ");
write (MsgLine, now); writeline (output, MsgLine);
write (MsgLine, "Uscita attesa: "); write (MsgLine, ris);
write (MsgLine, " Uscita prodotta dal circuito: ");
write (MsgLine, my_y); writeline (output, MsgLine);
err_cnt := err_cnt+1;
end if;
end loop;
FILE_CLOSE (test_vectors);
write (MsgLine, "Fine della simulazione. ");
write (MsgLine, "Errori individuati: ");
write (MsgLine, err_cnt); writeline (output, MsgLine);
wait; -- sospende per sempre il processo
end process test;
end tb3;

Fig. 14.12 Test bench per il contatore di bit ‘1’. I vettori di test sono in un file ASCII.
75 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

BIBLIOGRAFIA
76 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

INDICE

Capitolo 1: Introduzione 1
Capitolo 2: Entità ed Architetture 2
Capitolo 3: Dichiarazione di Entità 4
3.1 Modi 5
3.2 Tipi 5
Capitolo 4: Definizione dell’architettura 6
Capitolo 5: Descrizione VHDL strutturale 8
Capitolo 6: Descrizione dataflow 13
6.1 Simulazione funzionale 13
6.2 Assegnazione selezionata e condizionale 15
6.3 Sintesi di latch e flip-flop 18
Capitolo 7: Descrizione comportamentale 20
7.1 Segnali e variabili 23
7.2 Statements sequenziali 25
7.3 Sistemi con memoria 27
7.4 Sensitivity list incomplete 31
7.5 Lo statement wait 32
Capitolo 8: Descrizioni architetturali “miste”:
strutturali, data-flow e comportamentali 32

Capitolo 9: Tipi, sottotipi ed attributi 34


9.1 Tipi enumerati 34
9.2 Array 36
9.3 Array uncostrained 37
9.4 Sottotipi 38
9.5 Il tipo intero 39
9.6 Attributi 40
Capitolo 10: Funzioni e procedure 40
10.1 Funzioni 40
10.2 Procedure 42
Capitolo 11: Librerie e packages 43
11.1 Librerie 43
11.2 Packages 43
77 Corso di Architettura dei Sistemi Integrati. Note sul VHDL

Capitolo 12: Librerie standard 47


12.1 Il package std_logic_1164 47
12.2 Il package std_logic_arith 48
12.3 I packages std_logic_signed e 51
std_logic_unsigned
12.4 Sono davvero standard 51
i packages standard?
12.5 Esempi 52
Capitolo 13: Macchine a stati finiti 55
13.1 Descrizione con due processi 55
13.2 Descrizione con un solo processo 58
13.3 Codifica dello stato 59
13.4 Ottimizzazione dei ritardi 61
Capitolo 14: Simulazione in VHDL e test-bench 64
14.1 Modelli VHDL per simulazione 64
14.2 Aggiunta di messaggi di controllo ad
una descrizione VHDL 66
14.3 Test bench 67
14.4 Operazioni su file di testo 71
14.5 Testbench con vettori di test in un
file ASCII 73
Bibliografia 75

Potrebbero piacerti anche