Che materia stai cercando?

VHDL linguaggio Appunti scolastici Premium

Il linguaggio VHDL (VLSI Hardware Description Language) è un linguaggio per la descrizione dell’hardware. Questa prima definizione sottolinea un aspetto molto importante: il VHDL non è un linguaggio eseguibile ovvero non descrive quali operazioni un esecutore deve svolgere per ricavare il risultato di una elaborazione, bensì descrive gli... Vedi di più

Esame di Sistemi embedded docente Prof. L. Pomante

Anteprima

ESTRATTO DOCUMENTO

entity full_adder is

generic( delay: real );

port( a, b, cin: in bit;

s, cout: out bit );

end full_adder;

Come nel caso di codice sorgente per la realizzazione di software, è buona norma arricchire la

specifica con commenti che ne chiariscano gli aspetti essenziali. In VHDL i commenti sono

introdotti dal simbolo (due trattini) e si estendono fino alla fine della riga. Una versione

--

commentata della dichiarazione precedente potrebbe essere la seguente:

entity full_adder is

-- Generics

generic(

delay: real -- The full adder delay in ns

);

-- Ports

port(

-- Inputs

a: in bit; -- First operand

b: in bit; -- Second operand

cin: in bit; -- Carry-in

-- Outputs

s: out bit; -- Sum

cout: out bit -- Carry-out

);

end full_adder;

Nei paragrafi seguenti, quando si introdurranno i tipi vettoriali, si presenterà una applicazione dei

generic alla specifica di componenti configurabili orientata alla sintesi e non solo alla

simulazione. La specifica di un componente parametrico, infatti, offre molti vantaggi sia dal

punto di vista della compattezza e della chiarezza, sia dal punto di vista della riusabilità del

codice VHDL. Quest’ultimo aspetto è di particolare rilevanza nella progettazione di sistemi molto

complessi ed le metodologie di specifica di siffatti componenti sono attualmente argomento di

attiva ricerca e studio.

2.2 Architecture

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 interfaccie 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 ed compare la sezione destinata a

begin end implementation,

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.

A titolo di esempio si consideri il modulo full adder usato nell’esempio precedente e se ne dia la

specifica della funzionalità. Una possibilità è la seguente:

architecture first of full_adder is

begin

s <= a xor b xor cin;

cout <= (a and b) or (b and c) or (a and c);

end first;

In questo semplice esempio tutti i segnali utilizzati sono o gli ingressi del modulo o le sue uscite.

Per questo motivo non è necessaria alcuna parte dichiarativa. Per inciso, si noti che i segnali di

ingresso e come richiesto ed eveidenziato in precedenza, sono soltanto letti mentre i

a, b cin,

segnali di uscita e sono soltanto scritti. Consideriamo ora la descrisione seguente:

s cout

architecture second of full_adder is

signal p1, p2, p3: bit

begin

s <= a xor b xor cin;

p1 <= a and b;

p2 <= b and c;

p3 <= a and c;

cout <= p1 or p2 or p3;

end second;

I tre segnali temporanei, utilizzati per il calcolo di risultati intermedi, devono essere dichiarati

esplicitamente nella sezione dichiarativa dell’architecture declaration e devono avere un tipo

conforme al contesto in cui sono utilizzati. Si noti infine che tali segnali sono privi di

direzionalità e possono quindi essere sia letti sia scritti.

Affrontiamo ora un aspetto essenziale delle architecture. La parte implementativa è in generale

costituita da un insieme di statement quali assegnamenti, assegnamenti condizionali, costrutti di

selezione, istanziazioni ecc. Tali statement sono da considerarsi paralleli ovvero il loro risultato è

calcolato in parallelo. Nella specifica di componenti hardware, ciò è una necessità in quanto è

chiaro che tutti gli elementi di un circuito elaborano i propri ingressi in modo inerentemente

parallelo. Un primo esempio, molto semplice, chiarisce questo concetto:

architecture par_one of dummy is

begin

x <= a and b and c;

y <= d or e;

end par_one;

Questa definizione corrisponde alla realizzazione circuitale seguente:

a

b x

c

d y

e

Le due porte logiche calcolano ovviamente il valore delle uscite in modo parallelo. Risulta perciò

evidente che invertire l’ordine degli assegnamenti nell’architecture non influisce in alcun modo

sul risultato. Un caso leggermente più complsso è mostrato dalla architecture seguente:

architecture par_two of dummy is

signal t: bit;

begin

t <= a and b;

x <= t and c;

y <= d or e;

end par_two;

Il circuito che ne risulta è mostrato nella figura seguente:

a

b x

c

d y

e

Dallo schema circuitale appare chiaro che il valore del risultato è disponibile solo dopo che la

x

prima porta AND ha calcolato il prodotto logico e dopo che la seconda ha utilizzato

a and b

tale risultato per produrre il valore finale. Questo è esatto solo in parte, infatti la seconda porta

AND calcola continuamente il prodotto logico dei suoi ingressi, a prescindere da come e quando

tali ingressi sono calcolati. L’affermazion precedente riguardo il calcolo del risultato deve

x

quindi essere riformulata dicendo che il risultato è sempre disponibile e viene ricalcolato in

x

modo continuo. Tuttavia una variazione sugli ingressi o si ripercuoterà sull’uscita

a, b c

aggiornando il valore di secondo lo schema di valutazione delle espressioni che implica una

x

dipendenza tra i dati. In altre parole è vero che le due porte AND e la porta OR elaborano gli

ingressi in parallelo, anche se la dipendenza tra i vari segnali impone un ordine di propagazione

dei risultati intermedi.

Secondo tale principio, quindi, è possibile anche in questo caso scambiare l’ordine delle

espressioni senza modificare il comportamento del circuito risultante. La seguente architecture è

del tutto equivalente alla precedente:

architecture par_three of dummy is

signal t: bit;

begin

y <= d or e;

x <= t and c;

t <= a and b;

end par_three;

3. Tipi

Il VHDL dispone di un numero elevato di tipi. Ai fini della sintesi, tuttavia, solo pochi di essi

risultano utlizzabili in quanto riconosciuti dagli strumenti di sintesi automatica. Nei seguenti

paragrafi sono descritti i tipi base del linguaggio VHDL e sono introdotti alcuni concetti di base

riguardo le slices ed i tipi definititi dall’utente.

3.1 Il tipo bit

Il tipo è il più semplice tipo disponibile in VHDL. Tale tipo rappresenta un valore binario e

bit

può unicamente assumere i valori logici 0 ed 1. Si noti che le costanti 0 ed uno devono essere

racchiuse tra singoli apici (quindi e per distinguerle dai valori numerici interi 0 ed 1.

’0’ ’1’)

Gli operatori definiti per tale tipo sono soltanto gli operatori di assegnamento, gli operatori

confronto e gli operatori logici. I seguenti statement sono validi:

x <= a and b;

y <= ’1’;

z <= a xor (b and not c);

Quelli che seguono sono invece scorretti ed i commenti indicano il motivo:

x <= a + b; -- L’operatore + non è definito per il tipo bit

y <= 1; -- Le costanti di tipo bit richiedono gli apici

Spesso è utile raggruppare più segnali sotto un nome comune, ovvero utilizzare un array. Il

linguaggio VHDL dispone a tale scopo del tipo Un bit vector è sostanzialmente

bit_vector.

un insieme di segnali contraddistinti da un nome comune e da un indice. È possibile accedere in

lettura o in scrittura ai vari elementi del vettore mediante l’uso di indici. Analizziamo anzitutto la

sintassi della dichiarazione di un segnale di tipo bit vector.

signal_name: bit_vector( index1 {to|downto} index2 );

La dichiarazione specifica un segnale composto formato da segnali

index2-index1+1

semplici di tipo bit. Inoltre il vettore ha un ordinamento che è determinato dalla paraola chiave

o nella parte dichiarativa degli indici.

to downto

L’informazione relativa all’ordinamento determina quale deve essere considerato il bit più

significativo. Utilizzando la forma il bit più significativo è quello con

index1 to index2

indice metre nella forma il bit più significativo è quello in posizione

index1, downto

È sottinteso che tale distinzione ha senso solo quando il valore del vettore è interpretato

index2.

nell’insieme piuttosto che come collezione di singoli bit. Questo accade ad esempio quando si

utilizza un bit vector per rappresentare un valore numerico intero in codifica binaria naturale o in

complemento a 2.

Dalla forma della dichiarazione appare evidente che gli indici iniziale e finale del vettore possono

essere assolutamente arbitrari, purché interi, non negativi e consistenti con la specifica direzione.

Le seguenti dichiarazioni sono quindi valide:

a: bit_vector( 0 to 3 ); -- 4 elementi

b: bit_vector( 17 to 24 ); -- 8 elementi

c: bit_vector( 16 downto 1 ); -- 16 elementi

mentre le seguenti non lo sono:

a: bit_vector( 3 to 0 ); -- Errore: 3 > 0

b: bit_vector( -4 to +4 ); -- Errore: -2 negativo

c: bit_vector( 15 downto ’0’ ); -- Errore ’0’ non è un intero

Per riferirsi ad un elemento specifico del vettore si utilizza l’indice dell’elemento, racchiuso tra

parentesi tonde. Così l’elemento 5 del vettore si indica con È sottinteso che l’accesso ad

a a(5).

un elemento al di fuori dei limiti del vettore costituisce un errore.

Il tipo (e di conseguenza il tipo presenta alcune limitazioni. In particolare

bit bit_vector)

non consente di specificare condizioni di indifferenza e di alta impedenza. Si noti che

quest’ultimo problema non dovrebbe essere contemplato a livello di sintesi logica bensì ad un

livello più basso e precisamente a livello elettrico. Nonostante ciò, spesso accade di dover

specificare componenti che in condizioni particolari presentano in uscita non tanto un valore

logico bensì una alta impedenza, ad indicare un disaccoppiamento tra gli elementi che il

componente connette. Vedremo nel seguito che questo problema è superato dai tipi risolti e che il

tipo bit, nella pratica, è utilizzato molto raramente.

3.2 Il tipo integer

Il tipo rappresenta valori interi a 32 bit e può essere utilizzato per la sintesi. A tale

integer

proposito si presentano i due seguenti problemi. In primo luogo deve essere definito comen

interpretare un valore binario su 32 bit dal punto di vista del segno. Per default, i valori

rappresentati sono interi senza segno. Il secondo problema riguarda lo spreco di risorse che deriva

dal fatto che ogni volta che un segnale intero viene utilizzato, lo strumento di sintesi lo considera

come un bus a 32 bit e istanzia connessioni e componenti confacenti a tale dimensione.

Nella specifica di un contatore modulo 8, ad esempio, l’utilizzo di un segnale di tipo integer

produrrebbe la sintesi di un sommatore a 32 bit e di un banco di 32 registri benché 3 bit sarebbero

sufficienti.

3.3 Tipi IEEE

Come accenntao in precedenza, a volte è necessario anche ai fini della sintesi, specificare come

valore di un segnale un valore diverso da 0 e da 1. Il VHDL non dispone di un tale tipo, tuttavia

esiste una libreria standard, la libreria che definisce alcuni tipi aggiuntivi. I tipi in esame

IEEE,

utilizzano un sistema logico a 9 valori, descritti di seguito:

Valore logico 0.

’0’ Valore logico 1.

’1’ Alta impedenza.

’Z’ Indeterminato. Può essere 0 o 1.

’X’ Indefinito. Il valore non è mai stato assegnato.

’U’ Segnale debole. Non è possibile interpretarlo come 0 o 1.

’W’ Segnale debole. Interpretabile come 0.

’L’ Segnale debole. Interpretabile come 1.

’H’ Don’t care.

’-’

Di questi, quelli di maggiore interesse e di uso più comune sono, oltre ai valori logici ed il

0 1,

valore di alta impedenza ed i due valori di don’t care e Si noti che tra il valore

’Z’ ’X’ ’-’.

indeterminato ’X’ ed il valore indeterminato ’-’ esiste una differenza sostanziale: il primo viene

generato da qualsiasi simulatore ogni volta che non è possibile determinare il valore di un

segnale, mentre il secondo può essere solo assegnato e non generato in simulazione. Tale

differenza non ha alcun effetto ai fini della sintesi.

La libreria come ogni libreria VHDL, è suddivisa in package ognuno dei quali definisce

IEEE,

alcuni oggetti che possono esssere utilizzati nella progettazione. In particolare il package

definisce i due tipi risolti e ed i correspondenti

std_logic_1164 std_logic std_ulogic

tipi vettoriali ed

std_logic_vector std_ulogic_vector.

Di particolare interesse sono i tipi e in quanto sono tipi

std_logic std_logic_vector

risolti e sono supportati sia dagli strumenti di sintesi, sia dagli strumenti di simulazione. Per

chiarire il concetto di tipo risolto faremo riferimento ad un caso tipico del suo utilizzo. Si

consideri un bus sul quale sono connessi più dispositivi in wired-or, come nella figura seguente:

D1 D2 D3

B

U

S

Ogni linea del bus è quindi connessa a più dispositivi open-collector, i quali possno forzare valori

logici diversi in modo simultaneo, realizzando qello che si chiama wired-or. Benché questa

situazione non abbia alcun senso dal punto di vista della progettazione logica, dal punto di vista

elettrico è non solo sensata ma anche molto utilizzata e a volte persino necessaria. Supponiamo,

ad esempio, che un modulo mandi in uscita su una certa linea il valore logico 1 metre, nello stesso

istante, un altro modulo manda in uscita sulla stessa linea il valore logico 0.

1 0 ?

Quale è il valore assunto dalla linea in questione? La risposta è che dipende dalle caratteristiche

elettriche dei dispositivi, dai livelli di tensione utilizzati, dalla scelta di una logica positiva o

negativa e da altri fattori che comunque esulano dal progetto logico. I tipi risolti permettono di

definire il valore assunto dalla linea in questione mediante un insieme di regole associate alla

logica a nove valori descritta in precedenza.

Bisogna sottolineare un altro aspetto importante: proprio per l’impossibilità di stabilire il valore

risultatnte da una situazione di questo tipo, il linguaggio VHDL proibisce la connessione di più

segnali in modalità wired-or, a meno che per il tipo dei segnali in questione non siano define una

o più funzioni di risoluzione.

Dopo questa breve introduzione alla logica a 9 valori e ai concetti di tipo risolto e di funzione di

risoluzione, vediamo come possono essere usati i tipi definiti dal package std_logic_1164

della libreria Tali tipi, sono dal punto di vista logico, esattamente equivalenti al tipo

IEEE. bit

ed al tipo Anche per essi, infatti, sono definite le operazioni di assegnamento, di

bit_vector.

confronto e le operazioni logiche fondamentali.

La libreria dispone di tre package (std_logic_unsigned, e

IEEE std_logic_signed

di cui parleremo più dettagliatamente nel seguito, che ridefiniscono

std_logic_arith),

opportunamente alcuni operatori aritmetici e di confronto per questi tipi,rendendoli

particolarmente adatti alla specifica di moduli ai fini della sintesi. Grazie a tali package,

combinati con il package è possibile definire, ad esempio, tre segnali e

std_logic_1164 a, b

di tipo e di dimensioni identiche e scrivere uno statement come:

c std_logic_vector

c <= a + b;

intendendo con questo assegnare a la rappresentazione binaria del risultato della somma dei

c

valori numerici le cui rappresentazioni binarie sono i segnali e Affronteremo meglio nel

a b.

seguito questo tipo di problematiche.

3.4 Tipi definiti dall’utente

Un tipo definito dall’utente altro non è che un nuovo tipo le cui caratteristiche devono essere

specificate dall’utente, il quale deve, eventualmente, farsi carico di ridefinire alcuni operatori in

modo da poterli utilizzare su segnali del nuovo tipo. Il VHDL è un linguaggio molto flessibile e

come tale dispone di diversi meccanismi di costruzione di tipi. Tuttavia, ai fini della specifica di

sistemi di media e bassa complessità è sufficiente considerare due soli meccanismi: il subtyping

e l’enumeration.

Il subtyping consiste nel definire un nuovo tipo come equivalente ad un tipo esistente limitando

l’intervallo di variazione di quest’ultimo. Un tale meccanismo ha senso prevalentemente per i tipi

interi ed in particolare per il tipo La sintassi generale è la seguente:

integer.

subtype new_type_name is type_name range val1 to val2;

Tale definizione indica che il tipo è equivalente al tipo salvo

new_type_name type_name

per il fatto che può rappresentare solo i valori compresi tra e Nella pratica, il caso

val1 val2.

senza dubbio più utile riguarda il subtypeing per gli interi al fine di utilizzare per la loro

rappresentazione binaria il minor numero possibile di bit. Volendo ad esempio, rappresentare un

valore intero in codifica binaria naturale su 5 bit si può definire un subtype del tipo integer con

5

estremi 0 e 2 -1=31, ovvero:

subtype small_integer is integer range 0 to 31;

Benchè questa definizione introduca un tipo che può essere visto a tutti gli effetti come un vettore

di 5 bit l’uso di segnali associati ad esso è sconsigliato rispetto all’alternativa di utilizzare segnali

di tipo oppure come l’analogo

std_logic_vector(0 to 4) bit_vector(0 to 4).

Il secondo meccanismo di costruzione dei tipi prevede la definizione di un nuovo tipo mediante

l’enumerazione di tutti i valori – simbolici – che esso può assumere. La sintassi per una tale

definizione è la seguente:

type new_type_name is ( val0, val1, ..., valN );

In questa definizione ecc. rappresentano identificatori generici che possono essere

val1, val2

interpretati come costanti simboliche. Si badi che a priori non è definita alcuna associazione tra

tali valori simbolici e un qualsiasi equivalente numerico, né dal punto di vista del valore numerico

e neppure dal punto di vista del numero di bit impiegati per rappresentarlo. Per questo motivo i

tipi enumerativi devono essere usati con cognizione di causa altrimenti possono essere fonte di

gravi errori.

Un caso tipico di utilizzo di un tipo enumerato si presenta nella descrizione delle macchine a stati

finiti in cui spesso, la specifica è spesso fornita sotto forma di tabella degli stati in cui gli stati

hanno un nome simbolico e tale lo si vuole mantenere fino alla scelta di un codice e di una

codifica specifiche. Come esempio si consideri una macchina a stati costituita dai 5 stati RES,

INIT, COMP, ERR e OK. La dichiarazione di un tipo adatto a rappresentare tali stati sarebbe:

type status is ( RES, INIT, COMP, ERR; OK );

Per un segnale pres di questo tipo un assegnamento come:

pres <= INIT;

costituisce uno statement corretto, così come un confronto come

if( pres = ERR ) ...

è una condizione logica valida. Infine è importante sottolinare un ulteriore aspetto dei tipi

enumerativi. Benchè né il codice né la codifica siano assegnati a priori ai simboli che definiscono

il tipo, tuttavia è fissato l’ordinamento. Questa circostanza permette di compiere due operazioni:

la definizione di un sottotipo enumerativo ed il confronto tra segnali di tipo enumerativo. Una

definizione valida di sottotipo potrebbe essere la seguente:

type states is ( INIT, S0, S1, S2, S3, WRITEOUT, FINISHED );

type kernel_states is states range S0 to WRITEOUT;

Per quanto concerne invece il confronto, va tenuto presente che lo strumento di sintesi assegna

implicitamente la codifica binaria naturale ai simboli definiti basandosi sull’ordine in cui essi

appaiono nella definizione. Quindi, nel caso del tipo states dell’esempio precedente, si assume la

codifica seguente:

INIT 000

S0 001

S1 010

S2 011

S3 100

WRITEOUT 101

FINISHED 110

Come regola generale è quindi bene non utilizzare gli operatori di confronto tra valori simbolici

di un tipo enumerativo. Per la stessa ragione è sconsigliato utlizzare operatori aritmetici tra

segnali di tipo enumerativo.

4. Reti combinatorie

4.1 Slice e concatenamento

Prima di entrare nel dettaglio della specifica delle reti combinatorie è utile ntrodurre il concetto di

slice e l’operazione di concatenamento di segnali. Una slice è una porzione di un vettore,

ovvero, dal punto di vista circuitale, un sottoinsieme delle linee di un segnale composto. Per

specificare una slice si usa la sintassi seguente:

signal_name( index1 {to|downto} index2 )

in cui ed devono essere indici validi per il vettore e devono

index1 index2 signal_name

rispettare l’oridnamento imposto dalla clausola o Si consideri, ad esempio, la

to downto.

seguente porzione di architecture:

architeture rtl of dummy is

signal BUS: std_logic_vector(0 to 31);

signal B0, B1, B2, B3: std_logic_vector(0 to 7);

begin

B0 <= BUS(0 to 7);

B1 <= BUS(8 to 15);

B2 <= BUS(16 to 23);

B3 <= BUS(31 downto 24);

end rtl;

Questo corrisponde, in termini circuitali alla situazione mostrata in figura. Si noti che gli elementi

del vettore corrispondono agli elementi del vettore tra 24 e 31, presi in ordine

B3 BUS

decrescente, come specificato dalla clausola downto.

BUS

0 7 8 15 16 23 24 31 B0(0)

B0(7)

B1(0)

B1(7)

B2(0)

B2(7)

B3(7)

B3(0)

Il meccanismo delle slice è spesso utilizzato per estrarre, sotto un nome comune e più

significativo alcune liee di un bus. Un esempio tipico è quello mostrato, in un bus che porta una

intera word (32 bit) è spezzato nei byte (8 bit) che la compongono.

Il meccanismo del concatenamento funziona in modo inverso, ovvero permette di raggruppare più

segnali sotto uno stesso nome. L’operatore VHDL per il concatenamento è l’ampersand (&). Ad

esempio se si volesse ricomporre un nuovo bus a partire dai quattro byte mostrati

BUS2

nell’architettura precedente, scambiandone l’ordine (cioè con come più significativo e

B3 B0

come byte meno significativo si può ricorrere ad una espressione come la seguente:

BUS2 <= B3(7 downto 0) & B2 & B1 & B0;

L’esempio suppone che sia stato dichiarato come di tipo ed avente

BUS2 std_logic_vector

32 elementi. In altri casi, tale operatore è utilizzato per raccogliere sotto uno stesso nome segnali

indipendenti al fine di poter effettuare confronti più rapidi. Si consideri, a tal proposito, il caso in

cui si deve verificare se a vale 0, b vale 1 e c vale 0. Sono possibili due soluzioni. La prima è

semplicemente la traduzione della condizione direttamente in VHDL, cioè:

if ( a = ’1’ and b = ’0’ and c = ’1’ ) then ...

tale soluzione è accettabile e comoda se il test sulle tre variabili è uno solo. Nel caso in cui si

vogliano verificare diverse condizioni sui valori di a, b e c allora conviene adottare il meccanismo

del concatenamento. Per prima cosa si dichiara un segnale temporaneo della dimensione

adeguata, nel caso in esame un segnale di 3 bit:

signal temp: std_logic_vector(0 to 2);

quindi si costruisce il vettore temporaneo concatenando le variabili originali:

temp <= a & b & c;

ed infine si sccrivono tutte le condizioni nella forma compatta mostrata di seguito:

if ( temp = ”101” ) then ...

Questo stile è più leggibile e più sintetico del precedente ed è utilizzato spesso.

4.2 Espressioni logiche

Le espressioni logiche possono essere tradotte in VHDL in modo molto semplice e diretto.

Ricordiamo dapprima che per rappresentare correttamente variabili booleane si usano i tipi e

bit

In rari casi può essere neccessario o convniente utilizzare anche il tipo non risolto

std_logic. Gli operatori VHDL disponibili per la costruzione di espressioni logiche ed il

std_ulogic.

loro significato sono riassunti nella tabella seguente:

Operatore logico Operatore VHDL Espressione logica Espressione VHDL

and, ab, a•b

and a and b

or, + a+b

or a or b

not, ’,! !a, a’

not not a

⊕ ⊕

xor, a b

xor a xor b

L’espressione logica F = ac’ + d( a’ + b’c) ad esempio, è tradotta in VHDL dall’assegnamento:

F <= a and (not c) or d and ( (not a) or ((not b) and c) );

Alcune delle parentesi di tale espressione possono essere rimosse in quanto la valutazione

dell’espressione segue le regole di precedenza e associatività definite per gli operatori

dell’algebra booleana. Tuttavia una scirittura come quella mostrata non modifica l’espressione

pur aumentandone la chiarezza e la leggibilità. Una espressione logica descrive come alcuni

segnali devono essere combinati per produrre altri segnali e ciò è realizzato mediante l’operatore

di assegnamento tra segnali Le espressioni logiche quindi appartengono allo stile di specifica

<=.

a livello RTL.

4.3 Tabelle della verità

Una tabella della verità è in sostanza una lista di condizioni sulle variabili di ingresso alle quali è

subordinato il valore della funzione che si sta specificando. La seguente tabella della verità:

a b f

0 0 1

0 1 0

1 0 1

1 1 1

può quindi essere letta nel seguente modo: se vale 0 e vale 0 allora vale 1, altrimenti se

a b f a

vale 0 e vale 1 allora vale 0, altrimenti ecc. Questo modo di leggere la tabella della verità

b f

ammette una rappresetazione molto semplice in VHDL grazie al costrutto di assegnamento

condizionale la cui è la seguente:

signal_name <= const_1 when cond_1 else

const_2 when cond_2 else

...

const_N when cond_N else

const_default;

In tale costrutto il segnale assume i valori costanti ...,

signal_name const_1, const_N,

a seconda del verificarsi o meno delle condizioni ..., In

const_default cond_1, cond_N.

particolare, se nessuna delle consdizioni è vera, allora ilsegnale assume il valore di default

const_default.

La tabella della verità dell’esesempio è quindi tradotta in VHDL dal seguente assegnamento:

f <= ’1’ when a=’0’ and b=’0’ else

’0’ when a=’0’ and b=’1’ else

’1’ when a=’1’ and b=’0’ else

’1’ when a=’1’ and b=’1’;

A questo punto è importante notare che il linguaggio VHDL prevede che nelle espressioni di

assegnamento condizionato destinate a rappresentare reti puramente combinatorie, tutti i possibili

casi siano esplicitamente enumerati. Torniamo ora all’esempio appena mostrato. Se le variabili a

e b sono di tipo bit, l’assegnamento è corretto in quanto il tipo bit prevede solo i due valori 0 ed 1

e quindi tutti i possibili casi sono stati menzionati. Al conrario, se le variabili a e b fossero di tipo

std_logic, l’assegnamento non sarebbe corretto in quanto iltipo in questione ammette nove

possibili valori. Esplicitare tutti i casi possibili vorrebbe dire in questo caso scrivere tutte le 81

possibili condizioni. Una possibile soluzione potrebbe essere quella di esprimere esplicitamente

solo le prime 3 condizioni ed assegnare un valore di default alla quarta condizione e a tutte quelle

non esplicitamente citate. Si avrebbe, secondo questo schema:

f <= ’1’ when a=’0’ and b=’0’ else

’0’ when a=’0’ and b=’1’ else

’1’ when a=’1’ and b=’0’ else

’1’;

Seguendo tale strategia è possibile anche semplificare la scrittura di una tabella della verità

prendendo in considerazione solamente l’on-set oppure l’off-set. Nel caso in esame si potrebbe

scrivere un assegnamento del tipo:

f <= ’0’ when a=’0’ and b=’1’ else

’1’;

Questo approccio, tuttavia, non è la mgiliore soluzione possibile in termini di chiarezza ed inoltre

è applicabile solo alle funzioni completamente specificate, cioè quelle per cui il dc-set è vuoto.

Consideriamo ora una tabella della verità per una funzione non completamente specificata:

a b f

0 0 1

0 1 0

1 0 -

1 1 1

Al fine di permettere allo strumento di sintesi di individuare la soluzione ottima per tale funzione

è necessario esprimere le condizioni di indifferenza presenti nella specifica. Come prima

possibilità, ripercorrendo le varie soluzioni proposte per le funzioni completamente specificate,

consiste nell’esprime esplicitamente tutti i valori assunti dalla funzione in corrispondenza delle

possibili combinazioni dei valori in ingresso. Si noti, a tal proposito, che in qusto caso è

necessario esprimere il concetto di condizione di indifferenza e ciò non è previsto dal tipo bit

per cui è necessario ricorrere a segnali di tipo Tale tipo infatti prevede due

std_logic.

rappresentazioni per un valore indeterminato e cioè e Nella situazione in esame

’X’ ’-’.

entrambi possono essere usati indifferentemente. Una possibile soluzione è quindi la seguente:

f <= ’1’ when a=’0’ and b=’0’ else

’0’ when a=’0’ and b=’1’ else

’-’ when a=’1’ and b=’0’ else

’1’ when a=’1’ and b=’1’;

Questa soluzione, tuttavia, soffre del problema già visto e cioè non esprime tutti i possibili casi.

Una scelta migliore è la seguente:

f <= ’1’ when a=’0’ and b=’0’ else

’0’ when a=’0’ and b=’1’ else

’-’ when a=’1’ and b=’0’ else

’1’;

Volendo, infine, specificare la tabella nella forma più sintetica possibile, si ricorre alla forma in

cui solo due insiemi tra on-set, off-set e dc-set sono esplicitamente specificati. Scegliendo, ad

esempio, off-set e dc-set si avrebbe:

f <= ’0’ when a=’0’ and b=’1’ else

’-’ when a=’1’ and b=’0’ else

’1’;

Scegliendo invece on-set ed off-set si avrebbe l’assegnamento:

f <= ’1’ when a=’0’ and b=’0’ else

’1’ when a=’1’ and b=’1’ else

’0’ when a=’0’ and b=’1’ else

’-’;

Il linguaggio VHDL dispone di un costrutto alternativo per l’assegnamento condizionato che

offre la possibilità di sfruttare espressioni di confronto sintetiche mediante l’uso dell’operatore di

concatenamento. La sua sintassi generale è la seguente:

with test_signal select

signal_name <= const_1 when case_1,

const_2 when case_2,

...

const_N when case_N,

const_default when others;

Il significato è il seguente: il valore di è confrontato con le costanti ...,

test_signal case_1,

ed al segnale è assegnato il corrispondente valore di una delle costanti

case_N signal_name

..., Se è deiverso da tutte le costanti ...,

const_1, const_N. signal_test case_1,

allora a è assegnato il valore di È evidente che la

case_N signal_name const_default.

semantica di questo costrutto è equivalente a quella dell’assegnamento condizionato appena visto,

tuttavia, come modtra l’esempio seguente, questa forma è spesso più sintetica e leggibile.

La tabella della verità:

a b f

0 0 1

0 1 0

1 0 -

1 1 1

può essere codificata nel modo seguente. Innanzitutto è necessario definire un segnale vettoriale

temporaneo destinato a contenere la concatenazione delle variabili su cui si deve effettuare il

confronto, nel caso in esame le variabili e Ciò e realizzabile come segue:

a b.

architecture rtl of dummy is

signal temp: std_logic_vector(0 to 1);

begin

temp <= a & b;

Quindi si procede alla verifica delle condizioni non più sulle variabili di ingresso originali, bensi

sul nuovo vettore temporaneo, ovvero:

with temp select

f <= ’1’ when ”00”,

’0’ when ”01”,

’-’ when ”10”,

’1’ when ”11”,

’-’ when others;

Si nota immediatamente che questa forma offre una rappresentazione immediate di una tabella

della verità. Inoltre è bene sottolineare l’imoprtanza dell’ultima clausola del costrutto: in primo

luogo essa è necessaria in quanto si sta utilizzando un segnale in logica a 9 valori e quindi

molticasi non sono contemplati esplicitamente; in secondo luogo è bene notare che è buona

norma non accorpare il dc-set con tale clausola, principalmente per ragioni di chiarezza. La parola

chiave come sarà chiaro a questo punto, è una forma compatta per indicare tutti i casi

others,

non menzionati esplicitamente.

4.4 Tabelle di implicazione

Consideriamo ora il caso in un priority encoder da 6 a 3 bit. Il comportamento di tale elemento

può essere descritto in modo molto compatto mediante una tablla delle implicazioni. La tabella

6

della verità, per contro, avrebbe un numero molto elevato di righe (2 =64). La tabella in questione

è la seguente:

a b c d e f f0 f1 f2

0 0 0 0 0 0 0 0 0

0 0 0 0 0 1 0 0 1

0 0 0 0 1 - 0 1 0

0 0 0 1 - - 0 1 1

0 0 1 - - - 1 0 0

0 1 - - - - 1 0 1

1 - - - - - 1 1 0

Nell’esempio assumiamo che la funzione da realizzare sia specificata unicamente mediante

variabili scalari. In tal caso si dovrà ricorrere a due variabili temporanee che raccolgano in vettori

gli ingressi e le uscite. Vediamo ora la specifica completa, partendo dalla entity declaration.

entity pri_enc is

port( a: in std_logic;

b: in std_logic;

c: in std_logic;

d: in std_logic;

e: in std_logic;

f: in std_logic;

f0: out std_logic;

f1: out std_logic;

f2: out std_logic

);

end pri_enc;

L’achitecture che specifica il comportamento traducendo di fatto la tabella delle implicazioni è

quindi la seguente:

architecture rtl of pri_enc is

signal enc_in: std_logic_vector(0 to 5);

signal enc_out: std_logic_vector(0 to 2);

begin

-- Concatenates inputs into enc_in

enc_in <= a & b & c & d & e & f;

-- Implements the behaviour

with enc_in select

enc_out <= ”000” when ”000000”,

”001” when ”000001”,

”010” when ”00001-”,

”011” when ”0001--”,

”100” when ”001---”,

”101” when ”01----”,

”110” when ”1-----”,

”---” when others;

-- Splits the output vector enc_out

f0 <= enc_out(0);

f1 <= enc_out(1);

f2 <= enc_out(2);

end rtl;

Questo esempio mostra chiaramente come il costrutto … sia sintetico e leggibile.

with select

4.5 Moduli di uso comune

In questo paragrafo sono raccolte le descrizioni, opportunamente commentate, di alcuni

componenti combinatori di uso comune.

4.5.1 Decoder N

Un decoder è un dispositivo con N linee di ingresso e 2 linee di uscita, idealmente numerate da 0

N

a 2 -1. Il funzionamento è il seguente: tutte le linee di uscita hanno valore 0 tranne quella il cui

indice è fornito in ingresso in codifica binaria naturale. Il simbolo di un decoder 3-a-8, cioè con

tre bit di ingresso e 8 di uscita, è il seguente: Y(0)

X(0) Y(1)

X(1)

X(2) Y(7)

Il comportamento di un decoder può essere descritto mediante una tabella della verità.

x y

000 00000001

001 00000010

010 00000100

011 00001000

100 00010000

101 00100000

110 01000000

111 10000000

Si può anche vedere un decoder come un elemento che trasforma una codifica binaria naturale in

una codifica one-hot. Basandosi sulla tabella della verità è immediato passare alla specifica in

linguaggio VHDL:

entity decoder_3_8 is

port( dec_in: in std_logic_vector(0 to 2);

dec_out: out std_logic_vector(0 to 7)

);

end decoder_3_8;

architecture rtl of decoder_3_8 is

begin

with dec_in select

dec_out <= ”00000001” when ”000”,

”00000010” when ”001”,

”00000100” when ”010”,

”00001000” when ”011”,

”00010000” when ”100”,

”00100000” when ”101”,

”01000000” when ”110”,

”10000000” when ”111”,

”--------” when others;

end rtl;

Questo tipo di elemento è spesso utilizzato per decodificare un indirizzo di una memoria. Gli

ingressi sono appunto l’indirizzo di memoria in codifica binaria naturale mentre l’uscita è tale da

attivare una sola delle linee della memoria cui si vuole accedere.

4.5.2 Priority encoder

Un priority encoder è un componente con N linee di ingresso e M=log (N+1) linee di uscita. La

2

funzione svolta da tale elemento è quella di fornire in uscita la posizione dell’uno più

significativo presente sulle linee di ingresso. Il simbolo normalmente utilizzato è il seguente:

X(0) Y(0)

X(1) Y(1)

PRI Y(M-1)

X(N-1)

Riportiamo di seguito la specifica di un priority encoder con 6 bit di ingresso e 3 di uscita. Per

quanto riguarda i signali di ingresso/uscita, la scelta migliore è quella di utlizzare direttamente

vettori, come mostra l’entity declaration seguente:

entity pri_enc is

port( enc_in: in std_logic_vector(0 to 5);

enc_out: out std_logic_vector(0 to 2)

);

end pri_enc;

Nei paragrafi cprecedenti si sono già presentate alcune possibili descrizioni delle architecture di

un tale componente. Avendo scelto segnali vettoriali, la realizzazione della specifica del

comportamento risulta molto semplificata in quanto non è necessario ricorrere a slicing e

concatenamento. Il codice seguente mostra una possibile realizzazione.

architecture rtl of pri_enc is

begin

with enc_in select

enc_out <= ”000” when ”000000”,

”001” when ”000001”,

”010” when ”00001-”,

”011” when ”0001--”,

”100” when ”001---”,

”101” when ”01----”,

”110” when ”1-----”,

”---” when others;

end rtl;

4.5.3 Parity encoder

Un parity encoder è un elemento che calcola la parità della parola in ingresso. La parità di parola,

ovvero di una stringa di bit, è una indicazione del numero di uno che compaiono nella stringa.

Esistono due tipi di parità: la parità pari e la parità dispari. Nel primo caso il bit di parità vale 0 se

il numero di uno presenti nella parola d’ingresso è pari, mentre nel secondo caso vale zero se tale

numero è dispari. Per tale elemento non esiste un simbolo standard. La tabella della verità che

descrive il comportamento di un parirty encoder a parità pari è la seguente:

x y

000 0

001 1

010 1

011 0

100 1

101 0

110 0

111 1

La specifica in VHDL segue lo schema generale adottato per le tabelle della verità, ovvero:

entity parity_enc is

port( enc_in: in std_logic_vector(0 to 2);

enc_out: out std_logic );

end parity_enc;

architecture rtl of parity_enc is

begin

with enc_in select

enc_out <= ’0’ when ”000”,

’1’ when ”001”,

’1’ when ”010”,

’0’ when ”011”,

’1’ when ”100”,

’0’ when ”101”,

’0’ when ”110”,

’1’ when ”111”,

’-’ when others;

end rtl;

Nella seconda parte di questa dispensa vedremo ome genralizzare un parity encoder a parole di

lunghezza variabile mediante l’uso di elementi sequenziali.

4.5.4 Multiplexer

Negli esempi mostrati, al segnale di uscita sono sempre state assegnate delle costanti. Questa

circostanza tuttavia è un caso particolare. Un multiplexer, infatti, altro non è che un caso più

generale di quanto appena visto in cui un insieme di segnali di selezione permettono di portare

sull’uscita uno dei segnali di ingresso. Il simbolo comunemente utilizzato mostrato nella figura

seguente per due multiplexer differenti: 2-a-1 e 4-a-1.

A

A B

Y Y

C

B D

S S0 S1

Il comportamento di un multiplexer 2-a-1 è riassunto dalla seguente tabella:

s y

0 a

1 b

La tabella ha il seguente significato. Il segnale è il segnale di selezione, e sono gli ingressi

s a b

ed è l’uscita. Il seganle è di un bit, mentre sulla dimensione degli ingressi e dell’uscita non è

y s

stato specificato nulla. Supponiamo dapprima che anch’essi siano di un bit. Il VHDL che descrive

un simile comportamento è molto semplice. Vediamo nzitutto la entity declaration:

entity mux_2_1 is

port( s: in std_logic;

a, b: in std_logic;

y: out std_logic );

end mux_2_1;

Una possibile architecture utilizza il costrutto di assegnamento condizionato:

architecture rtl of mux_2_1 is

begin

y <= a when s=’0’ else

b when s=’1’ else

’-’;

end rtl;

Una soluzione alternativa sfrutta il costrutto ... come mostra il codice seguente:

with select,

architecture rtl of mux_2_1 is

begin

with s select

y <= a when ’0’,

b when ’1’,

’-’ when others;

end rtl;

Passiamo ora alla descrizione di un multiplexer 4-a-1. Il suo comportamento è il seguente:

s0 s1 y

0 0 a

0 1 b

1 0 c

1 1 d

Vediamo ora la descrizione VHDL di tale multiplexer 4-a-1.

entity mux_4_1 is

port( s0, s1: in std_logic;

a, b, c, d: in std_logic;

y: out std_logic

);

end mux_4_1;

architecture rtl of mux_4_1 is

begin

y <= a when s0=’0’ and s1=’0’ else

b when s0=’0’ and s1=’1’ else

c when s0=’1’ and s1=’0’ else

d when s0=’1’ and s1=’1’ else

’-’;

end rtl;

Questa soluzione ricalca la rappresentazione vista per le tabelle della verità con la sola differenza

chei i valori assegnati al segnale di uscita non sno costanti bensì segnali. Anche in questo caso

y

il costrutto ... offre la possibilità di rendere la specifica più sintetica e leggibile,

with select

come mostra la seguente architecture.

architecture rtl of mux_4_1 is

signal sel: std_logic_vector(0 to 1);

begin

with sel select

y <= a when ”00”,

b when ”01”,

c when ”10”,

d when ”11”,

’-’ when others;

end rtl;

Supponiamo ora che i segnali di ingresso, e di conseguenza il segnale di uscita, non siano di un

bit bensì siano segnali a 8 bit. La nuova entity per il multiplexer in esame diventa la seguente:

entity mux_4_1_8bit is

port( s0, s1: in std_logic;

a, b, c, d: in std_logic_vector(0 to 7);

y: out std_logic_vector(0 to 7)

);

end mux_4_1_8bit;

L’architecture rimane pressochè invariata. L’unica modifica necessaria è per l’assegnamento

della costante (condizione di indifferenza) per la clausa cioè:

when others,

architecture rtl of mux_4_1_8bit is

signal sel: std_logic_vector(0 to 1);

begin

with sel select

y <= a when ”00”,

b when ”01”,

c when ”10”,

d when ”11”,

”--------” when others;

end rtl;

Vediamo ora, a titolo di esempio, come è possibile specificare un multiplexer 4-a-1 generico per

segnali di dimensione variabile. Come accennato in precedenza, la parametrizzazione di un

componente si realizza mediante il costrutto Vediamo la dichiarazione dell’entity:

generic.

entity mux_4_1_Nbit is

generic( N: integer );

port( sel: in std_logic_vector(0 to 1);

a, b, c, d: in std_logic_vector(0 to N-1);

y: out std_logic_vector(0 to N-1)

);

end mux_4_1_Nbit;

La dichiarazione di generic introduce nell’entity un parametro, che indica la dimensione dei

N,

segnali di ingresso e del segnale di uscita. Quando questo componente sarà utilizzato nel progetto,

sarà necessario assegnare un valore specifico e costante al parametro Ciò fatto, lo strumento di

N.

sintesi sarà in grado di realizzare il componente in modo consistente con la dimensione assegnata

ai segnali. Consideriamo ora l’architecture descritta poco sopra: si nota che l’unica parte che

mostra una dipendenza esplicita dalla dimensone dei segnali è l’assegnamento del valore costante

costituito da tutti don’t care. Non conoscendo a priori la dimensione del segnale di uscita non è

y,

possibile utilizzare una costante predefinita per tale assegnamento. A tal fine il VHDL dispone di

un costrutto che permette di assegnare un valore costante ad ogni elemento di un vettore, sia

quando la dimensione è nota a priori, sia quando non lo è. La sintassi di tale costrutto, nel caso di

un assegnamento, è la seguente:

signal_name <= (others => const );

Questo sta a significare che ogni elemento del vettore prende il valore

signal_name const.

Mediante tale costrutto è possibile riscrivere l’architecture del multiplexer generico in modo

semplice e compatto, come mostrato di seguito.

architecture rtl of mux_4_1_Nbit is

begin

with sel select

y <= a when ”00”,

b when ”01”,

c when ”10”,

d when ”11”,

(others => ’-’) when others;

end rtl;

4.5.5 Demultiplexer

Un demultiplexer è un elemento che svolge la funzione duale del muliplexer. Nella sua versione

più semplice, esso dispone di una linea di ingresso un ingresso di selezione e due linee di

x, s

uscita ed Il simbolo utilizzato comunemente è il seguente:

y0 y1. Y0

X Y1

S

Il demultiplexer pone in uscita il valore su una delle due linee o a seconda del valore

x y0 y1,

del segnale di selezione mentre non è specificato il valore che assume l’altra linea. Il codice

VHDL corrispondente, per un demultiplexer 1-a-2 a 1 bit è il seguente:

entity demux_1_2

port( s: in std_logic;

x: in std_logic;

y0, y1: out std_logic );

end demux_1_2;

architecture rtl of demux_1_2 is

begin

y0 <= x when s=’0’ else ’-’;

y1 <= x when s=’1’ else ’-’;

end rtl;

L’estensione al demultiplexer 1-a-4 è semplice, così come lo è l’introduzuione di un generic che

specifichi la dimensione delle linee di ingresso e uscita. Il simbolo è il seguente:

A

B

X C

D

S0 S1

Il codice VHDL si può costruire estendendo il modello del demultiplexer e ricordando la tecnica

dei generic per la parametrizzazione di un componente.

entity demux_1_4_Nbit

generic( N: integer );

port( sel: in std_logic_vector(;

x: in std_logic_vector(0 to N-1);

y0, y1, y2, y3: out std_logic_vector(0 to N-1)

);

end demux_1_4_Nbit;

architecture rtl of demux_1_4_Nbit is

begin

y0 <= x when sel=”00” else (others => ’-’);

y1 <= x when sel=”01” else (others => ’-’);

y2 <= x when sel=”10” else (others => ’-’);

y3 <= x when sel=”11” else (others => ’-’);

end rtl;

4.5.6 Shifter

Uno shifter è un elemento che presenta all’uscita il valore dell’ingresso (vettoriale) spostato in

una direzione prefissata di un certo numero di bit. Ad esempio un shifter a destra di una posizione

per un segnale di 4 bit è un dispositivo che realizza la funzione descritta dalla seguente figura.

x(0) x(1) x(2) x(3)

0 x(0) x(1) x(2)

Dapprima vediamo l’entity declaration di tale dispositivo:

entity shift_right is

port( x: in std_logic_vector(0 to 3);

y: out std_logic_vector(0 to 3) );

end shift_right;

Una prima possible architettura è la seguente:

archietcture rtl of shift_right is

begin

y(0) <= ’0’;

y(1) <= x(0);

y(2) <= x(1);

y(3) <= x(2);

end rtl;

Ricordando il costrutto di slicing e l’operatore di concatenamento, possiamo fornire una specifica

più compatta per la stessa funzionalità, ovvero:

archietcture rtl of shift_right is

begin

y <= ’0’ & x(0 to 2);

end rtl;

Un elemento simile è lo shifter circolare che realizza l’operazione mosrata dalla figura seguente:

x(0) x(1) x(2) x(3)

x(3) x(0) x(1) x(2)

La specifica VHDL, già data nella forma compatta facente uso di slice e concatenamento è:

entity shift_circular is

port( x: in std_logic_vector(0 to 3);

y: out std_logic_vector(0 to 3)

);

end shift_circular;

archietcture rtl of shift_circular is

begin

y <= x(3) & x(0 to 2);

end rtl;

Mediante l’uso di generic è possibile realizzare, ad esempio, uno shifter circolare di un numero

arbitrario di posizioni su dati di dimensione altrettanto arbitraria. Il parametro indica la

N

dimensione dei dati mentre il parametri indica l’entità dello scorrimento.

M

x(0) x(1) x(N-M-1) x(N-M) x(N-2) x(N-1)

x(N-M) x(N-1) x(0) x(1) x(N-M-1)

L’entity declaration di unate componente è la seguente:

entity shift_circular_N_M is

generic( N: integer;

M: integer );

port( x: in std_logic_vector(0 to 3);

y: out std_logic_vector(0 to 3) );

end shift_circular_N_M;

L’architettura ricalca lo schema mostrato dalla figura e, in kodo molto sintetico, si esprime in

VHDL come:

archietcture rtl of shift_circular_N_M is

begin

y <= x(N-M to N-1) & x(0 to N-M-1);

end rtl;

Si noti che questo componente non permette di effetture uno shift circolare programmabile

mediante un segnale bensì una sorta di template di shifter circolare generico. Al momento

dell’utilizzo di tale componente nella costruzione di un circuito più complesso, l’entità dello

scorrimento deve essere fissata ad un valore costante.

Volendo realizzare uno shifter, ad esempio uno shifter a destra per vettori di 4 bit, controllabile

tramite segnali si deve ricorrere ad una architettura più complessa. Il comportamento di un tale

elemento potrebbe essere riassunto dalla tabella seguente, in cui s è un vettore di due elementi che

indica l’entità dello scorrimento:

Scorrimento

s y

0

00 x

1

01 ’0’ & x(0 to 2)

2

10 ”00” & x(0 to 1)

3

11 ”000” & x(0)

Ciò si traduce nella specifica seguente:

entity shift_right_prog is

port( x: in std_logic_vector(0 to 3);

s: in std_logic_vector(0 to 1);

y: out std_logic(0 to 3) );

end shift_right_prog;

archiecture rtl of shif_right_prog is

begin

with s select

y <= x when ”00”,

’0’ & x(0 to 2) when ”01”,

”00” & x(0 to 1) when ”10”,

”000” & x(0) when ”11”,

”0000” when others;

end rtl;

Questi semplici esempi mostrano come il VHDL offra la possibilità di specificare comportamenti

complessi con estrema semplicità, sintesi e flessibilità.

4.5.7 Buffer tri-state

Anche se di uso non molto frequente, i buffer tri-state possono essere utili in talune situazioni. Un

buffer tristate è dotato di un ingresso dati di una uscita e di un segnale di enable Il suo

D, Y E.

comportamento è il seguente: se vale 1 allora l’uscita è uguale a altrimenti l’uscita è in alta

E Y D

impedenza. Alcuni buffer tri-state possono essere invertenti, in tal caso quando vale 1 l’uscita

E Y

è il complemento dell’ingresso I simboli con cui si indicano tali dispositivi sono i seguenti:

D. E

E D Y

D Y

Ricordando che il valore del tipo indica la condizione di alta impedenza, la

’Z’ std_logic

specifica di un buffer tri-state non invertente è la seguente:

entity tri_state is

port( d: in std_logic;

e: in std_logic;

y: out std_logic );

end tri_state;

architecture rtl of tri_state is

begin

y <= d when e=’1’ else ’Z’;

end rtl;

Un esempio di applicazione del buffer tri-state è un multiplexer 2-a-1. Tale realizzazione sfrutta

una connessione wired-or e due tri-state, come mostra la figura.

S Y

A

B

Si noti che a volte un buffer tri-state con segnale di enable attivo basso, come il buffer in basso

della figura precedente si indica in forma compatta come mostrato nella figura seguente.

S Y

A

B

La specifica di un multiplexer realizzato in questo modo diviene quindi la seguente:

entity mux_2_1_tri_state is

port( s: in std_logic;

a, b: in std_logic;

y: out std_logic

);

end mux_2_1_tri_state;

architecture rtl of mux_2_1_tri_state is

begin

y <= a when s=’1’ else ’Z’;

y <= b when s=’0’ else ’Z’;

end rtl;

Come si può notare, il segnale di uscita è assegnato due volte: ciò normalmente costituisce una

y

violazione delle regole del VHDL secondo cui ogni segnale può avere uno ed un solo driver.

Tuttavia, essendo i driver del segnale dei buffer tri-state, la connessione in wired-or è accettata.

y

In maniera analoga, è molto intuitivo realizzare un demultiplexer mediante buffer tri-state.

Mentre nella specifica vista in precedenza le linee di uscita non selezionate assumevano un valore

di don’t care, in questo caso esse assumono un valore di alta impedenza. La realizzazione

circuitale che ne consegue è la seguente: Y0

S

X Y1

Ciò può essere tradotto in VHDL in modo chiaro e diretto:

entity demux_1_2_tri_state is

port( s: in std_logic;

x: in std_logic;

y0, y1: out std_logic

);

end demux_1_2_tri_state;

architecture rtl of demux_1_2_tri_state is

begin

y0 <= x when s=’0’ else ’Z’;

y1 <= x when s=’1’ else ’Z’;

end rtl;

4.6 Operatori aritmetici e relazionali

Come accennato nella sezione relativa alla libreria i due tipi e

IEEE, std_logic_vector

possono essere utilizzati indifferentemente per rappresentare stringhe di

std_ulogic_vector

bit e numeri interi in codifica binaria. Naturalmente una generica stringa di bit ammette almeno

due differenti interpretazioni del valore numerico corrispondente: quella naturale e quella in

complemento a 2. Così, ad esempio, la stringa 1101 può essere interpretata come 13 (binario

naturale) oppure come –3 (complmento a 2). Questa circostanza rende necessario esplicitare il

tipo di codifica utilizzata ogni qualvolta si voglia usare un vettore di bit come numero intero.

La libreria definisce due package destinati a tale scopo: e

IEEE std_logic_unsigned

Si ricorda che tali package devono essere utilizzati in mutua esclusione e

std_logic_signed.

sempre accoppiati al package Così, volendo usare numeri senza segno si

std_logic_1164.

dovranno inserire nella specifica VHDL le seguenti dichiarazioni:

library IEEE;

use IEEE.std_logic_1164.all;

use IEEE.std_logc_unsigned.all;

mentre per l’uso di valori con segno si ricorrerà alla dichiarazione:

library IEEE;

use IEEE.std_logic_1164.all;

use IEEE.std_logc_signed.all;

Normalmente, l’interpretazione di stringhe di bit secondo una codifica numerica ha senso se e

solo se su tali stringhe si vogliono eseguire operazioni aritmetiche. Così come i tipi non

IEEE

sono parte del linguaggio VHDL am ne costituiscono una estensione standardizzata, anche il

supporto per la specifica e la sintesi di operatori aritmetici e relazionali è una estensione del

linguaggio. Tutto ciò che è necessario a tal fine è raccolto nel package std_logic_arith,

che, al’occorrenza deve essere esplicitamente richiesto mediante la dichiarazione:

use IEEE.std_logic_arith.all;

Questo package definisce l’interpretazione di alcuni operatori e stabilisce i corrispondenti

template di sintesi in modo consistente. In particolare tutti gli strumenti di sintesi devono

supportare i seguenti operatori:

Classe Operatori

Aritmetici +, -, *

Relazionali >, >=, <, <=, =, /=

Shift sll, srl, rol, ror

Come primo esempio di utilizzo di tali operatori consideriamo la soma di due numeri naturali

positivi e rappresentati su 8 bit. Per prima cosa si noti che la somma di due numeri di 8 bit

a b

deve essere rappresentasta su 9 bit, interpretabili tutti come risultato oppure come 8 bit bit di

risultato più un bit (il più significativo) per la segnalazione dell’overflow.

Analizziamo dapprima il primo caso. L’entity declaration di un tale sommatore, completa di

dichairazione delle librerie e dei package necessari, potrebbe essere quindi la seguente:

entity adder_8_8_9 is

port( a, b: in std_logic_vector(0 to 7);

c: out std_logic_vector(0 to 8)

);

end adder_8_8_9;

Il linguaggio prevede che gli operandi ed il risultato di una somma abbiano tutti lo stesso numero

di bit, mentre nell’entity declaration appena vista abbiamo indicato dimensioni differenti per

motivi legati all’interpretazione del risultato. Nell’architecture, quindi, sarà necessario provvedere

opportunamente al padding degli operandi, ottenuto aggiungendo uno zero nella posizione più

significativa. Vediamo una possibile soluzione:

architecture rtl of adder_8_8_9 is

signal tempa: std_logic_vector(0 to 8);

signal tempb: std_logic_vector(0 to 8);

begin

-- Explicit padding

tempa <= ’0’ & a;

tempb <= ’0’ & b;

-- Addition

c <= tempa + tempb;

end rtl;

Questa soluzione, benché corretta, richiede la definizione di due segnali temporanei. La soluzione

seguente è altrettanto corretta ed ha il vantaggio di essere più sintetica:

architecture rtl of adder_8_8_9 is

begin

c <= (’0’ & a) + (’0’ & b);

end rtl;

Vediamo ora come realizzare un sommatore più complesso, ovvero un sommatore dotato di due

ingressi dati e di 8 bit, un ingresso di riporto ad 1 bit, un’uscita dati a 8 bit ed un

a b cin s

riporto in uscita ancora a 8 bit. L’operazine che si vuole realizzare è la seguente:

cout

0 a(0) a(1) a(2) a(3) a(4) a(5) a(6) a(7) +

0 b(0) b(1) b(2) b(3) b(4) b(5) b(6) b(7) +

0 0 0 0 0 0 0 cin =

cout s(0) s(1) s(2) s(3) s(4) s(5) s(6) s(7)

L’entity declaration è di facile scrittura:

entity adder_8_8_8_carry is

port( a, b: in std_logic_vector(0 to 7);

cin: in std_logic;

s: out std_logic_vector(0 to 7);

cout: out std_logic

);

end adder_8_8_8_carry;

Per quanto concerne l’architecture, è necessario estendere su una lunghezza di 9 bit i due

operandi ed il riporto, quindi sommarli e separare il riporto in uscita dal risultato della somma. Il

codice seguente mostra una possibile soluzione.

architecture rtl of adder_8_8_8_carry is

signal tempa: std_logic_vector(0 to 8);

signal tempb: std_logic_vector(0 to 8);

signal tempcin: std_logic_vector(0 to 8);

signal temps: std_logic_vector(0 to 8);

begin

-- Extends operands

tempa <= ’0’ & a;

tempb <= ’0’ & a;

tempcin <= ”00000000” & cin;

-- Addition

temps <= tempa + tempb + tempcin;

-- Splits the result

cout <= temps(0);

s <= temps(1 to 8);

end rtl;

Anche in questo caso è possibile eliminare alcuni segnali ridondanti:

architecture rtl of adder_8_8_8_carry is

signal temps: std_logic_vector(0 to 8);

begin

-- Addition

temps <= (’0’ & a) + (’0’ & b) + (”00000000” & cin);

-- Splits the result

cout <= temps(0);

s <= temps(1 to 8);

end rtl;

Questo schema è facilemente parametrizzabile sulla dimensione degli operandi. Ecco una

soluzione possibile. Vediamo dapprima l’entity declaration, che, come appare evidente, segue lo

schema già adottato per altri componenti parametrici.

entity adder_N_N_N_carry is

generic( N: integer );

port( a, b: in std_logic_vector(0 to N-1);

cin: in std_logic;

s: out std_logic_vector(0 to N-1);

cout: out std_logic

);

end adder_N_N_N_carry;

Consideriamo ora l’architecture. Si noti che l’estensione del riporto in ingresso sulla lunghezza di

parola (N) non richiede l’uso di un segnale temporaneo in quanto il linguaggio – e lo strumento di

sintesi – prevede l’estensone implicita alla dimansione massima tra i due operandi di una somma.

architecture rtl of adder_N_N_N_carry is

begin

signal temps: std_logic_vector(0 to N-1);

begin

-- Addition

temps <= (’0’ & a) + (’0’ & b) + cin;

-- Splits the result

cout <= temps(0);

s <= temps(1 to N-1);

end rtl;

In quest’ultima architettura abbiamo visto che è possible sommare in un’unica espressione tre

segnali. Questo risulta in una struttura come quella mostrata nella figura seguente:

’0’ & a + + s

’0’ & b

cin

Vediamo ora che cosa accade per una somma di quattro operandi, diaciamo a, b, c e d. Si

consideri dapprima l’assegnamento:

s <= a + b + c + d;

L’architettura risultante è la seguente:

a +

b + + s

c

d

Questa struttura rispecchia l’associatività dell’operatore di somma, cioè equivale all’espressione:

s <= (((a + b) + c) + d);

Detto T il ritardo di propagazione del segnale attraverso un sommatore, la soluzione appena vista

comporta un ritardo pari a 3T. Volendo ottimizzare le prestazioni si può forzare, mediante l’uso

delle parentesi, un ordine differente di valutazione dell’espressione, cioè:

s <= (a + b) + (c + d);

Da questa scrittura si perviene alla struttura ostrata di seguito, che, come appare chiaro,

parallelizza due delle 3 somme e quindi presenta un ritardo complessivo pari a 2T.

a +

b + s

c +

d

Questo esempio mostra come sia possibile modificare, mediante un attento uso delle parentesi, il

risultato della sintesi di espressioni algebricamente equivalenti.

Accenniamo ora al problema della condivisione degli operatori, noto anche come resource

sharing. A tale scopo utilizziamo un semplice esempio. Si consideri un modulo dotato

add_sel

di 4 ingressi dati e una uscita dati tutti ad 8 bit, ed un segnale di selezione se s

a, b, c d, y, s;

vale 0 allora altrimenti Vediamo una possibile architecture:

y=a+b y=c+d.

architecture rtl of add_sel is

begin

y <= a + b when s=’0’ else c + d;

end rtl;

Il risultato della sintesi, benché comunque aderente alla specifica, dipende dale caratteristiche

dello strumento di sintesi utilizzato. Una prima implementazione potrebbe essere la seguente:

a +

b s

c +

d

Questa sluzione è corretta ma poco efficiente in termini di area occupata, ovvero in termini di

numero di porte logiche necessarie. Una riformulazione del problema, e conseguentemente della

specifica VHDL, porta ad una architettura più compatta ed ugualmente efficiente.

architecture rtl of add_sel is

signal t: std_logic_vector(0 to 7);

signal u: std_logic_vector(0 to 7);

begin

t <= a when s=’0’ else c;

u <= b when s=’0’ else d;

y <= t + u;

end rtl;

In questo caso l’operazione di somma è una sola ed il problema si sposta sulla selezione degli

operandi mediante multiplexer. Il vantaggio in termini di area deriva dalla complessità inferiore

di un multiplexer rispetto ad un sommatore. Dal punto di vista circuitale questa specifica

corrisponde alla struttura mostrata di seguito:

a t

c s

+

b u

d

Come nota conclusiva, è bene notare che alcuni strumenti di sintesi particolarmente efficienti

sono in grado di riconoscere situazioni di questo tipo anche se implicita, come nella scrittura

originale, e pervenire a soluzioni ottimizzate come quella appena mostrata. Questo processo di

ottimizzazione prende il nome di operator sharing.

4.7 Descrizione strutturale

Quanto visto fino a questo punto è sufficiente per la specifica di moduli di bassa o media

complessità ma poco si adatta a circuiti di elevata complessità. Come in molti altri campi della

scienza, un buon approccio alla soluzione di un problema complesso consiste nella

scomposizione di questo in sottoproblemi più semplice e quindi nella ricomposizione delle

singole soluzioni. In termini di progettazione questo significa che la progettazione di un circuito

molto complesso deve essere affrontata individuando le funzionalità di base del sistema,

progettando componenti in grado di realizzarle e quindi comporre i vari moduli, mediante

opportune interconnessioni, fino a formare il sistema completo.

Per chiarire questo concetto, iniziamo da un semplice esempio. Si voglia realizzare una porta

XOR utilizzando le porte fondamentali OR, AND e NOT. Sappiamo che:

a xor b = (a and (not b)) or ((not a) and b)

In termini circuitali, questo corrisponde alla struttura mostrata nella seguente figura:

a y

b

Per realizzare la porta XOR parteno dai componenti di base è necessario per prima cosa realizzare

tali componenti. Vediamo le entity e le architecture necessarie:

-- The NOT gate

entity NOT_GATE is

port( x: in std_logic;

z: out std_logic );

end NOT_GATE;

architecture rtl of NOT_GATE is

begin

z <= not x;

end rtl;

-- The 2-input AND gate

entity AND2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end AND2_GATE;

architecture rtl of AND2_GATE is

begin

z <= x and y;

end rtl;

-- The 2-input OR gate

entity OR2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end OR2_GATE;

architecture rtl of OR2_GATE is

begin

z <= x or y;

end rtl;

Ora che disponiamo dei tre elementi di base possiamo procedere alla soluzione del problema

connettendo tali elementi in maniera opportune, cioè secondo lo schema circuitale mostrato

precedentemente. È bene notare, a scanso di equivoci, che esiste una differenza sostanziale tra, ad

esempio, l’operatore VHDL e il componente appena specificato: il primo è

and AND2_GATE

predefinito come appartenete al linguaggio e può unicamente essere utilizzato nelle espressioni,

menre il secondo è un componente generico che non può essere utilizzato in alcuna espressione,

ma piuttosto deve essere istanziato. Istanziare un componente significa inserirlo nella specifica, e

quindi nel circuito che si sta realizzando, e connetterlo opportunamente ad altri componenti

mediante l’uso di segnali.

Iniziamo ora la specifica della porta XOR partendo dalla entity declaration.

entity XOR2_GATE is

port( a: in std_logic;

b: in std_logic;

u: out std_logic );

end XOR2_GATE;

Ricordando che i segnali di ingresso e di uscita dichiarati in una entity declaration sono

utilizzabili nella corrispondente architecture, analizzaiamo il circuito che realizza la porta XOR

per individuare quali e quanti segnali sono necessari per le connessioni:

a t1

nb u

na t2

b

La figura mostra, tratteggiati, tali segnali: ed sono le uscite delle porte NOT e servono per

na nb

connettere queste agli ingressi delle due porte AND mentre i segnali e sono le uscite delle

t1 t2

porte AND e servono per connettere queste agli ingressi della porta OR. Dato che tali segnali non

sono né ingressi né uscite della porta XOR, si dice che si tratta di segnali interni. La parte

dichiarativa della architecture dovrà specificarne nome e tipo. Prima di procedere alla scrittura

della architecture ricordiamo un’altro aspetto del linguaggio VHDL: una architecture che utlizza

altri componenti deve dichiararne il nome e l’interfaccia nella sua parte dichiarativa. La

dichiarazione di un componente segue lo schema generale seguente:

component component_name is

[generic( generic_list );]

port( port_list );

end component;

Sostanzialmente è identico alla corrispondente entity declaration, salvo l’uso della parola chiave

al posto di A questo punto possiamo iniziare a vedere il codice VHDL

component entity.

dell’architecture, iniziando dalla parte dichiarativa:

architeture structural of XOR2_GATE is

-- The NOT gate

component NOT_GATE is

port( x: in std_logic;

z: out std_logic );

end component;

-- The 2-input AND gate

component AND2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end component;

-- The 2-input OR gate

component OR2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end component;

-- Internal signals

signal na: std_logic;

signal nb: std_logic;

signal t1: std_logic;

signal t2: std_logic;

begin

...

A questo punto abbiamo dichiarato tutto ciò che è necessario per procedere alla costruzione del

circuito. Per fare ciò, come già accennato, è necessario istanziare i componenti. Una istanziazione

ha la forma generale seguente:

instance_name: component_name

[generic map( generic_assignment_list );]

port map( port_assignment_list );

Ogni volta che si utilizza un componente è necessario assegnargli un nome: tale nome, detto

instance name, deve essere unico all’interno dell’architecture e non ha alcun legame con il nome

del componente che si sta istanziando. Il nome del componente di cui è

instance_name

un’istanza è e deve essere stato preventivamente dichiarato nella parte

component_name

diachiarativa dell’architecture. Trascuriamo per il momento la parte opzionale del costrutto di

istanziazione che riguarda i generic e passiamo ad analizzare il costrutto Tale

port map.

costrutto ha lo scopo di indicare come le porte dell’istanza del componente

instance_name

sono connesse ai segnali dell’architecture, siano essi ingressi/uscite o

component_name

segnali interni. La port_assignment_list è una lista di tali connessioni e può assumere due forme:

positional (posizionale) oppure named (nominale). La forma posizionale ha la seguente sintassi:

port map( signal_1, ..., signal_N );

In tal caso si assume che l’ordine dei segnali sia significativo in quanto il primo segnale della lista

sarà connesso alla prima porta che appare nella dichiarazione del component, il secondo segnale

alla seconda porta e così via. Questa forma, sicuramente molto sintetica, può risultare a volte di

difficile lettura. La seconda possibilità consiste nell’uso della forma nominale che segue la

sintassi seguente:

port map( port_1 => signal_1, ..., port_N => signal_N );

in cui I nomi delle porte del componente che si sta istanziando appaiono nominati esplicitamente.

Grazie a questo fatto l’associazione (ovvero la connessione dei segnali) avviene per nome e

quindi la posizione all’interno della lista perde di significato e può pertanto essere arbitraria.

Riprendiamo ora lo schema circuitale della porta XOR aggiungendo i nomi che intendiamo

assegnare alle istanzedei vari componenti. U3

a t1

U1 U5

nb u

na

U2 t2

b U4

Per chiarire il concetto di istanziazione e mostrare un esempio di entrambi gli approcci,

consideriamo dettagliatamente l’istanziazione di una delle porte AND, ad esempio l’istanza U3.

La figura che segue mostra tale istanza indicando il nome del component cui fa riferimento, i

nomi delle porte dichiarate nel component ed il nome dei segnali che si intendono connettere.

U3: AND2 GATE

a x t1

z

nb y

Il codice VHDL per effettuare tale istanziazione secondo il secondo schema (nominale), è il

seguente:

U3: AND2_GATE

port map( x => a, y => nb, z => t1 );

Questo significa che la porta del component sarà connessa al segnale la porta

x AND2_GATE a,

sarà connessa al segnale e la porta sarà connessa al segnale Come si nota, la direzione

y nb z t1.

delle porte non ha alcuna influenza sulla sintassi dell’istanziazione. Per scrivere la stessa

istanziazione nella prima forma (posizionale) è necessario ricordare che le porte del componente

sono state dichiarate nel seguente ordine: e Avremo pertanto:

AND2_GATE x, y z.

U3: AND2_GATE

port map( a, nb, t1 );

Si noti infine che la direzionalità dei segnali di ingresso e uscita dell’architecture deve essere

rispettata. Questo significa che un segnale di ingresso dell’architecture (ad esempio non può

a)

essere connesso ad una porta di uscita di uno dei componenti (poichè ciò equivarrebbe ad una

scrittura di un segnale di ingresso, circostanza che il VHDL proibisce) e così un segnale di uscita

dell’architecture (ad esempio può essere connesso ad un ingresso di uno dei componenti.

u)non

A questo punto possiamo completare il corpo dell’architecture della porta XOR semplicemente

elencando tutte le istanze che la compongono. Si noti che è possibile utilizzare all’interno della

stessa architecture sia istanziazioni con port map posizionale sia istanziazioni con port map

nominale. Vediamo ora il corpo completo dell’architecture.

...

begin

U1: NOT_GATE

port map( x => b, z => nb );

U2: NOT_GATE

port map( x => a, z => na );

U3: AND2_GATE

port map( x => a, y => nb, z => t1 );

U4: AND2_GATE

port map( x => na, y => b, z => t2 );

U3: OR2_GATE

port map( x => t1, y => t2, z => u );

end structural;

Questo stile di scrittura del VHDL prende il nome di VHDL strutturale in quanto pone l’accento

sulla struttura della rete piuttosto che sulle trasformazioni che i segnali subiscono.

Nella pratica di progettazione, l’uso di VHDL strutturale si limita alla connessione di componenti

complessi e solo molto raramente utilizza elementi quali porte logiche o flip-flop. È altresì

comune utilizzare nella stessa architecture sia lo stile strutturale sia lo stile RTL, in modo da

sfruttare i vantaggi di entrambi. Il VHDL così ottenuto non è né strutturale né RTL ma una

miscela dei due.

A titolo di esempio suponiamo di voler realizzare la stessa porta XOR sfruttando come

componenti solo le porte AND (AND2_GATE) ed OR (OR2_GATE) ricorrendo allo stile RTL per

costruire i segnali ed mediante loperatore VHDL La parte dichiarativa

na nb not.

dell’archietcture sarà in questo caso:

architeture mixed of XOR2_GATE is

-- The 2-input AND gate

component AND2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end component;

-- The 2-input OR gate

component OR2_GATE is

port( x: in std_logic;

y: in std_logic;

z: out std_logic );

end component;

-- Internal signals

signal na: std_logic;

signal nb: std_logic;

signal t1: std_logic;

signal t2: std_logic;

begin

...

Mantenedo invariati i nomi delle istanze delle porte AND e OR, il corpo dell’architecture diviene:

...

begin

-- RTL style

na <= not a;

nb <= not b;

-- Structural style

U3: AND2_GATE

port map( x => a, y => nb, z => t1 );

U4: AND2_GATE

port map( x => na, y => b, z => t2 );

U3: OR2_GATE

port map( x => t1, y => t2, z => u );

end mixed;

Affrontiamo ora il problema dei generic. Anche in questo caso usiamo un semplice esempio per

chiarire i diversi aspetti. Si voglia realizzare un sommatore di 4 valori e interi e

x1, x2, x3 x4

senza segno. Di questi ed sono rappresentati su 6 bit mentre ed sono rappresentati

x1 x2 x3 x4

su 8 bit. Il risultato deve essere rappresentato su 10 bit per garantire che non si verifichino

z

overflow. Per fare ciò si vuole procedere specificando dapprima una sommatore generico a due

ingressi e quindi utilizzare tale componente per realizzare il sommatore completo dei quattro

valori. Iniziamo dalla specifica del sommatore generico:

library IEEE;

use IEEE.std_logic_1164.all;

use IEEE.std_logic_unsigned.all;

use IEEE.std_logic_arith.all;

entity ADDER_N is

-- Input signal size

generic( N: integer );

-- Ports

port( a: in std_logic_vector(0 to N-1); -- First input

b: in std_logic_vector(0 to N-1); -- Second input

u: out std_logic_vector(0 to N) ); -- Output

end ADDER_N

architecture rtl of ADDER_N is

begin

-- Extends input operands and adds

u <= (’0’ & a) + (’0’ & b);

end rtl;

Analizziamo ora il problema iniziale per determinare quail caratteristiche dovranno avere I

sommatori necessari e quali segnali interni saranno richiesti. Per la somma di ed è

x1 x2

necessario un sommatore a 6 bit che produrrà il risultato su 7 bit. Per la somma di ed

s12 x3 x4

è necessario un seommatore su 8 bit che produrrà il risultato su 9 bit. Infine per la somma

s34

dei due risultati parziali ed è necessario per prima cosa estendere su 9 bit quindi

s12 s34 s12

utilizzare un sommatore a 9 bit che produrrà il risultato voluto su 10 bit. Questa soluzione è

schematizzata nella figura seguente in cui sono evidenziati i segnali interni, le loro dimensioni ed

i nomi delle istanze. U1: ADDER N U3: ADDER N

0 0

x1 y

u

a u

a

7

6 9

s12 N=9

N=6

x2 b b

6 s34

U2: ADDER N

x3 u

a 9

8 N=8

x4 b

8

5. Reti sequenziali

In questo capitolo affronteremo il problema della descrizione delle reti sequenziali. Per poter

introdurre i processi, che stanno alla base della specifica di comportamenti seuqnziali, è

necessario chiarire il concetto su cui si fonda il modello computazionale del linguaggio VHDL: il

concetto di evento. Consideriamo, ad esempio una porta AND ai cui ingressi sono presenti un uno

ed uno 0 e, di conseguenza, alla cui uscita si ha uno zero. Se gli ingressi non mutano allora

nemmeno l’uscita cambierà valore. Tuttavia appena si ha una transizione su uno dei segnali di

ingresso, il valore dell’uscita potrebbe cambiare. Se l’ingresso che si trova a zero passasse al

valore uno, allora si avrebbe una transizione sull’uscita della porta da zero a uno. Ciò, nella realtà

fisica, accade perchè la transizione in ingresso corrisponde ad un cambiamento della tensione sul

gate di uno dei MOSFET della porta AND che ha come conseguenza una variazione dello stato di

polarizzazione del MOSFET il quale, entrando in conduzione, porta l’uscita a massa, ovvero al

valore logico uno. Il modello di calcolo del VHDL deve poter rappresentare una simile situazione

in cui in assenza di variazione di segnale agli ingressi di una porta il sistema permane in uno stato

stabile mentre in presenza di una transizione il sistema deve poter reagire modificando il valore di

opportuni segnali. Usando una terminologia mutuata dall’ambito della simulazione di specifiche

VHDL si dice che il comportamento che una descrizione VHDL coglie è conttollato dagli eventi

o event-driven.

Consideriamo, ad esempio, una ipotetica porzione di architecture composta dai seguenti statement

concorrenti:

x <= a or z;

y <= c and (not x);

z <= b and d;

Benché l’ordine in cui sono scritti questi statement no rispecchi le dipendenze tra i segnali, la

corretta valutazione è garantita dal meccanismo degli eventi. Vediamo per quale motivo.

Supponiamo che il valore del segnale passi da 0 ad 1: ciò significa che sul segnale si è

b b

verificato un evento. Nella realtà fisica tale transizione corrisponderebbe ad una variazione del

valore di tensione in un punto della rete e comportrebbe una propagazione di tale variazione

all’interno della rete. Il modo in cui ciò è reso dal VHDL segue una logica analoga. In

corrispondenza dell’evento sul segnale è infatti necessario ricalcolare il valore di tutte le

b

espressioni che utilizzano nella parte destra. Nel caso in esame deve essere ricalcolato il valore

b

di Se il valore di dovesse cambiare (supponendo ad esempio che valga 1) ciò produrrebbe

z. z d

un nuovo evento, questa volta sul segnale Come conseguenza, sarebbe necessario ricalcolare

z.

tutte le espressioni in cui compare nella parte destra, ovvero, continuando con l’esempio,

z

l’espressione che calcola il valore del segnale Se anche cambiasse di valore, sarebbe

x. x

necessario analizzare, in modo simile a quelllo appena visto, tutte le espressioni al fine di

propagare l’evento. Si noti che nel modello di calcolo del VHDL tutto ciò avviene in un tempo

nullo, spesso indicato con il termine di delta-time. Se il primo evento su si verifica ad un certo

b

tempo allora la corrispondente variazione del segnale avviene al tempo t+∆t. Tale variazione

t, z

si propaga al segnale al tempo t+2∆t ed infine su al tempo t+3∆t. Dal momento che

x y

∆t

l’intervallo di tempo fittizio ha durata nulla, si ha una propagazione istantanea della variazione

del segnale b sul segnale y.

Ritornando al concetto di evento possiamo dire che uno statement VHDL è attivato da un evento

su uno dei segnali che lo statement legge. L’attivazione di uno statement potrebbe a sua volta

causare un evento ed attivare nuovi statement. Tutto ciò rispecchia in modo corretto il

comportamento delle reti combinatorie ideali. Una volta chiarite queste idee, possiamo passare ad

introdurre i processi.

5.1 Processi

Un processo è uno statement concorrente, al pari di un assegnamento, un assegnamento

condizionato o una istanziazione. Un processo, tuttavia, è anche uno statement composto,

costituito, tra le altre code da un corpo, a sua volta composto da statement. Una prima

importantissima questione a tale riguardo è che gli statement all’interno di un processo non sono

concorrenti besì sequenziali. Un primo utlizzo di un processo potrebbe quindi essere quello di

forzare una inerpretazione sequenziale del codice VHDL del suo corpo.

Prima di procedere oltre, vediamo la sintassi generale per la dichiarazione di un processo:

[process_name]: process ( sensitivity_list )

[declarations]

begin

[body]

end process;

Un processo ha un nome opzionale utile prevalentemente per motivi di

process_name,

leggibilità, una parte dichiarativa opzionale simile a quella dell’architecture

declaration,

declaration ed un corpo anch’esso teoricamente opzionale (un processo privo di corpo è

body,

accettabile in VHDL ma è privo di qualsiasi scopo). Infine un processo ha una sensitivity list

ovvero una lista di segnali in grado di attivarlo: tutte le volte che si verifica un evento, e soltanto

in queso caso, su uno dei segnali della il processo è attivato altrimenti

sensitivity_list

esso rimane inattivo. Per chiarire questo concetto consideriamo uno statement concorrente come:

z <= (x + y) or t;

Questo statement sarà attivato, come abbiamo visto, da un evento su almeno uno dei segnali x, y

oppure Analogamente, se volessimo rendere un processo attivabile da tali segnali, ovvero dalgi

t.

eventi che si dovessero verificare su tali segnali dovremmo inserire e nella sensitivity list,

x, y t

come mostra la seguente porzione di codice:

sample: process ( x, y, t )

begin

Consideriamo ora il seguente processo e studiamone il significato ed il comportamento:

sample: process ( a, b )

begin

x <= a or b;

y <= x or c;

end;

Dal momento che la sensitivity list contempla solo i segnali e un evento sul segnale non

a b, c

produrrebbe l’attivazione del processo con la conseguenza che il valore del segnale non

y

verrebbe aggiornato se non in corrispondenza di un evento su o Una tale specifica è pertanto

a b.

scorretta in quanto non rispecchia lo schema di calcolo previsto per il segnale y.

Chiarito il principio che sta alla base del meccanismo di attivazione dei processi, vediamo quali

sono gli statement sequenziali di cui il VHDL dispone.

5.2 Statement sequenziali

In costrutti sequenziali che possono essere utilizzati nel corpo di un processo sono quelli tipici di

ogni linguaggio di programmazione: espressioni ed assegnamenti, costrutti condizionali e cicli.

Questi ultimi, tuttavia, pongono una serie di problemi rispetto alla sintesi ed appartengono allo

stile di specifica behavioural, di cui non parliamo in questa breve introduzione.

5.2.1 Statement if-then-else

Il costrutto condizionale di base del linguaggio VHDL è lo statement if. Nella forma più generale

esso ha la seguente sintassi:

if condition_1 then

statement_1

[elsif condition_2 then

statement_2]

...

[else

statement_N]

end if;

Come si nota, sia il ramo sia il ramo sono opzionali. Inoltre il numero di rami

eslsif else

può essere arbitrario. A tale proposito è importante sottolineare e ricordare un concetto

elsif

già introdotto parlando degli assegnamenti condizionali. Anche per il costrutto vale infatti la

if

regola per cui tutti i possibili casi devono essere esplicitamente considerati. Questo, molto spesso

si riduce all’obbligo di avere il ramo Quindi, escluse le eccezioni che vedremo nel seguito,

else.

la forma minima corretta di tale costrutto è la seguente:

if condition then

statement_then

else statement_else

end if;

Il significato è quello desumibile dai più comuni linguaggi di programmazione: se la condizione

è vera l’insieme degli statement sarà abilitato, mentre se la

condition statement_then

condizione è falsa saranno gli statement ad essere abilitati. Consideriamo

statement_else

ora un primo, semplice esempio. Supponiamo di voler realizzare un multiplexer a due ingressi e

a

ed indichiamo con l’uscita e con il segnale di selezione di tale elemento. Abbiamo visto che

b u s

lo statement concorrente di assegnamento condizionato è adatto a tale scopo, precisamente:

architecture rtl_concurrent of mux is

begin

u <= a when s=’0’ else b;

end rtl_concurrent;

Questa specifica non richiede ulteriori commenti in quanto ampiamente studiata in precedenza.

Volendo realizzare lo stesso multiplexer mediante l’uso di processi e statement sequenziali, si

procederà come segue. Pe prima cosa è necessario individuare i segnali ai quali vogliamo che il

processo sia sensibile: nel nostro caso una variazione su uno qualsiasi dei segnali ed

a, b s

produrrà un cambiamento dell’uscita, quindi i tre segnali dovranno apparire nella sesitivity list del

processo. La condizione in questo caso è molto semplice e consiste nel confronto di con il

s

valore costante 0 (avremmo anche potuto effettuare il confronto con il valore, ricordando di

scambiare il ramo then con il ramo else). In conclusione possiamo scrivere quanto segue:

architecture rtl_sequential of mux is

begin

select: process( a, b, s )

begin

if( s = ’0’ ) then

u <= a;

else u <= b;

end process;

end rtl_sequential;

Risulta chiaro che una tale specifica è poco conveniente in termini di compattezza, benché

conduca comunque alla sintesi di un multiplexer. Consideriamo ora un esempio leggermente più

complesso e cioè un multiplexer a 4 ingressi in cui il segnale di selezione s è dunque un vettore di

due bit. Secondo lo schema di specifica concorrente si scriverebbe (tralasciando alcuni dettagli

ovvi):

with s select

u <= a when ”00”,

b when ”01”,

c when ”10”,

d when ”11”,

’X’ when others;

Questa scrittura porta alla sintesi di una rete del tipo:

a

b u

c

d s(0) s(1)

In termini di statement sequenziali, una soluzione potrebbe essere la seguente:

if( s = ”00” ) then

u <= a;

elsif( s = ”01” ) then

u <= b;

elsif( s = ”10” ) then

u <= c;

elsif( s = ”11” ) then

u <= d;

else u <=’X’;

end if;

Questa scrittura, tuttavia, produce un risultato diverso da quello che ci si potrebbe aspettare per il

fatto che lo statement sequenziale if impone un ordine di valutazione delle condizioni. La

struttura che ne deriva è quindi la seguente:

a

b u

c

d

s(0)

s(1)

Stabilito che la funzione svolta da tale rete è corretta ed equivalente a quella del circuito ottenuto

dalla sintesi della specifica comportamentale, si ha un differenza in termini di temporizzazione. In

particolare notiamo che se il segnale di ingresso attraversa un solo multiplexer per

s=”00” a

propagarsi fino all’uscita (quindi 2 livelli di logica) mentre se oppure i segnali

s=”10” s=”11”

o devono attraversare tre multiplexer ovvero 6 livelli di logica. Questo problema di

c d

temporizzazione a volte deve essere esplicitamente considerato e deve essere il criterio che guida

il progettista verso uno stile di specifica parallelo oppure sequenziale.

Una soluzione ancora differente potrebbe essere la seguente:

if( s(0) =’0’ ) then

if( s(1) = ’0’ ) then

u <= a;

else u <= b;

end if;

else if( s(1) = ’0’ ) then

u <= c;

else u <= d;

end if;

end if;

Questo stile di specifica conduce ad una rete ancora diversa, e cioè:

a

b

s(1) u

c

d

s(0)

L’utilizzo del costrutto if, quindi, offre la possibilità di un notevole controllo sull’architettura che

si intende realizzare.

5.2.2 Statement case

Molto spesso accade di avere la necessità di confrontare un segnale con una serie di costanti e

eseguire operazioni subordinatamente all’esito di tale confronto. Come risulta chiaro, è possibile

realizzare una tale rete mediante l’uso del costrutto esprimendo le

if-then-elsif-else

diverse condizioni nei diversi rami. Secondo quanto appena discusso, tuttavia, una tale struttura è

intrinsecamente sequenziale e comporta sia l’introduzione di una priorità per i vari costrutti, sia lo

sbilanciamento della rete dal punto di vista dei ritardi di propagazione delle diverse linee. Infine,

una struttura molto complessa di if, eventualmente annidati, risulta poco leggibile. Per ovviare a

tutti questi problemi si ricorre allo statement Tale costrutto è l’equivalente sequenziale del

case.

costrutto concorrente dotato di una maggiore flessibilità. La sintassi generale

with-select,

del costrutto è la seguente:

case

case signal_name is

when range_1 =>

statement_1

when range_2 =>

statement_2

....

when range_N =>

statement_N

when others =>

statement_default

end case;

Il significato è deducibile facendo riferimento o al costrutto di molti linguaggi di

case

programmazione, oppure ricordando la sintassi ed il significato del costrutto with-select.

Ad esempio, il multiplexer 4-a-1 di un esempio precedente potrebbe essere specificato così:

case sel is

when ”00” =>

u <= a;

when ”01” =>

u <= b;

when ”10” =>

u <= c;

when ”11” =>

u <= d;

when others =>

u <= ’X’;

end case;

In questo semplice esempio abbiamo usato come condizioni delle costanti. Le clausole del

when

costrutto tuttavia, permettono di verificare condizioni più complesse in forma compatta.

case,

La sintassi generale di una condizione è la seguente:

values [ | values] ...

in cui può essere o una costante oppure un range sia per variabili di tipo sia

values integer,

per tipi enumerativi definiti dall’utente. Ad esempio, il seguente case statement è corretto:

signal x: integer range 0 to 15;

case x is

when 0 =>

z <= ”00”;

when 1 | 3 =>

z <= ”01”;

when 4 to 8 | 2 =>

z <= ”10”;

when 9 to 15 =>

z <= ”1”;

when others =>

z <= ”XX”;

end case;

Quando le condizioni dei diversi rami sono espresse mediante range potrebbe accadere che alcuni

dei range siano parzialmente sovrapposti. Questo fatto costituisce una violazione delle regole di

sintesi e pertanto è un errore che deve essere evitato.

5.2.3 Statement for

Lo statement for è utilizzato per realizzare cicli. Nella specifica a livello RTL il costrutto for deve

sottostare ad alcuni vincoli che vdremo tra breve. La sua sintassi generale è la seguente:

for identifier in range loop

statements

end loop;

L’identificatore identifier è la variabile di controllo del ciclo che ad ogni iterazione assume i

valori appartenenti all’intervallo specificato da range. Si noti che identifier non è un segnale e

pertanto non può essere usato in nessun costrutto che richiede come operando un segnale.

L’intervallo di variazione di tale indice, cioè range, deve essere un intervallo costante ovvero tale

per cui gli estremi siano noti al momento della sintesi. L’intervallo può assumere una delle

seguenti tre forme:

first to last

last downto first

array_name’range

Le prime due forme hanno lo stesso significato già visto per la specifica delle dimensioni di un

array e per la descrizione delle slice. La terza forma sfrutta l’attributo dei segnali VHDL.

range

Se è il nome di un segnale di tipo vettoriale allora l’attributo applicato a

array_name range

tale segnale ritorna l’intervallo di variazione degli indici del vettore stesso. Così, ad esempio,

avendo dichiarato il segnale come:

datain

signal datain: std_logic_vector(1 to 8);

l’espressione ritorna il range espresso come In questo modo è

datain’range 1 to 8.

possibile accedere a tutti gli elementi di un array senza necessariamente conoscerne a priori le

dimensioni. Infine, dato che il costrutto è consentito solo all’interno di un process, il suo

for

corpo, indicato con statements nella sintassi mostrata, è necessariamente composto da una lista di

statement sequenziali.

Vediamo un semplice esempio di utilizzo del costrutto for per l’assegnamento di un segnale

vettoriale di 16 bit ad un segnale vettoriale della stessa dimensione, svolto bit a bit.

b a

signal a, b: std_logic_vector(0 to 15);

...

for I in 0 to 15 loop

a(I) <= b(I);

end loop;

Se la dimensione dei segnali in esame fosse, ad esempio dipendente da un generic N, potremmo

effetture lo stesso assegnamento mediante la scrittura:

signal a, b: std_logic_vector(0 to N-1);

...

for I in 0 to N-1 loop

a(I) <= b(I);

end loop;

Un’altrenativa per la specifica del range di variazione dell’indice potrebbe anche essere:

signal a, b: std_logic_vector(0 to N-1);

...

for I in b’range loop

a(I) <= b(I);

end loop;

Sull’indice del ciclo è consentito svolgere operazioni aritmetiche, purchè sia rispettata la

condizione di calcolabilità al momento della sintesi. Ad esempio, per copiare il vettore b di 16 bit

nel vettore a invertendo l’ordinamento dei bit possiamo scrivere:

signal a, b: std_logic_vector(0 to 15);

...

for I in 0 to 15 loop

a(I) <= b(15-I);

end loop;

Vediamo ora come realizzare uno shifter puramente combinatorio in grado di effettuare lo

scorrimento di M bit verso destra di una parola di lunghezza pari ad N bit. L’entity declaration è:

entiry shift_right_N_M is

generic( N: integer;

M: integer );

port( data_in: in std_logic_vector(0 to N-1);

data_out: out std_logic_vector(0 to N-1) );

end shift_right_N_M;


PAGINE

107

PESO

804.04 KB

AUTORE

Atreyu

PUBBLICATO

+1 anno fa


DESCRIZIONE DISPENSA

Il linguaggio VHDL (VLSI Hardware Description Language) è un linguaggio per la descrizione dell’hardware. Questa prima definizione sottolinea un aspetto molto importante: il VHDL non è un linguaggio eseguibile ovvero non descrive quali operazioni un esecutore deve svolgere per ricavare il risultato di una elaborazione, bensì descrive gli elementi che costituiscono il circuito digitale in grado di effettuare l’elaborazioe richiesta.


DETTAGLI
Corso di laurea: Corso di laurea magistrale in ingegneria delle telecomunicazioni
SSD:
Università: L'Aquila - Univaq
A.A.: 2011-2012

I contenuti di questa pagina costituiscono rielaborazioni personali del Publisher Atreyu di informazioni apprese con la frequenza delle lezioni di Sistemi embedded e studio autonomo di eventuali libri di riferimento in preparazione dell'esame finale o della tesi. Non devono intendersi come materiale ufficiale dell'università L'Aquila - Univaq o del prof Pomante Luigi.

Acquista con carta o conto PayPal

Scarica il file tutte le volte che vuoi

Paga con un conto PayPal per usufruire della garanzia Soddisfatto o rimborsato

Recensioni
Ti è piaciuto questo appunto? Valutalo!

Altri appunti di Sistemi embedded

Programmazione concorrente
Dispensa
Sistemi Embedded
Dispensa
SystemC
Dispensa
Real-time and embedded operating systems
Dispensa