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

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;

L’architecture può essere realizzata in modo semplice mediante l’uso di due cicli for: uno per

l’assegnamento degli M bit più significativi, tutti costantemente uguali a zero ed uno per

l’assegnamento dei restanti bit, opportunamente traslati. L’architecture è quindi:

architecture RTL of shift_right_N_M is

begin

process( data_in )

begin

-- The most significant M bits

for I in 0 to M-1 loop

data_out(I) <= ’0’;

end loop;

-- The least significant N-M bits

for I in M to N-1 loop

data_out(I) <= data_in(I-M);

end loop;

end process;

end RTL;

Come mostrano tutti gli esempi presentati, i valori delle espressioni in cui compare l’indice del

ciclo I sono determinabili in fase di sintesi, o perché si tratta di costanti oppure perché si tratta di

generic il cui valore deve essere necessariamente assegnato epr procedere alla sintesi. Si noti

infine che il VHDL impone che gli statement del corpo del ciclo non modifichino il valore

dell’indice. In altre parole, la variabile di iterazione può soltanto essere letta.

5.3 Latch

Un latch è il più semplice elemento di memoria che si possa utilizzare nella realizzazione di

circuiti digitali. Un latch è dotato di un segnale di controllo, generalmente indicato con il nome

o e di uno o due ingressi di controllo. Quando il seganle di enable è attivo (alto o

enable E

basso, a seconda della convenzione scelta dal progettista, una qualsiasi variazione degli ingressi

produce una variazione sull’uscita: in questo caso si dice che il latch è trasparente. Qunado enable

non è attivo, il latch non risponde alle variazioni sugli ingressi e pertanto mantiene memorizzato

l’ultimo valore assegnato. I vari latch differiscono per la tipologia dei segnali di controllo.

Vediamo di seguito i diversi dispositivi, le loro caratteristiche e le loro specifiche in VHDL.

5.3.1 Latch D

Il comportamento di un latch D è specificato dalla seguente tabella:

D

E Q*

-

0 Q

0

1 0

1

1 1

Ciò può essere espresso a parole dicendo che quando vale 0 il latch è opaco e mantiene l’ultimo

E

valore memorizzato mentre quando vale 1 il latch è trasparente e l’uscita segue l’ingresso.

E

Questo comportamento può essere reso in modo chiaro mediante il costrutto process con una

opportuna sensitivity list. Il simbolo utilizzato comunemente per tale dispositivo è il seguente:

Q

D D Q’

E

Descriviamo ora l’entity del latch.

entity D_LATCH is

port( D: in std_logic;

E: in std_logic;

Q: out std_logic

);

end D_LATCH;

Passiamo ora ad analizzare il comportamento. Per prima cosa notiamo che il valore dell’uscita del

latch può cambiare in corrispondenza di un evento sia sul segnale sia sul segnale Pertanto

D E.

entrambi i segnali devono essere menzionati nella sensitivity list. Inoltre notiamo che l’uscita

varia solo quando il segnale di enable è alto, mentre in caso contario il segnale di uscita non deve

essere aggiornato. L’architecture che ne consegue è la seguente:

architetcture rtl of D_LATCH is

begin

latch: process( D, E )

begin

if( E = ’1’ ) then

Q <= D;

end if;

end process;

end rtl;

La prima cosa che si può notare è l’assenza del ramo else del costrutto if: questo, come abbiamo

ampiamente discusso, è un errore quando si vuole specificare un comportamento puramente

combinatorio. Il motivo di tutto ciò ammette la seguente spiegazione intuitiva: non essendo

esplicitamente specificato cosa deve succedere quando il valore di è diverso da 1, lo strumento

E

di sintesi assume che i segnali che il processo scrive (cioè quelli che compaiono a sinistra di un

simbolo di assegnamento) non varino il loro valore il che implica che deve essere presente un

elemento di memoria in grado di conservare appunto tale valore.

Si noti inoltre che in tutti gli intervalli di tempo in cui il segnale di enable è alto, il valore

dell’uscita segue il valore dell’ingresso ciò è garantito dal fatto che è presente nella

Q D: D

sensitivity list e quindi ogni evento su tale segnale è in grado di attivare il processo. Il seguente

diagramma temporale mostra il comportamento del latch D.

E

D

Q

5.3.2 Latch SR

In maniera analoga si procede per la descrizione del latch SR. Il comportamento che vogliamo

ottenere è il seguente:

E S R Q*

- -

0 Q

0 0

1 Q

0 1

1 0

1 0

1 1

1 1

1 -

Ricordiamo che il comportamento del latch SR non è definito quando entrambi i segnali di

controllo valgono 1. Il simbolo usato per rappresentare un latch SR è il seguente:

S Q

R SR Q’

E

L’entity declaration per il latch SR è la seguente:

entity SR_LATCH is

port( S: in std_logic;

R: in std_logic;

E: in std_logic;

Q: out std_logic

);

end SR_LATCH;

Il comportamento può essere descritto in diversi modi: una possibilità è la seguente.

architetcture rtl of SR_LATCH is

begin

latch: process( S, R, E )

begin

if( E = '1' ) then

if( S = '1' and R = '0' ) then

Q <= '1';

elsif( S = '0' and R = '1' ) then

Q <= '0';

elsif( S = '0' and R = '0' ) then

null;

elsif( S = '1' and R = '1' ) then

Q <= 'X';

end if;

end if;

end process;

end rtl;

Un primo commento riguarda la sensitivity list: essa contiene tutti i segnali di ingresso quindi il

processo è attivato da un evento su ognuno di essi. Il cordpo del processo, tuttavia, contiene un

principale che verifica il valore del segnale di enable. Se tale segnale è al livello logico 1,

if

allora il comportamento del latch dipende dal valore dei segnali di controllo ed ed è

S R

immediatatmente sensibile ad ogni evento su di essi, altrimenti il latch mantiene il suo stato.

È importante sottolineare un’altra caratteristica importante della specifica. Quando entrambi i

segnali di controllo hanno valore zero, il latch, pur essendo abilitato, deve mantenere il proprio

stato: per indicare questo comportamento si utilizza lo statement VHDL che indica

null,

appunto un’azione nulla. Questo comportamento è esemplificato dal seguente diagramma

temporale. E

S

R

Q

A differenza del latch D, un latch SR può essere privo del segnale di enable. In questo caso il suo

comportamento è determinato unicamente dai segnali di controllo. La tabella della verità che

descrive questo tipo di elemento è la seguente:

S R Q*

0 0 Q

0 1 0

1 0 1

1 1 -

Un esempio di diagramma temporale può aiutare a chiarire il funzionamento del latch in esame.

S

R

Q

Il simbolo utilizzato nelle rappresentazioni circuitali è simile a quello visto, ma privo del segnale

di enable in ingresso. S Q

SR

R Q’

Conseguentemente l’entity declaration e l’architecture corrispondente sono:

entity SR_LATCH_NO_ENABLE is

port( S: in std_logic;

R: in std_logic;

Q: out std_logic

);

end SR_LATCH_NO_ENABLE;

architetcture rtl of SR_LATCH_NO_ENABLE is

begin

latch: process( S, R )

begin

if( S = '1' and R = '0' ) then

Q <= '1';

elsif( S = '0' and R = '1' ) then

Q <= '0';

elsif( S = '0' and R = '0' ) then

null;

elsif( S = '1' and R = '1' ) then

Q <= 'X';

end if;

end process;

end rtl;

5.3.3 Latch JK

Un latch JK ha un comportamento simile ad un latch SR salvo il fatto che risolve la condizione di

indeterminazione data dagli ingressi e In tale latch, infatti quando entrambi i segnali di

S=1 R=1.

ccontrollo e valgono 1, l’uscita assume il valore dello stato corrente complementato.

J K

Riassumendo, il comportamento di un latch JK con segnale di enable è il seguente:

E J K Q*

- -

0 Q

0 0

1 Q

0 1

1 0

1 0

1 1

1 1

1 Q’

Il simbolo utilizzato nelle rappresentazioni circuitali è:

J Q

K JK

E Q’

Un esempio di funzionamento è rappresentato dal seguente diagramma temporale.

E

J

K

Q

La specifica del lach JK ricalca quella già vista per il latch SR rimuovendo il caso indeterminato

in cui entrambi gli ingressi di controllo valgono 1. L’entity declaration non pone alcun problema.

entity JK_LATCH is

port( J: in std_logic;

K: in std_logic;

E: in std_logic;

Q: out std_logic

);

end JK_LATCH;

Per quanto riguarda l’architecture, ricordando la specifica data dalla tabella della verità, si nota

che sarà necessario un assegnamento del tipo:

Q <= not Q;

Tuttavia è una porta di tipo e pertanto non può essere letta. Questa circostanza impone

Q out

l’utilizzo di un segnale temporaneo, ad esempio per la gestione dello stato. Tale

QINTERNAL,

segnale può quindi essere assegnato alla porta di uscita che in tal modo viene solo scritta, come

Q

richiede il VHDL. L’architecture seguente mostra una tale realizzazione.

architetcture rtl of JK_LATCH is

signal QINTERNAL: std_logic;

begin

latch: process( J, K, E )

begin

if( E = '1' ) then

if( J = '1' and K = '0' ) then

QINTERNAL <= '1';

elsif( J = '0' and K = '1' ) then

QINTERNAL <= '0';

elsif( J = '0' and K = '0' ) then

null;

elsif( J = '1' and K = '1' ) then

QINTERNAL <= not QINTERNAL;

end if;

end if;

end process;

Q <= QINTERNAL;

end rtl;

Il problema della lettura di un segnale di uscita si ripresenterà, come vedremo, nella specifica

alcuni registri e contatori.

5.4 Flip-flop

Un flip-flop è un dispositivo sequenziale in grado di mantenere memorizzato un bit. A differenza

di un latch, un flip-flop è sensibile ad un fronte del segnale di sincronizzazione piuttosto che al

suo livello. Il segnale di sicronizzazione di un flip-flop prende solitamente il nome di clock ed è

un segnale periodico.

5.4.1 Flip-flop D

Un flip-flop D è descritto dalla tabella della verità che segue. Si noti che il egnale di clock non è

esplicitamente considerato in quanto si assume che le transizioni sul valore dello stato Q*

possano avvenire solo in corrispondenza di un fronte del clock.

D Q*

0 0

1 1

Il simbolo utilizzato nelle rappresentazioni circuitali è: Q

D DFF Q’

CLK

Un esempio di funzionamento è riportato nel seguente diagramma temporale.

CLK

D

Q

Iniziamo ora con la descrizione dell’entity del flip-flop D.

entity D_FF is

port( CLK: in std_logic;

D: in std_logic;

Q: out std_logic

);

end D_FF;

Passiamo ora alla specifica dell’architecture. Nel caso dei latch la condizione cui l’aggiornamento

dell’uscita era subordinata riguardava il livello di un segnale (il segnale di enable) e come tale

poteva essere espressa semplicemente confrontando il valore dl segnale con il livello di interesse

(0 oppure 1). Nel caso dei flip-flop la condizione riulta più complessa in quanto è necessario

esprimere il verificarsi una transizione. Per fissare le idee, supponiamo che il dipositivo che

vogliamo realizzare sia sensibile al fronte di salita. In tal caso dobbiamo verificare le due seguenti

condizioni:

1. Si è verificata una transizione sul segnale di clock (evento)

2. Il valore del segnale dopo la transizione è 1

Per quanto riguarda la seconda condizione non si hanno problemi. Per esprimere il verificarsi di

un evento sul segnale è necessario ricorrere ad un costrutto particolare bassato sul concetto di

attributo VHDL. Nel caso specifico dovremo verificare che l’attributo del segnale di

event

clock sia vero. La condizione complessiva, quindi, si esprime come segue

if( CLK’event and CLK = ’1’ ) then

...

end if;

Dato che in assenza di un fronte di salita sul segnale di clock il dispositivo deve mantenere il

proprio stato, anche in questo caso il ramo dell’if dovrà essere omesso. È chiaro infine

else

che il segnale dati non dovrà apparire nella sensitivity list del processo in quanto lo stato del

D

flip-flop non è influenzato da un evento su tale segnale. Basandoci su quanto appena visto

possiamo procedere alla specifica dell’architecture.

architecture rtl_rising_edge of D_FF is

begin

ff: process( CLK )

begin

if( CLK’event and CLK = ’1’ ) then

Q <= D;

end if;

end process;

end rtl_rising_edge;

Volendo descrivere un flip-flop D sensibile al fronte di discesa, è sufficiente verificare che il

valore del segnale di clock dopo l’evento sia 0 anziché 1, ovvero:

architecture rtl_falling_edge of D_FF is

begin

ff: process( CLK )

begin

if( CLK’event and CLK = ’0’ ) then

Q <= D;

end if;

end process;

end rtl_falling_edge;

In molti casi (ad esempio nella realizzazione di machine a stati) sono utili flip-flop dotati di un

segnale di reset, sincrono o asincrono. Il segnale di reset sincrono forza il valore 0 sul flip-flop

quando vale 1 (se attivo alto) solo in corrispondenza di un evento sul clock, mentre è ignorato in

caso contrario. Questo significa che per realizzare tale comportamento in VHDL il valore del

segnale di reset deve essere testato solo quando si è verificato un evento sul clock e che quindi un

evento sul segnale di reset non deve attivare il processo. Una possibile specifica è la seguente:

entity D_FF_SYNC_RESET is

port( CLK: in std_logic;

RESET: in std_logic;

D: in std_logic;

Q: out std_logic

);

end D_FF_SYNC_RESET;

architecture rtl of D_FF_SYNC_RESET is

begin

ff: process( CLK )

begin

if( CLK’event and CLK = ’0’ ) then

if( RESET = ’1’ ) then

Q <= ’0’;

else Q <= D;

end if;

end if;

end process;

end rtl;

A volte può essere utile anche un segnale di preset, cioè un segnale di controllo indipendente che

ha lo scopo di forzare lo stato del flip-flop ad 1. Dato che utilizzeremo un costrutto if-elsif,

dobbiamo stabilire la priorità che devono avere i due segnali di reset e preset. Scegliamo, ad

esempio che il segnale di reset abbia una priorità maggiore del segnale di preset. In tal caso il

comportamento del flip-flop sarà il seguente:

RESET PRESET D Q*

1 - - 0

0 1 - 1

0 0 0 0

0 0 1 1

L’entity declaration è una estensione della precedente:

entity D_FF_SYNC_RESET_PRESET is

port( CLK: in std_logic;

RESET: in std_logic;

PRESET: in std_logic;

D: in std_logic;

Q: out std_logic

);

end D_FF_SYNC_RESET_PRESET;

Per realizzare la priorità stabilta è necessario testare dapprima il valore del segnale di reset, quindi

se questo è 0, il segnale di reset. Tutto ciò, trattandiso di segnali sincroni, deve essere fatto solo in

corrispondenza di un fronte del clock. Ecco l’architecture che specifica un tale comportamento:

architecture rtl of D_FF_SYNC_RESET_PRESET is

begin

ff: process( CLK )

begin

if( CLK’event and CLK = ’0’ ) then

if( RESET = ’1’ ) then

Q <= ’0’;

elsif( PRESET = ’1’ ) then

Q <= ’1’;

else Q <= D;

end if;

end if;

end process;

end rtl;

Il simbolo comunemente utilizzato per un tale flip-flop è il seguente:

Q

D

RESET DFF

PRESET Q’

CLK

Vediamo ora come realizzare un flip-flop D dotato di un segnale di reset asincrono. Un reset

asincrono forza il valore 0 in uscita, indipendentemente dal verificarsi o meno di un evento sul

clock. La risposta del flip-flop ad un evento sul reset asincrono è quindi immediata: ciò significa

che il processo VHDL che implementa tale dispositivo deve essere sensibile anche al segnale di

reset. Inoltre, dato che il reset asincrono produce un cambiamento nello stato indipendentemente

dal verificarsi di un evento sul clock, il suo valore andrà verificato prima di analizzare il clock. I

simboli comunemente usati per i dispositivi con segnali di reset/preset asincroni ed attivi alti sono

i seguenti: PRESET PRESET

Q Q Q

D D D

DFF DFF DFF

Q’ Q’ Q’

CLK CLK CLK

RESET RESET

Quando gli stessi segnali sono attivi bassi si utilizzano invece i simboli seguenti:

PRESET PRESET

Q Q Q

D D D

DFF DFF DFF

Q’ Q’ Q’

CLK CLK CLK

RESET RESET

Vediamo ora la specifica completa, iniziando, come di consueto, dall’entity declaration:

entity D_FF_ASYNC_RESET is

port( CLK: in std_logic;

RESET: in std_logic;

D: in std_logic;

Q: out std_logic

);

end D_FF_SYNC_RESET;

La corrispondente architecture, codificata secondo le considerazioni appena svolte è la seguente:

architecture rtl of D_FF_ASYNC_RESET is

begin

ff: process( CLK )

begin

if( RESET = ’1’ ) then

Q <= ’0’;

else if( CLK’event and CLK = ’0’ ) then

Q <= D;

end if;

end if;

end process;

end rtl;

5.4.2 Flip-flop SR

Il flip-flop SR dispone di due segnali di controllo ed ha un comportamento molto simile al latch

SR già visto. L’unica differenza consiste nel modo in cui lo stato viene aggiornato: nel latch SR

ciò avviene durante tutto il periodo in cui il segnale di enable è attivo mentre nel flip-flop

l’aggiornamento avviene solo in corrispondenza di un fronte del clock. Il comportamento è quindi

descritto dalla seguente tabella della verità:

S R Q*

0 0 Q

0 1 0

1 0 1

1 1 -

Il simbolo utilizzato nelle rappresentazioni circuitali è mostrato di seguito.

Q

S

R SRFF Q’

CLK

La specifica segue lo schema di quella già vista per il flip-flop D per quanto riguarda la sensibilità

al fronte di clock e quella del latch SR per quanto concerne l’effetto dei segnali di controllo.

entity SR_FF is

port( CLK: in std_logic;

S: in std_logic;

R: in std_logic;

Q: out std_logic

);

end SR_FF;

architecture rtl of SR_FF is

begin

ff: process( CLK )

begin

if( CLK’event and CLK = ’0’ ) then

if( S = '1' and R = '0' ) then

Q <= '1';

elsif( S = '0' and R = '1' ) then

Q <= '0';

elsif( S = '0' and R = '0' ) then

null;

elsif( S = '1' and R = '1' ) then

Q <= 'X';

end if;

end if;

end process;

end rtl;

Anche a questo elemento è possibile aggiungere segnali di reset e/o preset sia sincroni che

asincroni seguendo i principi già visti per il flip-flop D e ricalcando gli schemi di

implementazione già presentati.

5.4.3 Flip-flop JK

Per la realizzazione del flip-flop JK adottiamo lo stesso schema visto per il flip-flop SR. La

tabella della verità ed il simbolo circuitale sono riportati di seguito.

J K Q*

0 0 Q

0 1 0

1 0 1

1 1 Q’ J Q

K SRFF

CLK Q’

La specifica VHDL non pone particolari problemi. È solo opportuno ricordare che è necessario

introdurre un segnale locale temporaneo per mantere lo stato del flip-flop in quanto il segnale Q,

essendo una porta di uscita (quindi di tipo out) può soltanto essere scritta. Per maggiori dettagli si

veda la descrizione del latch JK data in precedenza. Vediamo ora la specifica VHDL.

entity JK_FF is

port( CLK: in std_logic;

J: in std_logic;

K: in std_logic;

Q: out std_logic

);

end JK_FF;

architetcture rtl of JK_FF is

signal QINTERNAL: std_logic;

begin

ff: process( CLK )

begin

if( CLK’event and CLK = '1' ) then

if( J = '1' and K = '0' ) then

QINTERNAL <= '1';

elsif( J = '0' and K = '1' ) then

QINTERNAL <= '0';

elsif( J = '0' and K = '0' ) then

null;

elsif( J = '1' and K = '1' ) then

QINTERNAL <= not QINTERNAL;

end if;

end if;

end process;

Q <= QINTERNAL;

end rtl;

5.4.4 Flip-flop T

L’ultimo tipo di flip-flop di cui ci occuperemo è il flip-flop T, detto comunemente toggle. Esso

dispone di un ingrsso di controllo, appunto, che, se alto, provoca la commutazione dello stato,

T

altrimenti mantiene il valore precedente. È bene notare che tale elemento non dispone di alcun

ingresso di controllo in grado di forzare un valore specifico dello stato e ciò significa che, nella

sua forma più semplice, tale flip-flop non può essere portato in uno stato iniziale noto. Questa

circostanza rende il flip-flop toggle spesso inutilizzabile se privo di un segnale di reset o di preset

sincrono o asincrono. Approfondiremo questo problema tra poco.Vediamo dapprima il

comportamento del flip-flop mediante la sua tabella della verità:

T Q*

0 Q

1 Q’

Il simbolo circuitale è analogo ai precedenti: Q

T TFF Q’

CLK

La specifica di questo elemento, analogamente a quella del flip-flop JK, richiede l’uso di un

segnale locale per la memorizzazione dello stato. Il codice VHDL è quindi il seguente.

entity T_FF is

port( CLK: in std_logic;

T: in std_logic;

Q: out std_logic

);

end T_FF;

architetcture rtl of T_FF is

signal QINTERNAL: std_logic;

begin

ff: process( CLK )

begin

if( CLK’event and CLK = '1' ) then

if( T = '1' ) then

QINTERNAL <= not QINTERNAL;

end if;

end if;

end process;

Q <= QINTERNAL;

end rtl;

Il dispositivo specificato in questo modo non dispone di un segnale per forzare lo stato ad un

valore preciso ma può sltanto commutare oppure mantenere il valore corrente. In un dato istante,

perciò, non è possibile forzare un valore specifico. Questo è spesso un problema, tuttavia, in

alcuni casi il valore iniziale è ininfluente. Vediamo una applicazione tipica del flip-flop T: il

divisore di clock. Un divisore di clock è un dispositivo che riceve un clock con frequenza f in

ingresso e fornisce in uscita un segnale di clock con frequenza f/k. Se k è una potenza del due, il

divisore assume una forma particolarmente semplice. Supponiamo di voler realizzare un circuito

che riceve in ingresso un clock ad una frquenza f (arbitraria) e fornisce in uscita due nuovi

CKL

segnali di clock e rispettivamente a frequenza f/2 ed f/4. Il diagramma temporale

CLK2 CLK4

seguente mostra l’andamento dei segnali e

CLK, CLK2 CLK4.

CLK

CLK2

CLK4

Per realizzare questo comportamento si può ricorrere ad esempio alla seguente soluzione:

CLK2

CLK4

1 TFF

TFF

CLK

Tale scelta, tuttavia, non è corretta in quanto i due flip-flop non sono sincroni poichè alimentati

da due segnali di clock diversi. Per realizzare un divisore sincrono è necessario che i due flip-flop

siano alimentati dallo stesso clock. Una soluzione potrebbe essere quindi la seguente:

CLK2

CLK4

1 TFF TFF

CLK

Passimo quindi alla stesura del codice VHDL del dispositivo. L’entity declaration è la seguente:

entity CLK_DIV_2_4 is

port( CLK: in std_logic;

CLK2: out std_logic;

CLK4: out std_logic

);

end CLK_DIV_2_4;

Per descrivere l’architecture procediamo come già visto. Il comportamento di un flip-flop T

richiede l’uso di un segnale temporaneo per mantenere lo stato, quindi il divisore nell’insieme

utilizzerà due segnali temporanei e uno per ogni flip-flop.

CLK2T CLK4T,

architetcture rtl of CLK_DIV_2_4 is

signal CLK2T: std_logic;

signal CLK4T: std_logic;

begin

-- Divides CLK by two

div2_1: process( CLK )

begin

if( CLK’event and CLK = '1' ) then

-- Don’t test T because is always 1

CLK2T <= not CLK2T;

end if;

end process;

-- Divides CLK2 by two, i.e. CLK by four

div2_2: process( CLK )

begin

if( CLK’event and CLK = '1' ) then

-- The toggle signal is CLK2T

if( CLK2T = ’1’ ) then

CLK4T <= not CLK4T;

end if;

end if;

end process;

-- Assigns output signals

CLK2 <= CLK2T;

CLK4 <= CLK4T;

end rtl;

Dal codice e dalla definizione del comportamento del flip-flop T risulta chiaro che il valore dello

stato non può che essere indeterminato in quanto non esiste alcun segnale di controllo forzante.

Nella realtà fisica, il valore dello stato nel momento in cui il circuito viene alimentato sarà o 1 o 0

benché non è possibile determinare quale dei due valori. Indipendentemente da ciò, il circuito

funzionerà correttamente. Questo però pone un problema nel momento in cui volessimo simulare

il comportamento di tale rete: il simulatore, non potendo stabilire il valore iniziale dello stato dei

flip-flop assegnerà ad essi il valore simbolico X che, come conseguenza si propagherà attraverso i

flip-flop e produrrà per ogni segnale un valore indeterminato. Questo fatto impedisce di verificare

mediante simulazione il comportamento del circuito. Inoltre, e soprattutto, una regola di buona

progettazione impone che ogni flip-flop di un circuito sia dotato almeno del segnale di reset. Per

questo motivo la rete migliore per la realizzazione del divisore è la seguente:

CLK2

CLK4

1 TFF TFF

CLK

RESET

L’andamento dei segnali per un tale dispositivo è mostrato dal seguente diagramma temporale.

RESET

CLK

CLK2

CLK4

L’introduzione del segnale di reset porta quindi alla seguente specifica:

entity CLK_DIV_2_4_RESET is

port( CLK: in std_logic;

RESET: in std_logic;

CLK2: out std_logic;

CLK4: out std_logic

);

end CLK_DIV_2_4_RESET;

architetcture rtl of CLK_DIV_2_4_RESET is

signal CLK2T: std_logic;

signal CLK4T: std_logic;

begin

-- Divides CLK by two

div2_1: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

CLK2T <= ’0’;

elsif( CLK’event and CLK = '1' ) then

-- Don’t test T because is always 1

CLK2T <= not CLK2T;

end if;

end process;

-- Divides CLK2 by two, i.e. CLK by four

div2_2: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

CLK2T <= ’0’;

elsif( CLK’event and CLK = '1' ) then

-- The toggle signal is CLK2T

if( CLK2T = ’1’ ) then

CLK4T <= not CLK4T;

end if;

end if;

end process;

-- Assigns output signals

CLK2 <= CLK2T;

CLK4 <= CLK4T;

end rtl;

Ora vediamo un modo per realizzare la stessa specifica in maniera molto più compatta. Questo è

un caso particolare di pipeline, come vedremo nell’ultima parte di questo capitolo. L’idea sta nel

raggruppare in un unico processo la descrizione del comportamento dei due flip-flop, come

mostra la seguente architecture:

architetcture rtl of CLK_DIV_2_4_RESET is

signal CLK2T: std_logic;

signal CLK4T: std_logic;

begin

-- Divides CLK by two and by four

div24: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

-- Resets both signals (flip-flops)

CLK2T <= ’0’;

CLK4T <= ’0’;

elsif( CLK’event and CLK = '1' ) then

-- First flip-flop: Toggle is always ’1’

CLK2T <= not CLK2T;

-- Second: flop-flop: Toggle is CLK2T

if( CLK2T = '1' ) then

CLK4T <= not CLK4T;

end if;

end if;

end process;

-- Assigns output signals

CLK2 <= CLK2T;

CLK4 <= CLK4T;

end rtl;

Con questo semplice esempio concludiamo la presentazione degli elementi sequenziali di base

per passare ad un nuovo argomento: i registri.

6. Registri

Nell’accezione più comune un registro è un insieme di flip-flop, molto spesso di tipo D, destinato

a memorizzare non un singolo bit, bensì parole di dimensione maggiore. Esistono poi molti

registri che svolgono anche operazioni di manipolazione dei dati quali scorrimento,

serializzazione, parallelizzazione ecc. Nel seguito vediamo come realizzare in VHDL alcuni dei

registri più utilizzati, commentando gli apsetti di progettazione più rilevanti.

6.1 Registro parallelo-parallelo

Un registro parallelo-parallelo è quello cui ci si riferisce normalemente quando si parla

semplicemente di registro. Esso è costituito da un insieme (banco) di flip-flop D, tutti

sincronizzati dallo stesso segnale di clock, in cui le linee dati di ingresso sono connesse in

parallelo agli ingressi dati dei flip-flop e le uscite dati sono le uscite degli stessi flip-flop. Un

registro parallelo-parallelo a 4 bit ha pertanto una struttura come quella mostrata in figura. Nella

parte sinistra della figura sono mostrati esplicitamente tutti i segnali e le connessioni compresi il

segnale di clock ed il segnale di reset La figura di destra mostra invece una

CLK RESET.

rappresentazione semplificata in cui la presenza di unico segnale di clock ed un unico segnale di

reset comune a tutti i flip flop è sottintesa.

X(0) Y(0) X(0) Y(0)

CLK

RESET

X(1) Y(1) X(1) Y(1)

Y(2)

X(2) Y(2) X(2) Y(3)

X(3) Y(3) X(3)

Questa struttura ammette una semplice rappresentazione VHDL, riportata di seguito.

entity REG_PP_4_BIT is

port( CLK: in std_logic;

RESET: in std_logic;

X: in std_logic_vector(0 to 3);

Y: out std_logic_vector(0 to 3)

);

end REG_PP_4_BIT;

architetcture rtl of REG_PP_4_BIT is

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

Y(0) <= ’0’;

Y(1) <= ’0’;

Y(2) <= ’0’;

Y(3) <= ’0’;

elsif( CLK’event and CLK = '1' ) then

Y(0) <= X(0);

Y(1) <= X(1);

Y(2) <= X(2);

Y(3) <= X(3);

end if;

end process;

end rtl;

Si noti che le uscite di tutti i flip-flop sono aggiornate nello stesso istante poichè tutti i lip-flop

sono sensibili allo stesso clock. Per questo motivo è possibile scrivere un unico process in cui

tutti i segnali di ingresso sono assegnati ai corrispondenti segnali di uscita. Inoltre, trattandosi di

segnali vettoriali, possiamo descrivere il registro in forma molto più compatta e leggibile, ovvero:

architetcture rtl of REG_PP_4_BIT is

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

Y <= ”0000”;

elsif( CLK’event and CLK = '1' ) then

Y <= X;

end if;

end process;

end rtl;

Questa scrittura, nonchè il concetto su cui si basa, giustifica l’uso del simbolo seguente per la

rappresentazione circuitale di registri parallelo-parallelo:

4 4

X Y

CLK

RESET

Seguendo questo schema, ricordando il significato dei generic ed il costrutto others => ...,

possiamo facilmente descrivere un registro generico ad N bit. Ecco il codice.

entity REG_PP_N_BIT is

generic( N: integer );

port( CLK: in std_logic;

RESET: in std_logic;

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

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

end REG_PP_N_BIT;

architetcture rtl of REG_PP_N_BIT is

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

Y <= (others => ’0’);

elsif( CLK’event and CLK = '1' ) then

Y <= X;

end if;

end process;

end rtl;

6.2 Registro serie-serie: shift register

Un registro serie-serie è un componente dotato di unico ingresso dati di un bit ed un unica uscita

dati anch’essa di un bit. Il registro è costituito da un banco di flip-flop connessi in cascata, cioè

connessi in modo tale che l’uscita di uno sia l’ingresso del successivo. Ad esempio, un registro a

scorrimento o shift register a 4 bit ha la seguente struttura:

T1 T2 T3

X Y

CLK

RESET

A differenza del registro parallelo-parallelo, questo registro non ha alcun simbolo standard. Il suo

comportamento è quello di propagare un segnale di ingresso attraverso i vari flip-flop in

corrispondenza di ogni fronte del clock, come mostra il seguente diagramma temporale.

RESET

CLK

X

T1

T2

T3

Y

Per procedere all’implementazione VHDL possiamo descrivere a parole il comportamento del

registro a scorrimento in questo modo: ad ogni colpo di clock assume il valore di

T1 X, T2

assume il valore di assume il valore di e assume il valore di

T1, T3 T2 Y T3.

Si noti che questa descrizione, se mal interpretata, potrebbe essere letta come: ad ogni colpo di

clock assume il valore di Ciò non è vero poichè all’atto dell’attivazione di un process il

Y X.

valore di tutti i segnali viene letto contemporaneamente ed i nuovi valori sono assegnati,

anch’essi contemporaneamente, in un istante successivo di tempo. Quindi al primo fronte di

clock, il valore di e viene letto, quindi vengono eseguiti gli assegnamenti ai segnali

X, T1, T2 T3

ed

T1, T2, T3 Y.

Come regola mnemonica è utile ricordare che in un process tutti i segnali sono assegnati (cioè il

loro valore è modificato) alla fine del process stesso.

Al solito, iniziamo dalla descrizione dell’entity:

entity REG_SS_4_BIT is

port( CLK: in std_logic;

RESET: in std_logic;

X: in std_logic;

Y: out std_logic

);

end REG_SS_4_BIT;

architetcture rtl of REG_SS_4_BIT is

signal T1, T2, T3: std_logic;

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T1 <= ’0’;

T2 <= ’0’;

T3 <= ’0’;

Y <= ’0’;

elsif( CLK’event and CLK = '1' ) then

T1 <= X;

T2 <= T1;

T3 <= T2;

Y <= T3;

end if;

end process;

end rtl;

Un modo equivalente ma più elegante, per descrivere lo stesso comportamento ricorre all’uso di

un vettore temporaneo anziché più segnali scalari:

architetcture rtl of REG_SS_4_BIT is

signal T: std_logic_vector(0 to 3);

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T <= ”0000”;

elsif( CLK’event and CLK = '1' ) then

T(0) <= X;

T(1) <= T(0);

T(2) <= T(1);

T(3) <= T(2);

end if;

end process;

-- Assigns the output signal

Y <= T(3);

end rtl;

Ricordando l’uso delle slice, la stessa architettura assume una forma ancora più compatta,

regolare e leggibile.

architetcture rtl of REG_SS_4_BIT is

signal T: std_logic_vector(0 to 3);

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T <= ”0000”;

elsif( CLK’event and CLK = '1' ) then

T(0) <= X;

T(1 to 3) <= T(0 to 2);

end if;

end process;

-- Assigns the output signal

Y <= T(3);

end rtl;

Quest’utlima forma ci permette di generalizzare la struttura del registro rendendo la profordità

funzione di un generic. Vediamo il codice VHDL corrispondente:

entity REG_SS_N_BIT is

generic( N: integer );

port( CLK: in std_logic;

RESET: in std_logic;

X: in std_logic;

Y: out std_logic

);

end REG_SS_N_BIT;

architetcture rtl of REG_SS_N_BIT is

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

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T <= (others => ’0’);

elsif( CLK’event and CLK = '1' ) then

T(0) <= X;

T(1 to N-1) <= T(0 to N-2);

end if;

end process;

-- Assigns the output signal

Y <= T(N-1);

end rtl;

Solo a titolo di esempio prendiamo in considerazione un’altra possibilità implementativa basata

sull’uso del costrutto sequenziale for. Lo scorrimento, infatti, ricorrendo ancora all’uso di un

vettore temporaneo, può essere espresso dicendo che l’elemento i-esimo del vettore assume il

valore dell’elemento (i-1)-esimo, e ciò per tutti gli elementi ad eccezione del primo, cui deve

essere assegnato esplicitamente il valore del segnale X, e dell’ultimo che deve essere asseganto

esplicitamente al segnale Y. Vediamo quindi come questo approccio si traduce in una specifica

VHDL corretta.

architetcture rtl of REG_SS_N_BIT is

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

begin

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T <= (others => ’0’);

elsif( CLK’event and CLK = '1' ) then

-- First element

T(0) <= X;

-- Other elements

for I in 1 to N-1 loop

T(I) <= T(I-1);

end loop;

end if;

end process;

-- Assigns the output signal

Y <= T(N-1);

end rtl;

Si noti che l’assegnamento del segnale effettuato all’interno del process, dopo la fine del ciclo

Y

avrebbe costituito un grave errore. In tal caso, infatti, il valore di sarebbe stato assegnato

for Y

dopo aver letto il valore di ovvero nel successivo ciclo di clock. Il risultato sarebbe

T(N-1)

quindi stato quello di avere un flip-flop aggiuntivo tra ed inoltre, tale flip-flop

T(N-1) Y;

sarebbe privo di segnale di reset, come si può chiaramente dedurre dall’assenza di uno statement

di inizializzazione di al valore zero.

Y

6.3 Registro serie-serie circolare: circular shift register

Un registro serie-serie circolare dispone un ingresso di un bit, un’uscita di un bit e di due

X Y

modalità di funzionamento: una di caricamento ed una di scorrimento circolare, selezionabili

mediante un segnale di controllo Nella modalità di caricamento, che supponiamo essere

LOAD.

attiva quando il segnale vale 1, il registro si comporta come un normale shift register,

LOAD

propagando il segnale di ingresso attraverso i vari flip-flop. Quando il segnale vale

X LOAD

invece zero, l’ingresso è sconnesso dal registro ed il primo flip-flop (quello più a sinistra)

X

riceve in ingresso il bit memorizzato nell’ultimo flip-flop (quello più a destra), ottenedo quindi un

ricircolo dei dati nella catena di flip-flop. Lo schema seguente mostra l’architettura del registro.

0 XT T(0) T(1) T(2) T(3) Y

1

X

LOAD

CLK

RESET

Nella figura sono evidenziati i nomi dei segnali temporanei, in particolare, trattandosi di un unico

segnale vettoriale, sono riportati gli indici. Vediamo un esempio di diagramma temporale.

RESET

CLK

LOAD

X

XT

T(0)

T(1)

T(2)

T(3)

Y

Questo modo di rappresentare il valore dei segnali in un circuito è molto poco compatto. Spesso

si ricorre ad una rappresetazione in cui è evidenziato il valore binario di un intero segnale

vettoriale piuttosto che il valore delle singole linee. Il diagramma appena visto diviene quindi:

RESET

CLK

LOAD

X

XT 0000 1000 0100 0010 0001 1000 0100

T

Y

Quest’ultima rappresentazione evidenzia chiaramente lo scorrimento del bit ad uno nella catena di

flip-flop rappresentata dal segnale vettoriale Dal confronto con lo shift register visto in

T.

precedenza si nota che il registro circolare altro non è che un normale shift register con ingresso

ed uscita combinato con un multiplexer a monte che permette di selezionare il valore da

XT Y,

assegnare ad mediante il segnale di controllo Quando vale 0 il multiplexer

XT LOAD. LOAD

seleziona (ovvero altrimenti Procediamo quindi alla specifica in VHDL seguendo

Y T(3)) X.

questo schema che mantiene chiaramente separato il multiplexer dal registro a scorrimento.

entity REG_SS_CIRCULAR_4_BIT is

port( CLK: in std_logic;

RESET: in std_logic;

LOAD: in std_logic;

X: in std_logic;

Y: out std_logic

);

end REG_SS_CIRCULAR_4_BIT;

architetcture rtl of REG_SS_CIRCULAR_4_BIT is

signal T: std_logic_vector(0 to 3);

signal XT: std_logic;

begin

-- Input multiplexer

XT <= T(3) when LOAD = ’0’ else

X when LOAD = ’1’ else

’X’;

-- Register

reg: process( CLK, RESET )

begin

if( RESET = ’1’ ) then

T <= ”0000”;


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