Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
-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
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
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
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#!
!!
!
!!*
*
*
/ )! )! )!!! )!! )!! )!!*
"
2/#$*
2/3*
"2/"#4*
2/#5*
*
'
*
'
"
'
'
"
6
'
1
81
*
""'
'
$)
/$!'
$!2/
$! $!#*
$!2/
$!!
$!! $! $!!4*
!2/
0#!
!
!#*
!2/
0#!
!!
0#!
!!
!
!!4*
*
*
2/ )! )! )!!! )!! )!! )!!*
*
' *
(
'
6"
9
-
'
"":%
"
"
6
"
"
+' ' 1 1 "" '
2/
)!*
2/
)!*
2/ *
'
"*
&
"
'
'''
'
'
'*
' '
"
"
2/1$$$$$$$$1*
2/1$$$$$$$$1*
%
4$*
4$
2/1########1*
2/1$$$$$$$#1*
%
5$*
5$
2/1$#$#$#$#1*
2/1#$#$#$#$1*
%
5$*
5$
'
%
*
*
' *
entity and_or_invert_is
port (a1, a2, b1, b2: in bit:= ‘ 1 ‘; y:out bit);
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’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
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;
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);
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 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;
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;
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;
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 ]] )) {,...}
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;
constant b:integer:=3;
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;
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.
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:
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);
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:
la chiamata seguente
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.
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.
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:
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;
begin
process is
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;
begin
process is
+
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"
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
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.
–– 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
...
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.
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.
use work.RES_PACK.all;
entity WAND_VHDL is
port(X, Y: in BIT; Z: out RESOLVED_BIT);
end WAND_VHDL;
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:
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:
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;
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;
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.
Initial
Figure 5-3
Value Driver Values
Four State Resolution
with Three Values.
Z H Z L
X Resultant Value
124 Chapter Five
weakest-----------------------------strongest
The system consists of three strengths and three logic values. The three
strengths represent the following:
■ 0 —Logical 0 or false
■ 1 —Logical 1 or true
■ X —Logical unknown
■ 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:
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 );
END ninepack;
The package body contains the resolution function (package bodies are
discussed near the end of this chapter).
END IF;
END LOOP;
RETURN result;
END resolve9;
END ninepack;
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.
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;
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:
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
PACKAGE fourpack IS
TYPE fourval IS (X, L, H, Z);
132 Chapter Five
USE WORK.fourpack.ALL;
ENTITY mux2 IS
PORT( i1, i2, a : IN fourval;
q : OUT fourval);
END mux2;
COMPONENT inv
PORT( a : IN fourval;
b : OUT fourval);
END COMPONENT;
-- resolved signal
SIGNAL intq : resolve fourval := X;
BEGIN
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).
G I DS VDS
VGS S
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.
IDS = 0
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
3
VDD
I DS R
vo
vi
VDS
VGS
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:
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
5
vo
vi
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:
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
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:
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
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:
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;
begin
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;
entity ff_sr is
port (s,r: in bit; q, not_q: out bit);
end entity ff_sr;
q<=q_int;
not_q<=not_q_int;
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;
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:
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];
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;
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:
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;
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);
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.
entity two_phase is
port (ck:in bit; fl, f2:out bit);
end entity two_phase;
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
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;
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.
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;
begin
begin
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:
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
begin
simula:process
begin
clock<='0';
wait for 20 ns;
clock<='1';
wait for 20 ns;
clock<='0';
7
clock<='0';
wait;
end process simula ;
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:
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.
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:
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:
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.
component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit
);
end component cfa;
begin
11
(a=>x_in(0),b=>y_in(0),ci=>c_in,s=>s_out(0),co=>carry_chain(0));
end generate prima_cella;
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
adder_enne_bit::gen1::gen_stat(7)::ultima_cella::cella
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:
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;
entity testbench is
end entity testbench;
begin
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.
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
);
begin
c_out<=carry_chain(enne-1);
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:
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;
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;
component cfa is
port (
a:in bit;
b:in bit;
ci:in bit;
s:out bit;
co:out bit
);
end component cfa;
begin
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;
file di configurazione
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;
begin
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
• Structural modeling
– Component declaration
– Component instantiation
– Design examples
2
Outline of this lecture
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;
For instance:
1. rise and fall delays
2. size of interface ports
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
10
N-input AND gate
11
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;
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;
15
Configurations
entity configuration
binding
architecture-3
architecture-2
architecture-1
16
Component Binding
a combinational z
b logic
Binding Information
carry
Q D
Q Clk
architecture high_speed of comb is
R
• We are concerned with configuring the architecture and not the entity
• Enhances sharing of designs: simply change the configuration
17
1. Configuration specification
2. Configuration declaration
18
Default Binding Rules
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;
Configuration specification
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;
• If all the half adder use the same entity-architecture, then we can use the
following short form:
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;
26
Configuration specification – an example
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
28
Configuration specification – an example
29
Configuration declaration
Syntax:
30
Configuration Declaration
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;
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
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
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.
Modello comportamentale
U - Alfabeto d'Uscita
– È costituito dall'insieme finito e non vuoto dei simboli d'uscita
– Con m linee d'uscita si hanno 2m simboli
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
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
y2 Y2
FF2
yk Yk
FFk
Clock
Memoria di stato
09/04/2008 - 11 - 2007-
2007-08
xn RETE
COMBINATORIA
Ingressi
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à
09/04/2008 - 13 - 2007-
2007-08
Stato Presente St y1 Y1
FF1
INGRESSI FLIP-FLOP
STATO PROSSIMO/
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
. . . . . . . . . . .
09/04/2008 - 15 - 2007-
2007-08
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
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
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
09/04/2008 - 19 - 2007-
2007-08
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
09/04/2008 - 22 - 2007-
2007-08
Passi della Sintesi di una FSM
09/04/2008 - 23 - 2007-
2007-08
09/04/2008 - 24 - 2007-
2007-08
Passi della Sintesi di una FSM: Esempio
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
09/04/2008 - 26 - 2007-
2007-08
Passi della Sintesi di una FSM
09/04/2008 - 27 - 2007-
2007-08
09/04/2008 - 28 - 2007-
2007-08
Passi della Sintesi di una FSM
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
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
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
09/04/2008 - 32 - 2007-
2007-08
Tabella degli stati - Esempio 1 - caso (a)
09/04/2008 - 33 - 2007-
2007-08
(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 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
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
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
0 0
0 1 0; 1 0; 1 0; 1
09/04/2008 - 38 - 2007-
2007-08
Sintesi: Esempio 3
09/04/2008 - 39 - 2007-
2007-08
Sintesi: Esempio 3
09/04/2008 - 40 - 2007-
2007-08
Sintesi: Esempio 3
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 ,
09/04/2008 - 43 - 2007-
2007-08
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
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
09/04/2008 - 48 - 2007-
2007-08
Riduzione del numero degli stati:
stati equivalenti di una stessa macchina
09/04/2008 - 49 - 2007-
2007-08
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
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
b) si ~ sj
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
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
a b c d e f g
09/04/2008 - 59 - 2007-
2007-08
09/04/2008 - 60 - 2007-
2007-08
Riduzione del numero degli stati:
Esempio 1
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
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
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
09/04/2008 - 63 - 2007-
2007-08
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
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
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
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
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
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/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
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
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
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
09/04/2008 - 77 - 2007-
2007-08
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
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
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
09/04/2008 - 82 - 2007-
2007-08
Scelta dell’assegnamento
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
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
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
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
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.
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:
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):
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
fi le d i testo
d ic h iara zion e
d i entità
d efinizio ne
d ell’arch itettu ra
e n tità A
a rc h itettur a A
e n tità E
a rc h itettur a E
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
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:
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”.
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:
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
component nome_componente is
port ( nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale;
nome_segnale : modo tipo_segnale);
end component;
nome_label : nome_componente
port map(segnale1, segnale2, ..., segnaleN);
nome_label : nome_componente
port map(port1 => segnale1, ..., portN => segnaleN);
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
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;
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;
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;
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.
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;
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;
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;
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’.
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;
Fig. 6.3. Descrizione VHDL data flow del contatore di bit ‘1’.
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
Fig. 6.5. Descrizione del contatore di bit ‘1’ con assegnazione selezionata.
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)
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).
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:
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:
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
end beh;
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:
Analogamente, in corrispondenza del fronte di discesa del clock saranno vere le due espressioni
seguenti:
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
end beh;
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
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;
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.
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:
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.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;
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;
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;
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.
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
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.
process (t,g,c,d)
begin
if (g = ‘1’) then
y <= t;
elsif (c = ‘1’) then
y <= d;
end if;
end process;
end beh;
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.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.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.
Fig. 7.20.
31 Corso di Architettura dei Sistemi Integrati. Note sul VHDL
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;
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;
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. 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
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:
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.
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
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:
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
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’;
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:
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;
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:
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.
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;
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
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.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);
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.
architecture beh of s is
begin
y1 <= add(p1,q1);
y2 <= add(p2,q2);
end beh;
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;
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
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
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 mat;
use mat.ArithFunz.all;
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.
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.
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.
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.
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.
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);
Il package std_logic_arith fornisce due funzioni per effettuare shift dei bit di numeri signed ed
unsigned:
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.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.
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
scelta 0 0
leg gi 1 0
scriv i 0 1
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.
begin
end fsm;
La Fig. 13.3 mostra la descrizione VHDL della macchina a stati di Fig. 13.1.
Con le due righe:
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.
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.
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:
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
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
Fig. 13.12.Calcolo delle uscite a partire dallo stato corrente (macchina di Moore)
Registri uscite
Logica per
le uscite di stato
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
Fig. 13.15. Riduzione del ritardo clock-output. Le uscite corrispondano ad alcuni dei bit di
stato.
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.
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
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;
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:
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.
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;
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;
begin
test: process
begin
. . . . . . . . .
. . . . . . . . .
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
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;
begin
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;
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;
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;
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.
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
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.
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.
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
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;
begin
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