Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
COMPONENTI FONDAMENTALI
Mentre a livello gate erano le porte logiche, qua sono dei blocchi di dimensione maggiore. Anche i registri
sono dei componenti fondamentali per questo livello. Le macro-funzionali sono della macro, cioè sono dei
blocchi funzionali che svolgono delle funzioni di alto livello (es: un addizionatore è un macro-funzionale). Fa
parte dei componenti fondamentali anche la logica di instradamento, cioè tutto ciò che serve per
movimentare i dati, i segnali, l’informazione, all’interno del sistema. In particolare, fanno parte della logica
di instradamento i multiplexer. Anche le macchine a stati finiti sono dei componenti fondamentali.
DATA PATH & CONTROL UNIT
Ogni progetto, a questo livello, si divide in due parti: data path, cioè il cammino dei dati, e control unit, cioè
unità di controllo. I registri, le macro-funzionali e la logica di instradamento stanno nel data path, mentre la
control unit è la macchina a stati finiti. La macchina a stati finiti, cioè la control unit, ha il ruolo di controllare
i multiplexer perché il flusso dei dati è un flusso che segue un suo percorso di elaborazione implementando
una specifica, ma dove c’è bisogno di scegliere da dove debbano provenire i dati abbiamo bisogno di un
segnale di controllo. Questo segnale di controllo è generato dalla control unit.
Stili di progetto
1. Si parte da una specifica funzionale e si cerca di fare tutto in un solo ciclo di clock e questo è quello
che abbiamo fatto con il RPA senza introdurre i FF. Tutto veniva fatto in un solo ciclo di clock. Se
devono esserci i registri, ci sono in ingresso e in uscita che forniscono gli operandi e campionano i
risultati.
2. Questo stile di progetto consiste nello scomporre il progetto in più stadi segmentandolo in modo tale
che tra uno stadio e l’altro ci siano dei registri che disaccoppiano e sincronizzano. Cioè essi fanno si
che uno stadio possa svolgere parte del lavoro per poi produrre dei risultati parziali che vengono
campionati dai registri che diventano gli input per lo stadio successivo e così via. Se noi spezziamo
l’elaborazione a n stadi allora serviranno n cicli di clock per attraversali tutti e arrivare ai risultati.
Questo è quello che abbiamo fatto con il SRPA.
3. Questa opzione si applica solo a progetti a più stadi e il funzionamento è il pipelining, cioè se abbiamo
spezzato l’elaborazione, quindi il data path, in più stadi separati da registri allora possiamo farli
lavorare come una catena di montaggio. Appena il primo stadio ha svolto il suo compito passa i
risultati parziali al secondo stadio e nel frattempo inizia ad elaborare nuovi dati. Questo è quello che
abbiamo fatto con il PRPA.
4. Questa opzione di progetto è quella della condivisione di risorse. Questa opzione l’abbiamo utilizzata
con il BSA, cioè all’addizionatore seriale che usava un solo FA per svolgere il calcolo di tutti i bit. Anche
in questo stile di progetto, dopo aver spezzato l’elaborazione in cicli di clock, se nel primo ciclo di
clock c’è bisogno, ad esempio, di un moltiplicatore e nel secondo anche, allora il moltiplicatore del
primo ciclo lo possiamo usare anche nel secondo. In questo caso non possiamo fare il pipelining.
Riassumendo:
Adesso andiamo a vedere con degli esempi come queste scelte di progetto possono essere compiute. Lo
vediamo partendo da una specifica e introducendo uno strumento di rappresentazione della specifica che si
chiama data flow graph, cioè è un grafo che rappresenta il flusso dei dati.
Esempio 1
Immaginiamo di partire dalla specifica funzionale che sia un’espressione aritmetica:
=×+× × +
Questa è la funzione che io voglio calcolare. Trattandosi di un’espressione aritmetica e non sono AND e
OR ma prodotto e somma. Ora posso dare una rappresentazione con il data flow graph in cui i nodi sono gli
operatori e i collegamenti erano i legami di precedenza e di dipendenza tra le operazioni. Otteniamo allora:
× ×
+
Questa rappresentazione ci consente di effettuare già delle scelte di progetto. Per esempio, posso partire
ragionando su un implementazione a singolo stadio. Posso tirare delle linee orizzontale e taglio i segnali ad
un certo livello. Questi tagli rappresentano una certa sincronizzazione dei segnali che per tanto dal punto di
vista implementativo corrisponderà all’introduzione di registri dove si incrociano le linee.
× ×
+
Vediamo che si utilizza un solo stadio in cui vengono fatte tutte le operazioni. Se volessi riallineare i segnali
anche dopo i due moltiplicatori posso scegliere di fare un’implementazione a due stadi, cioè posso
aggiungere una nuova batteria di registri tra i moltiplicatori e gli addizionatori:
× ×
+
Quindi in questo caso divido le operazioni in due cicli di clock: il primo in cui svolgo le moltiplicazioni e il
secondo cui svolgo l’addizione. Tra queste due operazioni riesco ad allineare i segnali grazie all’introduzione
dei due registri nel mezzo. A questo punto io potrei usare questa implementazione a due stadi in pipelining
perché ho due stadi e allora quando i moltiplicatori hanno elaborato i loro output sono campionati dai registri
e quindi loro sono liberi di fare altro. L’addizionatore non prenderà più i segnali dai moltiplicatori ma dai
registri a monte. Quindi otterrei questo:
Abbiamo quindi visto l’implementazione a singolo stadio, a multiplo stadio e pipelining. La condivisione di
risorse non lo posso esemplificare in questo esempio perché le uniche due risorse uguali sono i due
moltiplicatori che funzionano parallelamente, cioè in contemporanea, quindi non posso ridurmi ad un unico
moltiplicatore. Affinché una stessa risorsa possa essere condivisa c’è bisogno che svolga la stessa funzione e
che siano messe in cicli di clock separati, cioè che la stessa funzione venga svolga in momenti diversi. Allora
potrei mettere i due moltiplicatori in due cicli di clock diversi e otterrei ad esempio:
× ×
+
Introdurre i registri in questo modo vorrebbe dire ritardare il calcolo del moltiplicatore di destra di un ciclo
di clock portandolo allo stesso livello dell’addizionatore. L’addizionatore prende in ingresso solo un segnale
stabilizzato che proviene dal moltiplicatore di sinistra, l’altro cambia a mano a mano che viene fornito. Se
volessi stabilizzare entrambi gli ingressi per l’addizionatore potrei suddividere il circuito in tre cicli di clock in
cui in ogni ciclo viene fatta un’operazione diversa. Questo vorrebbe dire ritardare di due cicli di clock l’inizio
dell’addizione e vuol dire anche far propagare a vuoto per un ciclo di clock il risultato parziale del
moltiplicatore di sinistra.
× ×
+
Ora possiamo condividere le risorse e quindi possiamo risparmiare l’hardware. Se decidiamo di non
condividere le risorse possiamo ancora lavorare in pipelining. Se invece decidiamo di utilizzare un solo
moltiplicatore allora il pipelining non è più ammesso. Non ammettere più il pipelining vuol dire tornare ad
espandere il tempo di elaborazione.
Esempio 2
Se decidiamo di fare un’implementazione a un solo stadio possiamo ragionare come facevamo a livello gate
per chiederci quale sia il cammino più lungo e il cammino più breve tra gli ingressi e le uscite. In particolare,
tra gli output dei registri di ingresso e l’input del registro di uscita. Nell’implementazione a un solo stadio il
cammino più lungo attraversa il moltiplicatore e l’addizionatore e il cammino più breve solo l’addizionatore.
Se dobbiamo stimare il tempo di contaminazione di tutto ciò che è racchiuso tra due registri abbiamo che il
Tc è quello dell’addizionatore, mentre il Tp è la somma dei tempi di propagazione dell’addizionatore e del
moltiplicatore. Questo significa che siccome questo è un sistema sincrono, se dobbiamo stabilire a che
frequenza può funzionare essa deve essere maggiore del tempo di propagazione Tp.
Se invece facessimo un’implementazione a due stadi parliamo di latenza, intesa come due cicli di clock per
produrre i risultati, e dobbiamo chiederci qual è il tempo di propagazione e il tempo di contaminazione di
ogni stadio perché questo ci serve a dimensionare il ciclo di clock. Il tempo di propagazione non sarà più
uguale al Tp(MUL)+Tp(ADD) ma sarà uguale al maggiore dei due tempi di propagazione, cioè dobbiamo fare
in modo che un ciclo di clock sia lungo a sufficienza da consentire al più lento degli stadi di elaborare.
L’implementazione a un solo stadio va più veloce, cioè produce più risultati. Questo perché supponiamo che
il Tp(ADD) sia 1 e Tp(MUL) sia 2, allora il Tp complessivo è 3.
Nell’implementazione a due stadi dobbiamo condizionare il ciclo di clock al più lento dei due e quindi ogni
ciclo di clock dovrà essere lungo due unità di tempo e se i cicli di clock sono due allora avremo bisogno di
quattro unità di tempo per avere il risultato.
Questo vuol dire che quando si spezza un’elaborazione in più stadi dobbiamo fare in modo che ogni stadio
faccia una quantità di lavoro che richieda più o meno lo stesso tempo.
Il tempo di contaminazione Tc serve a capire fino a quando possiamo permetterci di campionare i segnali,
cioè quanto resteranno stabili i segnali. Vorremmo che Tc fosse lungo e Tp fosse breve. Il Tc incide soltanto
sulla stabilità dei segnali dopo il campionamento, quindi a questo livello di astrazione non lo consideriamo.
Esempio 3
Qui vediamo una funzione che è una somma di tre prodotti. La somma di prodotti in senso aritmetico è una
delle elaborazioni di base di qualunque sistema di elaborazione dei segnali.
Il data flow graph fa già una scelta perché le due addizioni non hanno un unico ordine di precedenza reciproco
perché vale la proprietà commutativa e associativa. In realtà no perché vediamo che non sono specificati gli
operandi. Questa implementazione è a singolo stadio. Il Tc lo calcoliamo guardando il cammino più breve e
sommando i tempi di propagazione delle macro che incontriamo lungo questo cammino breve. Il Tp invece
lo stimiamo attraverso uno dei cammini più lunghi e sommando i tempi di propagazione delle macro che
incontriamo.
Esempio 4
In questo esempio vediamo due alternative della stessa funzione: una a due stadi e una a tre.
Nell’implementazione a due stadi faccio un taglio orizzontale che comporta prima il calcolo delle
molt