Appunti SDI Timing
description
Transcript of Appunti SDI Timing
Timing (09/12/11)
È necessario avere questi registri di pipe tra uno stadio combinatorio e un altro? La risposta è Nì.
Cioè ne sì ne no. Tutto dipende dal tipo di propagazione del segnale all’interno della mia struttura.
Voglio dire se i blocchi combinatori avessero tutti le stesse caratteristiche in termini di ritardo i
registri di temporizzazione sarebbero assolutamente inutili.
Qui c’è un esempio (slide 4) di una fibra ottica che se voi mandate un impulso all’ingresso della
fibra questo si propaga, dopo si manda un altro impulso , questo si propaga e si mantiene una
distanza tra gli impulsi senza aver bisogno di meccanismi che memorizzino temporaneamente i dati.
Questo è quello che si chiama il WAVE PIPELINING, che è una tecnica che si cerca di utilizzare
nei circuiti di elaborazione digitale, il che vuol dire che è inutile andare a mettere tutte quelle
tonnellate di registri di pipe. Se la propagazione combinatoria fosse a velocità costante facendo si
che tutti i punti vengono raggiunti con lo stesso ritardo noi potremmo fare tranquillamente una
struttura puramente combinatoria senza metterci i flip flop. PROBLEMA: va bene se ho una
struttura in cui tutti i ritardi sono rigidamente uguali tra di loro. RISULTATO: con i circuiti con cui
abbiamo a che fare ciò non succede. Questo qua arriva prima, quello dopo; c’è una dispersione
nell’arrivo dei segnali che in qualche modo mi blocca, perché quello che arriva prima rischia di
passare davanti agli altri perché fa il percorso più veloce, mi taglia la strada, allora l’unica cosa da
fare è metterci dei ‘semafori’. Questi semafori mi permettono di riaccodare il tutto e quindi
ripartire. Da qui nasce il concetto di pipe, il concetto di circuito sequenziale.
Vado ad inserire degli elementi di ritardo che temporizzano l’evoluzione del processing in modo
tale da garantire una tempistica corretta all’esecuzione.
Prima considerazione: l’aggiungere dei flip flop alla nostra struttura inevitabilmente fa andare più
piano il tutto, perché c’è un overhead (aumento) dovuto appunto all’inserzione di questi.
Allora se vi ricordate dall’anno scorso quando calcolavamo la massima frequenza di funzionamento
di un circuito avevamo un ritardo clock to output e consideravamo anche il ritardo combinatorio e il
tempo di setup. Il tempo clock to output e quello di setup sono dei ritardi che dipendono dall’aver
inserito un flip flop. Quindi avrò quello che si chiama ‘sequencing overhead’ che è il prezzo, in
termini di tempo, che pago nel non poter fare un‘elaborazione puramente combinatoria.
Riassunto delle puntate precedenti: questi elementi di sequencing possono essere di diversi tipi.
Abbiamo latch e flip flop. Il latch è un dispositivo che mi fa passare il segnale o in modo trasparente
o in modo opaco (di memoria), abbiamo i flip flop che invece sono sensibili ad un fronte di salita o
di discesa e che quindi si chiamano edge triggered. Quando il latch è trasparente cambia l’ingresso e
l’uscita cambia seguendo l’ingresso, nel caso di flip flop questo può avvenire solo su un fronte.
Vediamo quali sono i modi di realizzare le nostre strutture di memorizzazione.
Il modo più semplice di realizzare un latch è quello di mettere un transistor che funziona da pass
transistor.
Giocando sulle capacità parassite se phi=‘1’ Q=D, altrimenti Q mantiene il valore precedente di D
per un pò. Se passa troppo tempo si rischia di perdere il valore di Q. Il primo vantaggio è che uso
un solo transistore, quindi è piccolo. Un solo transistore vuol dire che il mio clock va ad un solo
transistore che mi fa da latch. Si hanno poi tutta una serie di aspetti negativi: sappiamo che in
questo modo mi passano dei buoni zeri ma non dei buoni uni (a causa della caduta della tensione di
soglia); per qualche motivo l’uscita potrebbe far variare l’ingresso cioè quello che si chiama back-
driving ovvero non è un flusso unidirezionale; è semplicemente un interruttore che chiude due punti
e di lì vince quello più forte. Il tutto è dinamico perché l’uscita dopo un po’ si scarica ecc.. ecc..
Allora qualcuno ha pensato che invece di mettere un solo transistor se ne mettono due (Pmos e
Nmos) cloccati su fasi differenti. Questo genera quello che si chiama il transmission-gate che è un
interruttore migliore perché l’Nmos mi fa passare molto bene gli zeri mentre il Pmos mi fa passare
molto bene gli uni. Li metto in parallelo così che ce ne sia sempre uno che mi passare bene il
segnale. Non ho più la caduta legata alla caduta legata alla tensione di soglia. Va meglio ma ho due
inconvenienti: invece di un transistor me ne bisognano due quindi raddoppia l’area; invece di un
clock ho bisogno di due fasi e quindi raddoppia anche tutta la parte di generazione del clock.
Per il resto è sempre dinamico, non si sa chi sia l’ingresso e chi sia l’uscita => non è unidirezionale.
Andando avanti si comincia a renderlo unidirezionale, ovvero dire all’uscita del transmission gate ci
metto un inverter oppure ce lo metto prima. In questo modo è una struttura invertente, però ho il
vantaggio che è unidirezionale e non ho perdita di segnale in nessun modo.
Poi si può discutere se è meglio mettere l’inverter a monte o a valle. Se sono meno sicuro sul
pilotare il mondo esterno lo metto a valle perché così è lui che pilota.
Se ho problemi con chi pilota me allora è meglio metterlo prima (a monte); quindi dipende dove
voglio avere una maggiore robustezza, se sulla parte di uscita o su quella d’ingresso.
Questo è sempre di tipo dinamico per cui quando va in memoria mantiene l’informazione. Se voglio
farlo diventare di tipo statico devo introdurre un feedback:
In questo modo diventa statico e posso scrivere lungo la mia catena di due inverter in reazione e
quando non scrivo chiudo il loop in modo da mantenere il segnale teoricamente per tutto il tempo
che voglio. Devo fare attenzione in questo caso al back-drive ovvero che il valore dell’uscita non si
ripercuota all’ingresso. Poi ci sono varie configurazioni:
Ci possiamo mettere un buffer d’ingresso per evitare appunto il back-drive.
Ci posso mettere un buffer d’uscita per svincolare il pilotaggio dell’uscita rispetto alla
memorizzazione del dato con tutta una serie di vantaggi e svantaggi:
Esistono quindi varie topologie e siamo arrivati ad avere una decina di transistori per realizzare un
solo latch! Se poi voglio fare un flip flop sappiamo che bisogna farlo con due latch in
configurazione master-slave:
Quindi con due stadi cloccati su fasi differenti. Da notare che una volta phi va sull’Nmos e una
volta va sul Pmos. Alla fine il risultato è che un oggetto di questo genere è composto da una ventina
di transistori. I flip flop visti lavorano su due fasi e ad ogni colpo di clock memorizzano il dato in
ingresso. PROBLEMA: questo va bene per una pipe pura, se io voglio usarli in una control pipe in
cui sono io che voglio decidere se usarli o meno devo aggiungerci il load enable.
Aggiungere l’enable vuol dire avere strutture che oltre al segnale di clock hanno un segnale di
controllo che dice si sul fronte clocca ma solo se ti abilito io.
Il progetto del flip flop in cui ho aggiunto l‘enable può essere fatto in due modi. Il modo classico è
quello di usare un approccio di tipo multiplexer. Ciò vuol dire: se c’è l’enable abilitato, sul fronte
memorizza l’ingresso, se no mantieni il valore vecchio.
Quando vado a definire la struttura con enable oltre a tutto quello già visto devo metterci pure
questo multiplexer che decide se caricare un nuovo dato o mantenere il vecchio.
In questo modo il flip flop viene campionato ad ogni colpo di clock. È il Mux che decide se fargli
campionare il dato vecchio o il nuovo.
Un approccio alternativo da NON USARE, tranne in certi casi, consiste nel fare il clock gating,
ovvero mettere in and l’enable con il clock. Questa è una tecnica assolutamente da
SCONSIGLIARE, perché fatta semplicemente così può portare a circuiti che non funzionano.
Se l’enable che viene da una macchina a stati ha dei glitch (commutazioni spurie e indesiderate) e il
clock è uno si ha un controllo errato in uscita alla and e il flip flop campiona senza volerlo!
Si vedrà in realtà che esiste il modo di generare quest’enable senza glitch e questo porterà nel corso
di low power a realizzare strutture che usano questa tecnica per ridurre il consumo di potenza.
Il concetto è che se un blocco in un certo momento non mi serve che faccia nulla allora lo disabilito
evitando che gli arrivi il clock.
Ovviamente a tutti questi flip flop vogliamo aggiungere anche il reset e sappiamo che esistono due
tipi di reset: quello sincrono e quello asincrono.
La differenza principale è che il reset sincrono entra esclusivamente sull’ingresso del mio flip flop e
quindi richiede che in qualche modo il dato in ingresso sia filtrato attraverso una porta e quindi il
reset sincrono va ad agire solo sulla parte d’ingresso e quindi solo sul setup dei dati; aumenta il
ritardo in ingresso e basta. Il reset asincrono richiede l’inserimento del reset anche nell’anello che
mantiene il dato e questo vuol dire che il reset asincrono richiede l’intervento nell’anello perché
quando il reset diventa attivo deve essere immediatamente operativo.
Il reset sincrono invece l’ho fatto semplicemente con un filtraggio del dato d’ingresso.
Se poi oltre al reset voglio metterci pure il set il risultato è il seguente:
Abbiamo quindi strutture di flip flop con una ventina di transistori. Questo è il motivo per cui una
trentina di anni fa si preferiva usare il latch con il minor numero possibile di transistor. Adesso che i
transistor non costano nulla e hanno aree infime si preferisce usare il flip flop perché lavorare sui
fronti dà tutta una serie di vantaggi. Gli elementi visti devono essere usati con dei regimi di
temporizzazione. Principalmente abbiamo tre regimi con cui lavorare; diciamo due perché il terzo è
molto meno utilizzato e ci interessa solo sapere che esiste.
Metodo Principale: Sincronizzazione a Fronti.
Il concetto è avere flip flop-rete combinatoria-flip flop e i due flip flop sono campionati sullo stesso
fronte:
Ho un flusso di dati che impiega un colpo di clock intero per andare da un flip flop al successivo.
Questa è la struttura tradizionale.
Un’altra struttura con cui si aveva a che fare in passato e si ha ancora a che fare quando si vuole
risparmiare area, è una struttura a latch: latch con fase1- rete combinatoria- latch con fase2- rete
combinatoria- latch con fase1. Quindi si usano due fasi che per poter funzionare sfruttano degli
intervalli di non-overlap tra loro, in cui nessuna delle due fai è attiva. In questo tempo di non-
overlap entrambi i latch sulle due fasi sono in stato di memoria. Praticamente in ogni fase avanzo di
mezzo step. Questa struttura a latch in due fasi è una struttura che è stata usata tantissimo nei
circuiti integrati ed è tutt’ora usata abbastanza perché vedremo che ha una serie di vantaggi.
Dal punto di vista del timing è una struttura estremamente robusta e facilmente scalabile.
Un progetto fatto con questo metodo infatti è possibile riportarlo su qualunque tipo di tecnologia
senza avere problemi di timing.
Per completezza vediamo anche il terzo metodo che useremo MAI che è quello che si chiama
PULSED LATCH. Notiamo che ho un latch su una fase- rete combinatoria- latch sulla stessa fase.
Quindi i latch hanno fase unica. Do un impulso al latch in modo che passi il dato, il dato si propaga
nella logica combinatoria, si presenta sul latch successivo con lo stesso clock al prossimo impulso.
Ho ridotto da due ad una la fase su cui lavorano i latch. Si hanno però un po’ di problemini.
Per esempio nella nostra logica combinatoria ci possono essere percorsi più o meno lenti. Siccome i
due latch sono attivi sulla stessa fase, se c’è un percorso nella rete combinatoria particolarmente
veloce, questo rischia di arrivare già al prossimo latch mentre la fase di quest’ultimo è ancora alta e
venire memorizzato da esso quindi. Quindi non funziona più tutto il meccanismo, cioè ad ogni
colpo di clock vai avanti solo di uno stadio per volta. Non è che questa struttura non funzioni ma è
più difficile da progettare, meno robusta e più sensibile a quello che metto nella logica
combinatoria.
Quindi tipicamente, come nell’FPGA, si usano solo strutture a flip flop. Nei circuiti integrati si può
anche usare la struttura a latch su due fasi non-overlapped per i vantaggi che abbiamo visto.
Nella slide in figura si trovano i vari tempi con cui abbiamo a che fare:
Abbiamo a che fare con tempi di propagazione da un ingresso ad un’uscita con tempi di setup.
I tempi di propagazione li abbiamo visti l’anno scorso utilizzando tempi di propagazione minimi e
massimi. Cosa vuol dire tempo minimo di 2 ns e massimo di 4 ns? Vuol dire che prima di 2 ns
l’uscita sicuramente non cambia. Solo dopo 2 ns l’uscita potrà cambiare (può essere contaminata) e
cambia certamente prima di un tempo massimo (4 ns).
tcd è proprio il tempo di contaminazione del blocco logico, ovvero da quando cambia l’ingresso il
primo istante in cui l’uscita può cambiare è quello che chiamiamo tempo di contaminazione. È
quello che abbiamo chiamato ritardo minimo. Invece il propagation delay (tpd) è il ritardo
massimo di propagazione tra ingresso e uscita. Tcd quindi è il tempo minimo per cambiare l’uscita.
Nella nostra analisi worst case lavoriamo con tempi di contaminazione minimi e tempi di
propagazione massimi.
Nella prossima slide si vede che da quando cambia il clock a quando può cambiare l’uscita è il
tempo di contaminazione clock to output, da quando cambia il clock a quando cambia l’uscita al
valore massimo è il tempo di propagazione clock to output.
Questa slide non è esatta perché alcune di queste frecce non hanno il minimo senso. Il tempo di
setup non è una freccia! Non significa cambia D2 e questo provoca una variazione del clock!
Allora sul tempo di setup e il tempo di hold vanno cancellate tutte le frecce, perché questi tempi
sono dei tempi che io devo garantire, ma non sono dei ritardi del progetto.
Il tempo di setup mi dice semplicemente che se voglio campionare correttamente il dato devo
avere l’ingresso stabile almeno un tempo di setup prima del fronte di clock.
Il tempo di hold non vuol dire fronte di salita allora … !
È necessario che il dato resti stabile anche per un tempo successivo al fronte del clock. Si definisce
allora tempo di hold, il minimo intervallo di tempo che deve trascorrere dal fronte attivo del clock
prima che si verifichi una variazione del dato.
Allora i tempi di setup e di hold NON SONO dei ritardi/tempi di propagazione. Sono delle
grandezze che devo tenere in conto per capire il timing del nostro sistema.
Andiamo ad identificare delle disequazioni che permettono di farci capire qual è la massima
frequenza e quali sono le condizioni operative con le quali può funzionare il nostro circuito.
Meglio usare la tecnica con i flip flop o la tecnica con i due latch?
Caso che ben conosciamo: flip flop – logica combinatoria- fronte, da quando cambia il clock
l’uscita Q1 cambia con un ritardo di propagazione clock to output, a questo punto inizia il ritardo
della logica combinatoria cioè quello che viene chiamato tpd, questo porta a un certo punto a
cambiare definitivamente l’ingresso D2 del secondo flip flop; bisogna che ci sia almeno un tempo
di setup prima del fronte del clock per essere sicuro che quel dato venga campionato correttamente.
Questo ha portato a dire che in un colpo di clock deve passare il tempo di propagazione clock to
output + il tempo di propagazione della logica combinatoria + il tempo di setup.
Questo vuol dire che con questa struttura fissata una frequenza di funzionamento, quindi un periodo
di clock Tc, il massimo tempo a disposizione della logica combinatoria tpd è minore di un colpo di
clock meno il ritardo clock to output meno il tempo di setup.
Il sequencing overhead è la somma del tempo di setup + il ritardo clock to output. È una parte del
tempo che non viene usato per fare calcoli. È una parte che viene utilizzata per prendere il dato,
mandarlo in uscita al flip flop e stare a aspettare il prossimo colpo di clock.
Questo concetto è da applicare a tutti i percorsi combinatori della mia macchina. Definiti tutti i flip
flop e conoscendo le loro caratteristiche, mi fanno capire qual è il massimo ritardo combinatorio che
posso permettermi per garantire che tutto funzioni correttamente.
Esiste un discorso da fare anche sul minimo tempo di propagazione, ovvero abbiamo dei vincoli sul
massimo tempo della logica combinatoria ma abbiamo anche un vincolo sul tempo minimo.
Perché? Perché se è vero che tutti questi flip flop sono tutti in pipelining e lavorano tutti sullo stesso
colpo di clock, sullo stesso colpo di clock io campiono il dato in ingresso e campiono anche il dato
D2. Campiono il dato in ingresso; dopo un tempo minimo (quello di contaminazione clock to
output) incomincia a cambiare D1, che poi potrà cambiare anche dopo ma non importa.
Questo vuol dire che posso incominciare a cambiare della parte combinatoria e quindi dopo un
tempo minimo di passaggio attraverso la parte combinatoria, ovvero il contamination-time ingresso
uscita, D2 potrebbe incominciare a variare. Potrebbe variare più e più volte fino ad un certo punto a
stabilizzarsi. Qual è il problema? Il problema è che se D2 cambia troppo presto il rischio è che
questo flip flop che comunque campiona e continua a campionare il valore vecchio, si trova il suo
valore vecchio che incomincia a cambiare prima del tempo di hold del flip flop stesso.
Quindi io so che per campionare F2 il dato in ingresso, deve essere verificato il setup e questo lo
verifichiamo con questa disequazione; campiono e prendo D2 che deve essere stabile almeno un
setup prima, però quello che deve essere chiaro è che dopo il campionamento per almeno un tempo
di hold D2 non deve cambiare e il problema è che D2 cambia dopo un ritardo di contaminazione
clock to output + il tempo di contaminazione della logica combinatoria. Che cosa deve succedere
affinchè funzioni correttamente? Deve succedere che la somma di questi due tempi minimi deve
comunque essere più grande del tempo di hold. Il che vuol dire che l’uscita comincerà a cambiare
quando ormai ho finito la memorizzazione del dato. Questo vuol dire che il tempo di hold deve
essere minore di questi due tempi combinatori.
Questo è un punto che crea delle turbe non trascurabili ai sistemi di sintesi, perché questo mi porta
comunque ad un vincolo sul minimo tempo di contaminazione della logica combinatoria.
Va tutto bene, il sistema funziona, però attenzione perché il rischio è che se il sistema è troppo
veloce rischia di non funzionare più e l’uscita va già a sporcare la memorizzazione del dato che c’è
in F2. Se si vìola questa disequazione il sistema non funziona a qualunque frequenza. Il
sistema, cioè, non riesce a campionare in modo corretto i dati, qualunque sia la frequenza.
Attenti dunque alla violazione del tempo di hold.
Mentre se ho una violazione del setup posso allungare il clock e avendo più tempo a disposizione
prima o poi il setup viene garantito, sugli hold non c’è verso!
Se facendo la sintesi il sintetizzatore vi dice Alt Hold Violation, fermi tutti, si devono
necessariamente eliminare le violazioni dei tempi di hold.
Nelle slide da 32-35 ci sono degli esempi sui latch impulsati ma non ci interessano. L’unica cosa
fondamentale da notare è che anche qua il ritardo della logica combinatoria deve essere inferiore
alla differenza tra un periodo di clock e il massimo tempo di inserzione. Ho un limite sul massimo
ritardo combinatorio. Anche qua ho un limite sul minimo tempo di propagazione e contaminazione.
Vediamo invece adesso il discorso delle due fasi, molto più usato.
Qui il concetto è: fase 1 attiva, dopo un po’ di tempo varia l’uscita del primo latch. Dopo il tempo di
propagazione di Q1, quindi del primo latch, cambia l’uscita Q1. A questo punto inizia la
propagazione verso la rete combinatoria, quindi quello che chiamiamo tpd1; questo a un certo punto
arriva al secondo latch, il secondo latch a un certo punto è attivo; quando è attivo passo attraverso il
secondo latch, inizia la propagazione nella logica combinatoria e a questo punto mi presento al terzo
latch. Da quando arriva il dato D1 a quando arriva il dato D3 è passato un periodo di clock. In un
periodo di clock ho il ritardo ingresso-uscita del primo latch, ritardo combinatorio 1, ingresso-uscita
del secondo latch, ritardo combinatorio 2. Il tempo di propagazione della logica combinatoria tpd1
+ tpd2 è uguale a un periodo intero meno i due ritardi ingresso-uscita che guardate in questo caso
diventa il sequencing overhead. In questo caso il tempo di propagazione a disposizione dei due
blocchettini, io ho diviso il tempo combinatorio in due parti, è uguale praticamente al periodo meno
il tempo di attraversamento dei due latch. Questo semplicemente per dire che anche in questo caso
ho un tempo di inserzione, il sequencing overhead. Questa volta il sequencing overhead è pari alla
somma, supponendo che siano uguali i latch, sono due volte i tempi di propagazione ingresso-uscita
del latch. Quindi se vogliamo da questo punto di vista le cose vanno un po’ peggio perché devo
passare attraverso 2 oggetti anziché 1. Però se vado nei dettagli vedo che i latch sono più piccoli
quindi i tempi di propagazione sono inferiori a quelli dei flip flop.
La cosa fondamentale è che questa parte di non overlap mi permette di garantire che non ci sarà mai
la situazione che c’era nel caso dei latch impulsati. Non può mai verificarsi un errore per cui il dato
di qua nello stesso colpo di clock buca L2 e si presenta su L3, perché c’è comunque un momento in
cui i latch sono tutti in memoria. Sono tutti nella zona opaca, nessuno è trasparente quindi la
propagazione si blocca prima del latch. Per quanto riguarda il discorso del tempo minimo:
Per quanto riguarda l’hold time qui il discorso è molto più semplice. Guardiamo, phi1 diventa attivo
in questo momento, quindi solo in questo momento può iniziare a passare il dato attraverso L1.
Dopo un tempo minimo che è il tempo di contaminazione clock to output, inizierà a cambiare Q1,
inizierà a cambiare la logica combinatoria e dopo un tempo di contaminazione tcd il dato si
presenterà a D2. Si presenta a D2 ma la fase a D2 è già finita ed è finita un tempo di overlap prima
di questa cosa. Se andiamo a vedere i due percorsi da una parte ho un tempo di hold, da quando
scende phi2, che devo garantire, dall’altra ho il tempo di non overlap + il tempo di contaminazione
clock to output + il tempo della logica combinatoria. Bisogna che questo percorso sia più lungo del
tempo di hold. Il tempo di hold deve essere minore dell’overlap + i due tempi di contaminazione,
ovvero che i tempi di contaminazione debbano essere maggiori dell’hold meno il tempo di
contaminazione clock to output meno il tempo di non-overlap. Rispetto al caso precedente il tempo
di overlap si sottrae quindi mi permette di essere molto più tranquillo. Molto spesso a causa
dell’overlap la parte sulla destra è negativa il che vuol dire qualunque sia il ritardo minimo il
sistema funziona. Il sistema diventa molto più robusto. Non ho più, o quasi, di dover considerare
ritardi minimi. Non ho problemi di violazione dell’hold time. Il non overlap mi aiuta a non avere
problemi legati all’hold time del nostro sistema. Risultato: questo metodo di temporizzazione a due
fasi è un metodo che è molto robusto. Mi permette di agire sugli hold time. Sono in crisi, allora
aumento il tempo di non overlap tra le due fasi. Andando ad agire sulla frequenza e sul tempo di
non overlap sono in grado di far funzionare questa macchina su una qualunque tecnologia.
Basta scegliere un regime di temporizzazione delle due fasi che abbia un non overlap minimo e che
sia in grado di lavorare a una certa frequenza garantendo un tempo di ciclo minimo. Che cosa pago?
Ho il doppio delle linee di clock da portare in giro, le due fasi. A parte il fatto che devo generare i
due segnali di clock, pago il fatto che devo avere una struttura che comunque mi garantisca di avere
una zona di non overlap dappertutto. È così importante che molto spesso si realizzano strutture in
cui la logica combinatoria viene divisa in due metà.
Quello che devo realizzare è fatto con una prima metà in cui metto un latch su una prima fase, la
seconda metà ci metto un latch su una seconda fase e così via. Rispetto alla sintesi con flip flop in
cui tutta la mia parte combinatoria viene messa insieme in modo combinatorio da un flip flop e il
successivo, qui la parte combinatoria è divisa in due metà e ci metto in mezzo un latch su una fase
diversa. Questa è una struttura di temporizzazione assolutamente generale. Se lavoriamo con un
sistema di temporizzazione a due fasi non-overlapped tutti i percorsi combinatori devono essere
sempre e comunque interrotti da una sequenza alternata di latch su fasi diverse. Se si fa questo tipo
di scelta garantiamo che comunque si possa trovare un regime di temporizzazione per cui il mio
circuito funzionerà sicuramente. Applico in maniera alternata la fase 1 e la fase 2 sui vari stadi in
cascata così come sono previsti. Il concetto è che sempre una struttura qualsiasi richiede che ci
siano dei percorsi combinatori, questi percorsi sono percorsi in cui faccio di tutto e di più tra un
evento di temporizzazione e un evento di temporizzazione successivo. Dopodichè possono essere
flip flop tutti su un fronte, possono essere latch su fasi diverse, non importa. La mia macchina avrà
tanti oggetti di questo tipo, tante parti combinatorie, è chiaro che perché la macchina funzioni
correttamente tutti questi percorsi combinatori tra un elemento di memoria a monte e uno a valle
deve essere in grado di terminare il lavoro in modo corretto. Questo vuol dire che io ho una
limitazione sulla massima frequenza legata a quello che è il percorso critico. Il percorso critico
(critical path) è il più lungo. Per questo un passo di progetto è cercare di bilanciare il più possibile i
ritardi combinatori della macchina cercando di rendere minimo il percorso critico. Infatti, chi mi
limita la frequenza di funzionamento è il massimo ritardo combinatorio. Se per esempio ho un
percorso che ci sta 1 ns e uno che ce ne sta 10 ns, si cerca di modificare la struttura in modo che per
esempio ci stiano entrambi 5 ns. Allora ho dimezzato il percorso critico e ho raddoppiato la
frequenza di funzionamento. Si tratta dunque di fare un RETIMING, ovvero cercare di distribuire
gli elementi di memoria in modo da minimizzare i percorsi critici.
Nell’esempio in slide se la porta Nand fa parte di un percorso critico e mi basta bypassare il suo
ritardo per migliorare le cose, posso spostare l’elemento di memoria dalla sua uscita agli ingressi, al
costo in questo caso di duplicare il numero di elementi di memoria. Ovviamente se alla destra della
Nand c’è un altro percorso combinatorio, dopo tale spostamento, tale percorso avrà un ritardo che
risulterà incrementato del ritardo della Nand. Si tratta dunque di bilanciare i percorsi. È una tecnica
che può essere dispendiosa dal punto di vista del numero di flip flop con cui vado a lavorare.
Bisogna stare molto attenti perché se ho loop rischio di fare danni; introducendo latch/flip flop nei
loop cambio la tempistica del loop e le cose non vanno più come dovrebbero.
Posso usare questa tecnica quando ho un flusso di esecuzione senza loop, tipo datapath.
Abbiamo visto la struttura a due fasi, vediamo ora come generare queste due fasi in modo da avere
quei tempi di non overlap.
Metodo banale: un bel set/reset pilotato da un segnale di clock che fa lavorare il set/reset sempre
nelle condizioni o set o reset attivo, nel feedback ci metto in maniera voluta delle reti di ritardo
delta1 e delta2 in modo che ritardino di una certa quantità la generazione delle fasi delle porte. Si
può dimostrare che i tempi di non-overlap sono esattamente uguali a delta1 + delta2. Andando cioè
a inserire nel feedback dei ritardi opportuni io posso generare i tempi di non-overlap che voglio.
Questo è un vantaggio notevole perché vuol dire che il sistema che genera le due fasi è banale.
Prendi il tuo generatore di clock lo mandi dentro a questo circuito e automaticamente in uscita
avremo due fasi non sovrapposte. Per costruzione questo è un circuito in cui le due uscite non
saranno mai entrambe a ‘1’.
Avremo segnali che arrivano da una logica combinatoria e andranno ad essere campionati o sulla
fase phiA o sulla fase phiB. La domanda è: come deve essere il segnale che arriva per essere
campionato correttamente da phiA? Esso deve essere stabile per almeno un tempo di setup prima
che venga campionato e dopo per almeno un tempo di hold. La stessa cosa per phiB.
Quindi diciamo che il segnale è valido A o valido B in dipendenza del fatto che può essere
correttamente campionato da A o B rispettivamente.
Un segnale si dice stabile A o stabile B se esso non varia per tutto il tempo in cui la
corrispondente fase è attiva ed è già valido sul fronte precedente dell’altra fase.
Tva = TsetupA + TholdA
Tsa = TsetupB + Tnonoverlap + T1phiA + TholdA
Se noi ipotizziamo di lavorare con queste quattro possibili configurazioni, cioè i segnali possono
essere o validi o stabili su una delle due fasi, possiamo costruirci un regime di temporizzazione a
due fasi non-overlapped generale, che funziona sempre, scalabile, portabile da una tecnologia
all’altra, robusto. Può essere adattato a qualunque tecnologia semplicemente cambiando la
frequenza di funzionamento della macchina. Su ogni nodo interno io appiccico un’etichetta che mi
dice qual è la sua caratteristica dal punto di vista del timing. Uso quest’etichetta per fare un’analisi
dal punto di vista della correttezza del timing del sistema.
In questo modo è facile capire dal progetto se le cose funzionano o no. Se fatto bene, il progetto
funziona per costruzione, evito tutti i problemi legati al timing nel mio sistema.
Questo sistema a latch a due fasi è usato pesantemente nei circuiti integrati. Non tanto
nell’FPGA, là si usano i flip flop.
Il fatto di lavorare a segnali validi e stabili è un meccanismo molto facile da usare e comprensibile
da tutti gli strumenti CAD senza che si commettano errori e senza dover fare nessuna simulazione
elettrica. Garantisco che i dati arrivino validi e stabili quando devono essere campionati.
Ultima considerazione: Un altro vantaggio della tecnica a due fasi non-overlapped rispetto alla
tecnica a flip flop è legato a questo aspetto. Nella tecnica a flip flop abbiamo detto che possiamo
avere circuiti combinatori con ritardi differenti, e usando il retiming si può arrivare ad avere un
numero davvero troppo eccessivo di flip flop. La tenica a due fasi mi permette di gestire molto
meglio i casi in cui i miei percorsi combinatori sono sbilanciati, senza fare tanti trucchi tipo il
retiming. In particolare la tecnica a due latch mi permette di fare quello che si chiama il TIME
BORROWING ovvero farmi prestare del tempo.
Il concetto è questo: visto che tu hai bisogno di poco tempo per fare l’elaborazione, mi presti un po’
del tempo che hai perché io ho tante cose da fare.
Vediamo l’esempio: lasciamo perdere per adesso il non-overlap, qui non c’interessa. Partiamo, sulla
fase 1 io campiono sul latch1, inizia il percorso combinatorio, magari la prima fase finisce, devo
finire prima della prossima fase, questo vuol dire che in teoria avrei dovuto finire il tutto pima della
seconda metà del ciclo. Io continuo la mia elaborazione anche se è già attiva la fase phi2, sono in
fase trasparente, chiedo un prestito al semiciclo successivo, ad un certo punto io finirò, l’importante
è che phi2 mi campioni correttamente il dato. Inizia la propagazione di phi2 e ragionevolmente io
posso andare ancora avanti anche quando phi2 è tornato a zero. Cioè, posso in qualche modo
continuare a lavorare sulla fase successiva quindi ho più tempo a disposizione di quello che è
garantito da un mezzo ciclo. È chiaro che a un certo punto dovrò recuperare e di molto anche. Tutti
i prestiti prima o poi dovranno in qualche modo essere restituiti. Vediamo nell’esempio di un loop:
Parto, la prima parte combinatoria impiega più di metà ciclo, vuol dire che io finisco già durante la
fase phi2, dopodichè partirà la seconda parte combinatoria. È chiaro che se vado poi a richiudermi
sullo stesso latch di prima, è chiaro che il prestito deve essersi concluso in un ciclo intero. Il
vantaggio è che io ho un vicolo, a questo punto, sulla somma dei due tempi. È importante che la
somma dei due tempi combinatori stia dentro un ciclo. Ho meno vincoli sul fatto che ci sia un
bilanciamento perfetto tra le varie sottofasi. Posso avere questo prestito tra le fasi giocando sul fatto
che i nostri elementi di memoria sono dei latch, quindi il dato passa comunque se in fase
trasparente; non ho il vincolo del flip flop al quale il dato deve essere valido sul fronte. È aperto il
latch, quando arrivi arrivi ma è chiaro che se arrivi dopo hai meno tempo per quello che devi fare
dopo, però comunque in ogni caso si può giocare su questo prestito. L’overlap c’è lo stesso. Ma Lui
può lavorare anche quando c’è l’overlap e non ha ancora finito, va avanti quando si attiva la
seconda parte. Il concetto del BORROWING è un concetto del tipo guarda, dato che nella prima
fase ho una zona morta l’importante è che quando attivi la parte successiva anche se non ha ancora
finito chi se ne frega. Si ha più tempo prima di passare attraverso questo latch. È ovvio che hai
meno tempo dopo. Non è che questo mi permette di andare a frequenze infinite grazie ai prestiti
però il concetto è che prima o poi quando si chiude da qualche parte devo recuperarlo. È solo un
modo che mi permette di bilanciare meglio i ritardi e non dover garantire di dire no guarda…
Siccome essendo una struttura pipelinata i dati variano nel tempo io devo comunque mettermi in un
punto di partenza. Supponiamo che questo sia l’inizio della mia pipe. Questo vuol dire che il dato
che arriva in ingresso è stabile e quindi quando la fase è attiva inizia la sua propagazione. Il risultato
di questa tecnica è che se io ho un sistema con dei loop ovviamente non vado più in fretta. Cioè se
uno impiega 2 ns e l’altro 10 ns io devo aspettarne almeno 12 ns in un ciclo. Io posso non dover
bilanciare questi due percorsi grazie al fatto che ho il meccanismo del prestito, tu hai poco da fare
ok allora io impiego più tempo tanto poi quando ti arriva il dato tu fai in fretta. Il meccanismo del
latch trasparente mi permette di avere una maggior flessibilità nella definizione dei ritardi
combinatori delle varie sottofasi, cosa che con un sistema a fronti non posso fare, perché in tal caso
il concetto è guarda se arrivi in tempo campioni se no non campioni. Qui il concetto è guarda arriva
quando ti pare l’importante è che io abbia tempo a sufficienza per chiudere la mia fase.
L’importante è che faccia in tempo a passare il latch e fare questa parte combinatoria prima del
prossimo giro. La slide precedente mi dice qual è il massimo prestito che posso avere. Qui è
ricomparso il non-overlap, la mia struttura è una struttura che teoricamente ha due semi periodi,
dove il tempo di ciclo complessivo è dato da 2 volte Tc/2. Qual è il massimo tempo che posso avere
in prestito? È mezzo ciclo, non di più, meno il tempo di non overlap meno il tempo di setup del
secondo latch. Questo è il massimo che io posso prendere in prestito. Vuol dire che io ho una
maggiore flessibilità nel fare il mio progetto dal punto di vista del timing. Io posso andare ad usare
le tecniche di Retiming se voglio, ma non ho il vincolo sulla singola fase. Il vincolo ce l’ho sulla
coppia di due fasi in cascata. La tecnica a latch a doppia fase è quindi più facilmente
ottimizzabile rispetto a quella a flip flop. Il mio progetto quindi al momento di essere sintetizzato
finirà dunque in uno strumento CAD e avrò dei vincoli di timing su ogni percorso combinatorio che
unisca due elementi di memoria. Abbiamo visto come il non-overlap porta il nostro progetto ad
essere più flessibile, però in questo momento abbiamo ipotizzato una cosa che non è realistica.
Ovvero che il segnale di temporizzazione sia stabile nel tempo.
L’ho generato con un oscillatore che dà un segnale ad una certa frequenza e non cambia per nessun
motivo. In realtà nella struttura con cui io vado a lavorare questi clock non sono così precisi.
Ovvero non arrivano in tutti i punti della mia architettura esattamente in modo così preciso secondo
quella che è la frequenza di funzionamento che mi serve. Perché? Per tutta una serie di motivi, di
incertezze e per quello che si chiama clock delivery. Quest’incertezza del clock ha un effetto
estremamente negativo sul timing, praticamente tutta l’incertezza che ho sul clock si ripercuote in
un peggioramento drastico delle caratteristiche di timing della mia architettura. L’incertezza sul
clock peggiora le cose, sia sul discorso dell’hold time, sia sul discorso del massimo tempo di
propagazione, sia su quanto posso prendere in prestito nel caso dei sistemi su due fasi.
Partiamo dal primo punto. Perché io ho un’incertezza sul clock? Ho un’incertezza sul clock che è
tipicamente legata a una serie di fattori più o meno indipendenti. Questi fattori sono visibili in parte
in slide. Il clock è generato da un oscillatore che ha una sua precisione e la precisione
dell’oscillatore non è infinita. Questo vuol dire che se io vado a misurare due periodi del clock
questi sono più o meno uguali ma non identici. Non è garantito a monte che tutti i periodi di clock
abbiano esattamente la stessa durata, a causa del fatto che l’oscillatore non è ideale. Magari una
frequenza dipende in qualche modo anche dalla tensione di alimentazione, dalla temperatura, per
cui ho uno scostamento da quella che dovrebbe essere la forma d’onda ideale. Questo segnale che
viene generato e mandato all’interno della mia architettura dovrà passare attraverso dei buffer, delle
strutture che danno la potenza necessaria, la corrente necessaria per essere distribuito. Questi sono
dispositivi reali e quindi sono caratterizzati dall’avere delle tolleranze. Vuol dire che se questo
segnale lo mando a due dispositivi per andare a due flip flop diversi, questi dispositivi non sono
identici quindi magari qualcuno è più veloce dell’altro, vuol dire che il segnale che passa attraverso
questo dispositivo impiega meno tempo a propagarsi rispetto a questo. Il clock che era uguale per
tutti, passando attraverso differenti driver può arrivare in momenti differenti. Non solo, questi
segnali viaggiano su delle interconnessioni, su delle linee; queste linee sono più o meno lunghe, più
o meno caricate. Risultato: far viaggiare un fronte su una linea di 1 mm e una linea di 5mm, 1 cm,
10 cm vuol dire impiegare più o meno tempo. Se quella linea è più o meno caricata ha una capacità
maggiore o minore. Capacità maggiore o minore vuol dire impiegare più tempo prima di convincere
le capacità a cambiare stato. Risultato: tutte queste cose, portano a dire che è bello dire che tutti i
flip flop sono raggiunti dal clock nello stesso momento ma in realtà non è così. È una bufala!
Tutto questo ce lo giochiamo identificando dei parametri che ci servono per fare il progetto.
Definiamo due parametri legati all’incertezza sul clock. Il primo è quello che chiamiamo clock
skew. Il clock skew è praticamente il discorso visto in cui il clock arriva in due punti diversi con
tempi diversi. A causa dello skew due flip flop non sono raggiunti dal fronte di clock nello stesso
momento ma c’è una differenza di fase nell’arrivo del segnale di clock tra tali flip flop. È quella che
si chiama una variazione spaziale. Ci possiamo giocare questo come vogliamo. Si parla di skew
minimo e di skew massimo. Oppure parliamo di un valore deterministico più un’incertezza rispetto
a un valore nominale. Il concetto fondamentale è che questi flip flop sono raggiunti dal clock con
un’incertezza che ha valore minimo e valore massimo. Secondo punto: se io vado a prendere lo
stesso flip flop e vado a vedere più impulsi che raggiungano lo stesso flip flop temporalmente vedo
che questi impulsi non arrivano a multipli del periodo ma hanno anche lì un’incertezza.
Quest’incertezza temporale si chiama jitter.
Allora ho il clock skew che è l’incertezza nell’arrivo, tra due oggetti separati, dello stesso fronte e
ho il clock jitter che è l’incertezza nell’arrivo di più fronti consecutivi nello stesso punto. Sono due
fenomeni separati. Sono legati a cause tipicamente diverse. Risultato: il clock non arriva nello
stesso momento e in più nello stesso punto non è sempre uguale. Se lavoriamo in un sistema a
impulsi in realtà si scosta a seconda di ciò visto. Vediamo una rappresentazione dei concetti appena
esposti:
Lo skew è la distanza che c’è tra i segnali di clock che raggiungono due qualunque flip flop diversi.
Quindi lo skew è tra un fronte che arriva a un flip flop e quello che arriva ad un altro flip flop. Il
jitter si riferisce invece alla differenza tra il tempo atteso di arrivo e il tempo in cui fisicamente
arriva il fronte nello stesso flip flop. Il jitter è quindi un’incertezza che riguarda me, non riguarda gli
altri. È una variazione sul tempo che arriva a me. Lo skew è un qualcosa legato all’incertezza su tra
arriva il clock a me e quando arriva ai miei vicini. Il jitter è legato in qualche modo a un’incertezza
che non nasce solo dall’oscillatore ma da tutta la catena per cui magari il jitter all’inizio è più
contenuto rispetto ai circuiti più a valle. Cosa posso farci allora?
Quello che vado a fare è che vado a prendere il massimo di tutti gli skew possibili e il massimo di
tutti i jitter possibili. Questo è quello che faccio per poter lavorare. Lavorerò considerando questi
due termini. La domanda che ci poniamo è: Se io ho uno skew e uno jitter come interveniamo
nell’analisi della massima frequenza di funzionamento? Andiamo a considerare le disequazioni che
abbiamo visto prima. Prima considerazione: limitiamoci allo skew.
Lo skew vuol dire che flip flop differenti nella mia catena vengono raggiunti dal clock in momenti
differenti. Si identificano tipicamente due diverse situazioni. La prima è questa: Guardiamo la
seguente struttura di pipe.
Il clock arriva da sinistra e viene propagato verso destra, nella stessa direzione dei dati. Questa
situazione (percorso in alto) è una situazione che si dice di skew positivo. Il clock skew viaggia
nello stesso senso dei dati. Il caso di sotto è detto skew negativo nel senso che il clock viaggia in
direzione opposta a quella di avanzamento dei dati. Il clock del primo registro è in ritardo rispetto al
clock del secondo registro che è in ritardo rispetto al clock del terzo.
Nel caso dello skew positivo il clock dei registri più a valle arriva ritardato, spostato verso destra in
figura. Se le cose fossero così saremo estremamente contenti. Perché? Perché la propagazione ha a
disposizione quanto tempo? Non un periodo solo ma un periodo più lo skew. In questo caso si
dice che lo skew è positivo perché questo mi da più tempo per la propagazione del segnale e per
finire le mie cose. D’altra parte lo skew negativo invece è brutto, perché lo skew negativo mi dice
che io parto già in ritardo rispetto al fronte del ricevitore. Vuol dire che io parto ma non ho a
disposizione un clock intero. Il ricevitore è in anticipo rispetto a me e quindi al periodo di clock
devo sottrarre questo skew. Lo skew negativo rema contro. Se io uso uno skew negativo rischio di
non starci con la frequenza, devo andare a lavorare con una frequenza minore con un periodo di
clock maggiore per recuperare quello skew. Detto questo vediamo cosa si può dire sul tempo
minimo di ciclo. Se io ho un milione di flip flop sparsi qua e là è chiaro che ci saranno delle zone in
cui ho skew positivo e zone in cui ho skew negativo.
Siccome lo skew negativo mi limita la propagazione, io posso dire, nel caso generale, che se delta è
il modulo dello skew, non sapendo se è negativo o positivo, nel calcolo del percorso del minimo
tempo di ciclo al periodo devo per forza togliere questo skew massimo. Lo skew massimo è un
termine che mi peggiora il comportamento del sistema dal punto di vista del timing. Tutte le
formule che abbiamo visto in cui il tempo di propagazione è minore di.. adesso a quelle formule
devo sottrarre comunque lo skew. Quelle formule sono state calcolate ipotizzando che tutti i flip
flop fossero raggiunti tutti nello stesso momento. Risultato: lo skew gioca pesantemente un ruolo
negativo nella riduzione del tempo a disposizione della logica combinatoria e in particolare è lo
skew negativo che gioca pesantemente. Ma non è finita qui. Andiamo a vedere il discorso dell’hold
time. Il discorso dell’hold time è dire ok, quando io campiono questo signore devo garantire che il
dato cambi almeno un tempo di hold dopo il fronte per garantire che non venga campionato
erroneamente. Questa volta lo skew, se positivo, gioca di nuovo a sfavore, perché questo vuol dire
che io prendo il dato, lo carico, passo attraverso la rete combinatoria ma deve essere stabile per
almeno un tempo di hold dopo il fronte. Se il fronte è ritardato in avanti, questo vuol dire che devo
prendere anche lo skew come tempo aggiuntivo di stabilizzazione del dato. La seconda
disequazione mi dice che il ritardo clock to output + il ritardo del minimo tempo di contaminazione
della logica, la somma dei due deve essere maggiore del tempo di hold + lo skew. Quindi il
concetto finale è che lo skew gioca due volte contro. Gioca contro nel ridurre il tempo a
disposizione della logica combinatoria e gioca contro nell’aumentare il tempo di contaminazione
della logica per evitare violazioni sull’hold. Lo skew gioca contro due volte, sui tempi massimi e sui
tempi minimi. Cosa vuol dire jitter? Vuol dire che anche nel momento in cui io vado a considerare
quello che succede a me come flip flop io devo considerare il fatto che rispetto al clock, quando
dovrebbe arrivare, prima o dopo io posso avere un’incertezza pari a questo jitter. Se io devo
mandare il segnale qui, ma io ho un jitter positivo per cui in realtà lo lancio dopo, il signore
dall’altra parte dovendo ricevere il dato qui, però lui ha un jitter negativo per come è fatto per cui lo
riceve prima.
Vuol dire che il mio jitter e anche quello del ricevitore sono da sommare allo skew. Ci sono un po’
di slide con un po’ di esempi 65-70, ma quello che mi interessa è il risultato. Il risultato è questo:
nel calcolo del ritardo massimo, quindi clock to output, ritardo combinatorio + setup, io non ho un
ciclo a disposizione ma al ciclo devo togliere lo skew e 2 volte il jitter.
Lo skew + 2 volte il jitter rappresenta il tempo che ho nella mia logica combinatoria per portare a
casa il risultato. Skew -> spaziale, jitter -> temporale. Nel progetto worst case devo semplicemente
togliere dal tempo a disposizione lo skew e 2 volte il jitter. Questo vuole anche dire che dal punto di
vista dell’hold time, non solo ho a disposizione uno skew in più in cui devo mantenere il dato fisso,
ma a questo punto il jitter gioca al contrario e mi allunga il tempo che devo utilizzare mantenendo il
dato costante per evitare una violazione dell’hold time. Tutti i discorsi fatti sui tempi devono essere
modificati in peggio aggiungendo il tempo di skew + 2 volte il tempo di jitter. Lo skew + 2 volte il
jitter è un tempo da sommare al tempo di contaminazione minimo, è una quantità da sottrarre al
tempo di propagazione massimo. Ancora una considerazione che è questa: per quanto riguarda
andare in fretta, preoccupiamoci dell’hold time. Se io mandassi il clock nello stesso verso dei dati in
realtà ho più tempo a disposizione. Dal punto di vista del tempo massimo di propagazione il
positive skew è vantaggioso per me perché ho più tempo a disposizione.
Se io ho dei datapath che non hanno dei feedback, delle reazioni, posso immaginare di adottare
delle tecniche di clock delivering in modo tale che il clock segua il flusso dei dati. In questo caso
posso avere dei vantaggi. Il problema è che la maggior parte dei datapath con cui abbiamo a che
fare fanno tutti delle elaborazioni che hanno dei risultati parziali che vengono riportati in reazione.
Esempio:
Si vede in figura che la reazione è un percorso che va in senso opposto rispetto al flusso dei dati, è
quindi affetto da skew negativo. Risultato: per cercare di migliorare le cose il più possibile l’unica
cosa che posso fare è andare è cercare di ridurre il più possibile lo skew e il jitter, perché possono
essere abbastanza pesanti. Lo skew comunque rappresenta un limite invalicabile per la frequenza di
funzionamento della mia macchina. Abbiamo visto che in tutte queste equazioni il tempo minimo è
pari allo skew più qualche cosa. Ciò vuol dire che se ho uno skew di 2 ns non posso fare il tempo di
ciclo inferiore a 2 ns. Nel caso del flip flop, che è quello che ci interessa di più, il tempo di
propagazione è il tempo di clock meno il tempo di inserzione, che è il ritardo di propagazione clock
to output + il setup + lo skew + 2 volte il jitter.
Questo vuol dire che lo skew e il jitter mi riducono la frequenza massima di funzionamento della
macchina, o detto in altri termini mi aumenta il periodo minimo di ciclo della macchina.
Quello che c’è scritto sotto, in slide a pagina precedente, è analogo. Il tempo di contaminazione è
incrementato dallo skew + eventualmente 2 volte il jitter. Questa è la realtà, ovvero l’incertezza sul
clock rischia di penalizzare pesantemente la massima frequenza di funzionamento della mia
macchina. Ci sono altri esempi sui latch, ma non ci importano più di tanto.
Risultato della chiaccherata: io ho tre opzioni dal punto di vista del timing: a fronti, due fasi non
overlapped, latch impulsati. Lasciamo perdere i latch impulsati. Con le due fasi è molto più facile
ottimizzare il mio sistema, posso fare il borrowing tra le varie zone, posso garantire che il mio
progetto può essere trasferito su un’altra tecnologia. Cambiando semplicemente l’intervallo di non
overlap il mio progetto funziona sicuro. Posso fare tutta una serie di scelte per cui vado a definire
degli istanti dal punto di vista del timing per cui posso fare tutta una serie di controlli di
temporizzazione senza fare simulazioni. L’inconveniente è che viaggiano due fasi, raddoppio le
linne di clock, raddoppio il carico su queste linee. Ovviamente Se ho già uno skew su un segnale,
adesso che ne ho due che viaggiano i problemi di skew sono ancora più grandi.
La soluzione a flip flop è molto più semplice da fare, non ho il meccanismo del prestito, devo starci
in un colpo di clock, e o ci sto o non ci sto, questo mi porta dei vincoli sulla massima frequenza di
funzionamento, ho comunque un vincolo sul tempo di contaminazione, minimo ritardo per evitare
una violazione dell’hold time, quello che è fondamentale è che se io ho una violazione sull’hold
time in un sistema a flip flop quel sistema non funziona a qualunque frequenza operativa. Mentre il
sistema a latch a doppia fase abbassando opportunamente la frequenza il sistema mi funziona in un
sistema a fronti se c’è una violazione di hold il sistema non funziona mai.
Immaginiamo di dovere realizzare un nuovo processore. Vediamo come limitare i problemi dello
skew. Solo per portare il segnale da un punto all’altro del circuito potrebbero tranquillamente
passare un paio di ns. Ciò significa che due flip flop lontani possono essere raggiunti dal clock in
istanti diversi con un ritardo tra i due dell’ordine del ns.
Devo quindi in fase di progetto pagare un sacco di ingegneri per prevedere reti di distribuzione del
clock che si occupino di inviarmi il clock alle diverse parti di circuito nella maniera più uniforme
possibile, riducendo al minimo lo skew. Ci sono quindi ingegneri che passano il loro tempo a
progettare alberi di distribuzione del clock, visibile in slide, per ottenere tale obiettivo.
Tecniche possibili: distribuzione ad H, consiste nel fornire il clock nel punto centrale, poi faccio
una bella H prevedendo dei buffer che rigenerino in qualche modo il clock, poi nei punti opportuni
faccio altre H e così via. La distribuzione del clock avviene secondo uno schema ad albero. Vado a
fare una scelta di carichi e di distanze tali per cui cerco di rendere lo skew nullo. Questo è vero in
teoria ma poi nella realtà vado a scoprire che il mondo che ci sta sotto non è così regolare, per cui la
distribuzione è più o meno quella visibile in slide ma ciò è più o meno realistico. Quindi riuscirò a
ridurre lo skew del clock ma non riuscirò assolutamente a portarlo a zero.
Un metodo sicuramente migliore rispetto alla distribuzione ad albero vista, è quello che si chiama
distribuzione a griglia, dove il concetto è che l’albero di distribuzione del clock è un’immensa
griglia che viene alimentata dai quattro lati, con un controllo sul clock che arriva dai quattro lati
effettivamente con skew nullo, e questa distribuzione a griglia garantisce che tutti i punti siano
raggiunti con uno skew estremamente ridotto. Tutti i processori odierni, si basano più o meno sul
principio di questa distribuzione a griglia, proprio perché mi permette di gestire quelle situazioni in
cui la struttura che ci sta sotto non è caricata uniformemente, garantendo un certo bilanciamento
interno.
I primi processori, con una decina di milioni di porte, presentavano un carico totale per il clock di
3,75 nF (tenere presente che un singolo gate presenta una capacità di pochi fF). Essi consumavano
50 W, 20 W dei quali erano usati solamente per la generazione e gestione del clock.
In slide si possono vedere delle piazzole per la gestione del clock poste in maniera strategica, questo
era un processore a 2 fasi non-overlapped. Nell’immagine a destra si vede la differente
distribuzione dello skew in un chip di 1.7cm x 1.7cm. Se vediamo i valori massimi dello skew sono
50 ps. Cioè per riuscire a lavorare a 300MHz ho bisogno di uno skew al di sotto dei 50 ps.
Questo lo vediamo per capire la complessità nella distribuzione del clock.
Una tecnica più evoluta, visibile nella slide seguente, è consistita nel dividere la rete di
distribuzione in quattro zone con una distribuzione a griglia. C’è una parte centrale della griglia e
dopodichè distribuisco il tutto. In questo modo si è riusciti a migliorare, infatti l’esempio in slide
mostra un processore che va al doppio della frequenza (600MHz) rispetto al caso visto prima.
Faccio una struttura gerarchica, ho una griglia generale che va a delle sottogriglie che poi
localmente fanno da dispatcher del clock. Nel progetto di sistemi che lavorano sopra i 100MHz la
parte di distribuzione del clock è la parte più difficile e più lunga da risolvere.
Conclusione della chiaccherata: quando io vado a fare il progetto di un sistema digitale complesso,
oltre a fare tutte le considerazioni viste sulla derivazione architetturale, control unit, execution unit,
ecc, il punto finale ma fondamentale a cui devo prestare la massima attenzione è la rete di gestione
del timing, perché esso mi può limitare le prestazioni della macchina in modo estremamente
pesante. A questo si aggiunge un punto ulteriore: se io sto progettando un sistema complesso, tipo
un’interfaccia seriale con regimi di clock multipli, cioè un clock per la ricezione, uno per la
trasmissione, il clock del microprocessore questi sono tutti scorrelati tra loro.
Molto spesso la mia architettura ha delle zone che lavorano a frequenze differenti. La frequenza
operativa è legata, nei sistemi odierni a basso consumo, a quanti calcoli devo fare, per cui se magari
non devo far niente una zona di circuito la forzo a lavorare a frequenza più bassa così da consumare
di meno. Tutte le volte che ho zone che lavorano a frequenze diverse e queste devono parlare devo
garantire che queste dialoghino in modo corretto.
Ci sono diversi protocolli di timing possibili. Essi sono riportati nella slide successiva.
Il più semplice è il primo, quello sincrono detto GATED, con stessa frequenza e sfasamento nullo
tra i due regimi. Quando devo mandarti qualcosa ti do un segnale di controllo. Questo mi dice ok
campiona, no non campionare, funziona bene. Secondo caso possibile: regime MESOCRONO: è un
regime in cui i clock lavorano a stessa frequenza ma esiste uno sfasamento tra i due regimi che è
fisso ma non è noto a priori. Ho regimi di clock isofrequenziali ma con sfasamento incognito. Qui
cominciano a esserci dei problemi che nascono dal setup e hold. Ti mando il dato perché secondo
me il momento è quello giusto, peccato che a causa dello sfasamento il dato ti cambia mentre tu lo
campioni: violazione dei tempi di setup e di hold.
Questo vale nel momento in cui la sorgente di clock è la stessa. Il passo successivo è quello che si
chiama il regime PLESIOCRONO: è il caso per esempio di un’interfaccia seriale da una parte,
un’interfaccia seriale dall’altra, si scambiano i dati, tutti sono d’accordo per parlarsi a una certa
frequenza, peccato che questa frequenza è derivata da un oscillatore che è diverso da quello del
ricevitore. È vero che magari i due oscillatori sono entrambi a 100MHz ma sappiamo che quegli
oscillatori non oscilleranno mai e poi mai alla stessa frequenza. Vuol dire che le frequenze potranno
essere leggermente differenti, vuol dire che questa struttura plesiocrona ipotizza che ci sia una
variazione di frequenza tra i due regimi molto piccola ma che porta ad avere uno shift nello
sfasamento continuo dei clock. Mesocrona vuol dire che lo shift è fisso, plesiocrona vuol dire che lo
shift varia, lentamente ma varia. Quanto lentamente? Questi sono problemi di progetto nella scelta
della precisione dell’oscillatore. Siccome vengono generate due strade diverse con due oscillatori
diversi tipicamente non è proprio zero la differenza, la differenza è molto piccola ma il risultato è di
avere due frequenze leggermente differenti che lo shift della fase varia. L’ultimo caso è un sistema
puramente ASINCRONO: le due frequenze sono generate in modo qualunque, non esiste nessuna
relazione di fase tra i due regimi. Nasce dunque il problema della sincronizzazione della
trasmissione. Nei processori tipicamente all’interno si può avere una frequenza variabile, ma
l’interfaccia verso il bus esterno lavora a frequenza fissa. Quello che cambia è un adattamento della
frequenza interna a seconda del carico. La frequenza esterna è tipicamente molto più bassa di quella
interna.
Timing 2 (13/12/11)
Vediamo adesso come poter far comunicare tra loro e come fanno a scambiarsi i dati oggetti che
lavorano con regimi di clock differenti. Il problema è proprio un problema di sincronizzazione dei
dati perché chi manda il dato ha le sue tempistiche, lavora con i suoi tempi di setup e hold sui dati,
chi riceve il dato ha le sue tempistiche e lavora con i suoi tempi di setup e hold e bisogna che i due
si parlino. Bisogna considerare due grandi classi di sistemi: quelli in cui i regimi di temporizzazione
differenti sono on-chip, sulla stesso chip, e qui è più facile perché c’è un solo clock master da cui si
derivano tutti gli altri. Ci sono invece casi di strutture di sincronizzazione off-chip, abbiamo due
oggetti che si scambiano dei dati, questi due oggetti sono fisicamente diversi, possono essere 2
circuiti integrati ma anche due schede diverse e allora lì i problemi di sincronizzazzione possono
essere di tipo differente. La lezione scorsa abbiamo visto tutti i protocolli di temporizzazione con
cui possiamo avere a che fare. Il concetto è quello di avere 2 regimi, R1 e R2, che si scambiano
delle informazioni, dei dati, e hanno 2 clock , clock1 e clock 2, e questi due regimi di
temporizzazione possono essere più o meno differenti. Allora abbiamo detto che la prima
considerazione è quella di avere uno stadio di formazione tra i due regimi che in realtà hanno lo
stesso clock, quindi stessa frequenza e sfasamento nullo. È ovvio che lo scambio di informazioni
può avvenire con la tecnica del gating, ovvero vuol dire se devo mandare un dato ti abilito, se non
devo mandare un dato disabilito il ricevitore.
La seconda possibilità è Mesocrona, cioè la frequenza del trasmettitore e ricevitore è la stessa ma
non so nulla sullo sfasamento. Vuol dire che i due clock sono due forme d’onda esattamente
identiche però la differenza in fase tra le due forme d’onda è incognita. Perché incognita? Perché
magari trasmettitore e ricevitore non sono vicini e quindi il clock impiega del tempo per andare da
un posto all’altro e non è garantito che ci sia una sincronizzazione dal punto di vista della fase.
I sistemi plesiocroni sono caratterizzati dall’avere due clock che nominalmente sono alla stessa
frequenza ma sono generati in modo differente. La scheda di ricezione e quella di trasmissione
hanno ognuna il suo quarzo. Avrò una certa precisione sulle frequenze dei quarzi ma non avrò mai
la certezza che le due frequenze siano uguali. Se le due frequenze non sono uguali c’è uno shift
continuo nello sfasamento. Ho una variazione di frequenza molto lenta, nominalmente la frequenza
è uguale ma non lo è, quello che cambia è uno sfasamento continuo nel tempo. La differenza
fondamentale tra il mesocrono e il plesiocrono è che nel mesocrono lo sfasamento è incognito ma
fisso, nel plesiocrono lo sfasamento continua a crescere nel tempo. Il modello è un modello,
abbiamo detto, a schede di ricezione e trasmissione separate e quindi un modello a rete. Quindi io
trasmetto col mio clock, il ricevitore riceve col suo clock, nominalmente ho la stessa frequenza ma
in realtà non è proprio identica, dopodichè c’è il caso generale di un sistema di ricezione e
trasmissione completamente asincrono. Non c’è nessuna relazione né di fase né di frequenza tra chi
parla e chi riceve. E allora devo preoccuparmi di fare le sincronizzazioni del caso.
Sincronizzazione MESOCRONA: il clock è lo stesso, lo sfasamento è incognito. Io trasmetto dei
dati in modo sincrono col mio clock, questo vuol dire che i miei dati viaggiano e arrivano al
ricevitore. Il problema è che quando arrivano al ricevitore arrivano in modo assolutamente
scorrelato rispetto al clock del ricevitore. Il rischio è che i dati cambino nel momento in cui il
ricevitore va a campionare i dati. Cosa vuol dire andare a campionare i dati in un qualunque
momento? Vuol dire che non ho nessuna sicurezza del fatto che il campionamento possa avvenire in
modo corretto. Posso avere delle violazioni dei tempi di setup e hold time del ricevitore e se io ho
queste violazioni vuol dire che mi posso ritrovare in uno stato metastabile e quindi campionare un
dato fasullo. Si tratta di capire quali possano essere le soluzioni possibili per garantire che la
ricezione del dato avvenga in modo corretto. Ci sono diverse tecniche più o meno costose dal punto
di vista di complessità energetico e di banda del sistema. La prima tecnica consiste nel dire, siccome
non so come interfacciare i due oggetti, non so come ricavare in qualche modo la fase, il metodo più
semplice consiste nel prendere il clock del trasmettitore e iniettarlo in qualche modo nel segnale che
viaggia, ovvero fare una codifica dell’informazione in cui ci sia in qualche modo il clock e far sì
che quest’informazione viaggi annegata nel segnale e in ricezione io vado a estrarre
quest’informazione. Questo porta a tutta una serie di codifiche del segnale usatissime in alcune
applicazioni. Il metodo può voler dire che se questo è lo spazio a disposizione di un bit, io definisco
per esempio che trasmettere uno zero vuol dire trasmettere un segnale che ha un fronte di salita a
metà dello spazio, trasmettere un uno vuol dire magari trasmettere un segnale che ha un fronte di
discesa a metà dello spazio. Tipo la codifica Manchester o cose del genere. Posso studiare quindi un
circuito in ricezione che vada a sincronizzarsi esattamente su quell’istante e quindi vada ad estrarre
in qualche modo l’informazione di fase per fare il campionamento corretto. Questo metodo tutto
sommato costa poco ed è ragionevole. L’inconveniente qual è? L’inconveniente è molto semplice,
se andiamo a vedere quello che succede per esempio in questa struttura, può benissimo capitare che
ci siano due fronti all’interno dello spazio di un bit, quindi se io ho una limitazione in banda questo
metodo mi porta a dire che posso utilizzare al massimo metà della banda disponibile per mandare
l’informazione. L’unica cosa che pago è che va tutto bene se non devo andare a frequenze elevate,
perché il tipo di codifica fa si che io devo prevedere che in un periodo di un bit possa avere due
fronti. Mentre se invece uso un metodo di trasferimento dell’informazione a livello in un periodo io
ho un solo fronte quindi vuol dire che posso lavorare al doppio della velocità di trasmissione.
Il secondo metodo è usato quando uno va ad utilizzare interfacce di trasmissione seriali, tipo
l’RS232 o robe del genere, è un metodo che è usatissimo e consiste nel dire, è vero che hai in
ricezione un clock, ma mi genero in qualche modo in ricezione un clock a frequenza molto
maggiore e campiono tante volte il segnale in ricezione e così capisco quando il segnale sta
variando e di lì riesco a ricostruire in modo ragionevole, dove sia presente la transizione, la forma
d’onda e la fase del segnale. Uso quindi un clock in ricezione a frequenza alta, multiplo di quello
del trasmettitore ovviamente, e vado a scoprire dove ci sono le transizioni del segnale. Questo è un
metodo che costa pure poco. Ha solo un inconveniente, se io trasmetto a 1 GHz, il ricevitore che
deve lavorare a frequenza molto più alta magari deve lavorare a 10GHz, allora non è l’ideale!
Quindi è un metodo che va bene per trasmissioni a bassa velocità. Infatti nell’RS232 funziona
benissimo perché la velocità di trasmissione è bassa. Sia il primo che il secondo modo hanno tutta
una serie di vantaggi e svantaggi. Quello che permette di raggiungere il massimo delle prestazioni è
sicuramente il terzo. Il terzo prevede il fatto che io in qualche modo vada a spostare la fase del
ricevitore andando a recuperare quello sfasamento, andando ad analizzare i segnali che mi arrivano,
utilizzando una linea di ritardo analogica controllata in qualche modo che mi permetta di ottenere
una sincronizzazione tra trasmettitore e ricevitore shiftando la fase al ricevitore fino a quando
ottengo l’aggancio. Questa è una sincronizzazione che avviene mediante quello che si chiama un
circuito di recupero dello sfasamento (Self Recovery Circuit). Qui c’è praticamente il concetto:
regime numero 1, clock 1, trasmette alla frequenza del clock 1; regime numero 2, macchina a stati
numero 2, stessa frequenza, sfasamento incognito. Il concetto è che i dati che viaggiano passano
attraverso questa linea di ritardo analogica programmabile, dove è il controllo della macchina a stati
che va a spostare, va a aumentare o diminuire il ritardo in modo tale che il dato che arriva alla
macchina a stati numero 2 arrivi in fase con le caratteristiche del ricevitore. La linea di ritardo
controllata da un segnale opportuno va a shiftare i dati che arrivano al ricevitore fin tanto che questi
risultano in qualche modo agganciati correttamente.
Problema: Come fa il ricevitore a sapere come shiftare se lo sfasamento è incognito? Il meccanismo
è semplice e consiste nel dire prima di fare la trasmissione dei dati, il trasmettitore e il ricevitore si
mettono d’accordo e il trasmettitore per esempio trasmette una serie di dati di prova a frequenza
opportuna, quello che si chiama il preambolo, prima della trasmissione. Questi colpi di clock
iniziali noti al ricevitore vengono utilizzati dal ricevitore per andare ad adattare la linea di ritardo.
Col preambolo il ricevitore va a fare il Pweauning??? della linea di ritardo sua fin tanto che ottiene
l’aggancio. Nel momento in cui ottiene l’aggancio può partire la trasmissione del segnale e il
ricevitore da quel momento in poi ha uno sfasamento nullo rispetto al trasmettitore e quindi la
trasmissione avviene correttamente. Perdo il tempo dell’aggancio ovviamente. Ogni quanto viene
effettuato l’aggancio dipende dal tipo di regime, nel senso che se il regime è mesocrono la
frequenza è la stessa e una volta che ho recuperato la fase quello è agganciato. Se il clock invece
non è effettivamente lo stesso l’aggancio dura per un certo periodo che possiamo calcolare, nota la
precisione con cui so la frequenza del trasmettitore e del ricevitore. Il concetto è che se sicuramente
la frequenza è identica una volta che ho agganciato mantengo l’aggancio per tutta la vita e so che
non è vero! Posso avere sistemi più o meno complessi che cercano di recuperare l’aggancio, però
teoricamente con sistemi di tipo mesocrono una volta che ho l’aggancio quello rimane. Preambolo
iniziale, e lì nel preambolo non invio sicuramente dei dati. Se non è certo che i due clock abbiano
esattamente la stessa frequenza, entriamo nel caso del sistema plesiocrono. Il concetto è lo stesso, io
posso recuperare lo sfasamento, ma quello che so per certo è che quello sfasamento cambierà nel
tempo. In questo caso devo andare a fare proprio dei calcoli sull’incertezza che ho sui due clock del
trasmettitore e ricevitore per capire ad ogni trasmissione di quanto mi posso spostare di fase ed è
ovvio che io devo ripetere l’aggancio, riutilizzando di nuovo il preambolo per il segnale di
sincronismo o quello che si manda in questi tipi di protocolli, prima che sia troppo tardi, ovvero
prima che lo sfasamento sia tale per cui io vado a campionare il dato quando non devo.
Si tratta di capire, in base alla precisione dei due clock, quanto dura l’aggancio prima di dover
rieffettuare il prossimo aggancio. La durata dipende dalla precisione dei due clock. Nel caso più a
basso livello, pensiamo all’RS232, l’aggancio dura al massimo 8 bit. Tutte le volte ho uno start bit
che serve per agganciarmi in qualche modo, dopo di che trasmetto un dato a 5 o 8 bit o quanti sono
e alla fine ricomincio da capo. In quel modo ho un’incertezza sul clock ammissibile che è
abbastanza vasta perché vuol dire che l’aggancio deve essere garantito per otto colpi di clock della
trasmissione. È chiaro che va bene per trasmettere pochi bit, pochi byte in generale, e infatti se vado
su tecniche un po’ più moderne le tecniche sono completamente differenti. In questo caso
l’aggancio avviene con una trasmissione di tipo burst, quindi aggancio trasmetto una serie di bit
tutti insieme, poi ricomincio, trasmetto, ecc.. .
L’unica cosa che posso fare è avere un aggancio più o meno continuo, tipo nella trasmissione
sincrona in cui mando un carattere di sincronizzazione, poi mando una serie di caratteri di dato, in
modo tale che la sincronizzazione viene in qualche modo mantenuta con un PLL o quello che è, sto
perdendo un pochino perché non ho più l’informazione, prima che sia troppo tardi rimando un
carattere di sincronismo per riagganciarmi in modo più o meno corretto.
Dopo di che posso avere due frequenze teoricamente differenti, f1 e f2. Se sono differenti entriamo
nel mondo teorico dei sistemi asincroni. I due clock sono teoricamente qualunque e devo scambiare
dei dati tra due oggetti con due clock qualunque. Prima di dire che i due clock sono qualunque,
analizziamo un caso frequente: cioè di sistemi e di interfacciamenti Pseudo-asincroni. Cioè il caso
in cui è vero che i due regimi hanno frequenze differenti ma queste frequenze sono sempre e
comunque derivate da un clock master uguale per tutti. È il caso che ho per esempio dentro un
circuito integrato, ho blocchi che lavorano a diverse frequenze ma tutti e due i clock sono generati
da un clock master unico. È il caso in cui le due frequenze sono derivate da un’unica frequenza
master del sistema, che prevede anche divisioni di frequenza. In questo caso esiste comunque una
qualche relazione di fase tra i clock. Supponiamo di lavorare con due clock, uno a una frequenza e
uno a metà frequenza. Il concetto è che se sono generate dallo stesso clock esiste comunque una
relazione di fase tra le due frequenze. Io posso identificare degli intervalli temporali in cui è
garantito un tempo di setup e un tempo di hold che può essere utilizzato. Il master trasmette alla
frequenza maggiore, trasmette per cui può scambiare i dati in corrispondenza del clock, il ricevitore
riceve con la sua frequenza, è ovvio che lavorando a frequenza più bassa il master non può
trasmettere a ogni colpo di clock ma come minimo, in questo caso, una volta ogni due colpi di
clock, quindi il ricevitore sa che quando riceve questo dato questo è stato trasmesso al colpo di
clock precedente, lo va a campionare poin andrà a campionare questo dato e quindi se i due sono in
qualche modo agganciati, è vero che lavorano a frequenze differenti ma posso comunque definire
dei meccanismi di gating e dire ok, adesso lo ricevi, adesso no, adesso sì, adesso no.
Questa è una tecnica molto utilizzata, lavoro con due clock a frequenza differente però essendo tutti
e due generati dallo stesso clock sorgente posso ricavare delle finestre in cui i dati vengono inviati e
vengono ricevuti teoricamente in modo corretto, garantendo il setup e l’hold time. Dentro un
circuito integrato questa è una soluzione che tutto sommato è abbastanza semplice ed efficiente.
Richiede dei segnali di gating, quindi di abilitazione, per dire ok adesso prendi il dato perché
disponibile, adesso non prenderlo, ecc.. Quindi basta che i due, ricevitore e trasmettitore, sappiano
qual è il fronte in cui deve avvenire lo scambio di informazione. Avrò bisogno di pochi segnali di
controllo che vadano a stabilire qual è la finestra in cui fare il campionamento e in quelle che invece
non deve essere fatto. È una tecnica molto usata nei sistemi di ricezione dell’informazione. Avete
un sistema che trasmette via radio, avete un ricevitore che riceve in maniera seriale, si ha bisogno
quindi di un qualcosa che prende i dati in modo seriale, li parallelizzi e poi mi dia un blocchetto che
lavora a frequenza più bassa. Un serializzatore prende i dati in arrivo dal canale, li serializza, li
manda ad un oggetto che farà quello che deve fare, tipicamente lavora a frequenza molto più alta e
talvolta multipla di quella di byte, word o quello che è a seconda del parallelismo
dell’informazione. Quindi ci sono due frequenze oggettivamente diverse ma legate dal fatto di avere
una relazione di fase. Il concetto è che i dati arrivano in modo seriale ad uno shift register che
lavora alla frequenza abbiamo detto fck1, questo li parallelizza e tira fuori i dati e questi vanno ad
un registro campionato ad una frequenza fck2. Le due frequenze sono differenti, stanno in un
rapporto che è dato dal parallelismo con cui impacchetto i dati e quindi magari questo signore
lavora a 640MHz e questo signore magari lavora a 80MHz. Divide per otto. C’è solo un rischio da
tenere in conto, può capitare che questi due oggetti non siano all’interno dello stesso circuito
integrato, ma siano in due integrati differenti, uno che lavora a frequenze più elevate e l’altro a
frequenze più basse, infatti può capitare che questi due oggetti siano realizzati con tecnologie
differenti. C’è un problemino che può capitare: se chi trasmette trasmette a frequenza più elevata
quando cambiano i dati rispetto a questo clock, i tempi con cui i dati cambiano sono legati alla
tecnologia con cui ho a che fare. Se questa tecnologia è veloce, vuol dire che i dati cambiano
immediatamente dopo il fronte di salita del clock con tempi molto veloci perché la tecnologia lo
permette. Il problema è che quel dato cambia e viene campionato da un sistema a frequenza molto
più bassa, molto più bassa vuol dire che magari per come è fatto ha bisogno di tempi di hold più
grandi. Tipicamente se io lavoro con una stessa tecnologia i tempi di hold sono compatibili con i
tempi di propagazione. Quindi quando si collegano insieme i due oggetti non si hanno tanti
problemi da questo punto di vista. Il problema nasce quando chi manda i dati è così veloce che sul
fronte di salita, subito dopo, già cambia. Quello stesso dato è campionato da un oggetto molto più
lento, che quindi ha bisogno magari di un tempo di hold maggiore. Lì posso avere delle violazioni.
Quindi devo fare molta attenzione quando si interfacciano due oggetti con due diversi regimi di
clock, che presentano però due tecnologie differenti e la tecnologia a frequenza più alta ha magari
dei tempi di propagazione molto inferiori dei tempi di hold della tecnologia a valle. In questo caso
rischio. Allora mi devo preoccupare di adottare tutta una serie di trucchi per evitare violazioni di
questi tempi. Il metodo del gated clock funziona benissimo, il ricevitore sa quando deve campionare
e quando il dato è valido e fa il campionamento in modo corretto senza particolari problemi. Se il
clock è unico, ho due regimi di temporizzazione, due frequenze qualunque ma il clock è unico, io
posso comunque garantire che ci siano queste finestre in cui i fronti del trasmettitore e del ricevitore
sono effettivamente in fase utilizzando delle tecniche ad aggancio di fase che aggancino in qualche
modo le fasi dei due oggetti. Queste sono tecniche basate sul Phased Locked Loop.
Essi usano dei DLL e sono usati soprattutto nei sistemi nell’aggancio tra chip, quindi per circuiti
differenti, oppure le tecniche dentro il circuito integrato basate sui DLL (Delay Locked Loop).
Queste sono tecniche che permettono comunque di garantire l’aggancio tra le due frequenze, in
modo tale che durante la trasmissione del segnale lo sfasamento tra trasmettitore e ricevitore sia
nullo. Adesso vedremo qualcosetta relativa a queste tecniche di aggancio. Qual è la condizione da
cui si parte comunque? La condizione da cui si parte e che vale praticamente sempre è questa: un
sistema dentro un circuito integrato tipicamente lavora a frequenze molto maggiori rispetto a un
sistema su scheda, il che vuol dire che il clock che io mando sulla scheda viaggia ad una frequenza
inferiore rispetto alla frequenza operativa interna di un circuito integrato. Se andiamo a smontare un
computer troviamo il uP che lavora a 2GHz ma il clock del sistema viaggia ad una frequenza molto
più bassa. Esso talvolta viene incrementato in frequenza da moltiplicatori di frequenza che lavorano
con i DLL. Vediamo come funziona un sistema di moltiplicazione di frequenza.
Il concetto è che io ho un clock off-chip, che arriva dall’esterno, che lavora ad una certa frequenza
f, questo clock viene confrontato col clock prodotto all’interno della macchina che lavora ad una
frequenza n volte più grande, questo clock viene diviso da un contatore in modulo, in modo che il
segnale che ritorna indietro sia ad una frequenza esattamente uguale a quella dell’ingresso, dopo di
chè sappiamo che esiste un circuito che è il Phase Detector che mi calcola lo sfasamento tra il clock
che arriva e l’on chip clock diviso per n.
Questo circuito che calcola lo sfasamento utilizza questo sfasamento per andare ad agire
opportunamente filtrato su un VCO (Voltage Comparator Oscillator) per variare la frequenza di
uscita in funzione di questo sfasamento in modo tale che il segnale di uscita ritornando indietro
abbia a regime sfasamento nullo rispetto al segnale d’ingresso. Questa è la tecnica base sui DLL,
ma non entreremo molto nei dettagli. Quindi il concetto è ho un clock di riferimento, ho un clock
locale che viene prodotto dal sistema interno mediante questo divisore per n, il Phase Detector si
accorge quale dei due clock è in anticipo, e a secondache sia in anticipo o l’uno o l’altro dice ok,
aumenta up oppure diminuisci il segnale che poi dovrà andare al VCO per far cambiare il segnale
prodotto. Questa operazione che va a cambiare la tensione di controllo del VCO avviene mediante
due oggetti. Il primo che fa praticamente da integratore dello sfasamento mediante quei due segnali
up e down, che vanno poi a un filtro passa basso che stabilizza in qualche modo le variazioni per
rendere meno nervoso l’anello di reazione in modo tale che ci sia un effetto di smoothing, di
variazione lenta della frequenza prodotta in uscita. Cosa deve fare il Phase Detector? Deve andare a
vedere lo sfasamento tra il segnale di riferimento e il segnale che torna indietro. Devo calcolare in
qualche modo la differenza tra questi due segnali, capire quanto sono sfasati. Il metodo più
semplice per fare un Phase Detector è metterci uno XOR tra i due segnali di riferimento,
l’XOR sarà 1 tutte le volte che i segnali sono differenti. Più sono sfasati più il valore dell’Xor a 1
aumenta e quindi io ho un andamento che teoricamente è lineare, mi da una tensione di uscita, una
volta filtrata, che è proporzionale allo sfasamento. Oltre i 180 gradi lo sfasamento torna a zero
perché non ho nessun tipo di controllo ovviamente sui flip flop. Questo è un metodo che ha un
inconveniente enorme. L’inconveniente enorme è che la curva visibile in slide (a sinistra) è
simmetrica per ovvi motivi perché lo sfasamento mi dice solo quanto è sfasato ma non chi è in
anticipo rispetto all’altro. In realtà può andar bene se il meccanismo cerca di farmi ottenere
l’aggancio ma non so se devo andare più in fretta o più piano, perché non so quale dei due arriva
prima. È molto meglio, anche se leggermente più complicato, il Phase-Frequency-Detector nel
senso che quest’oggetto invece di avere uno XOR ha due flip flop e una porta; semplicemente
utilizza i fronti che arrivano dalla sorgente e dalla reazione, e semplicemente la sorgente fa
un’operazione di reset; la sorgente provoca l’accensione del segnale up, il che vuol dire vai verso
l’alto, il fronte sul segnale B invece accende il segnale down e quando ci sono tutti e due a 1 attivi,
automaticamente il sistema si resetta, quindi quando up e down sono entrambi a 1 resetto entrambi i
sistemi. In questo modo può succedere che se A arriva prima di B il segnale up è attivo per questo
tempo, mentre B è un impulsino piccolo, se B arriva prima di A questa volta è down che è più
grande rispetto all’up. Se quindi si va a fare una differenza tra i due si vede che in un caso prevale
up e quindi vuol dire che abbiamo anche un senso in cui far muovere i circuiti a valle. Nell’altro
caso in cui c’è prima B prevale il segnale down e quindi posso avere l’informazione non solo sullo
sfasamento ma su come muovermi. Si può vedere il diagramma degli stati che fa quello sopra
descritto. Le due condizioni sono o prima up attivo oppure prima down attivo a seconda di quale dei
due segnali è in anticipo. Questo vuol dire che io posso andare a recuperare in qualche modo lo
sfasamento, avendo a disposizione non solo l’ampiezza dello sfasamento ma anche quale dei due in
anticipo e quindi capire se devo aumentare o se devo ridurre la frequenza di funzionamento. Se
andiamo a vedere la caratteristica del Phase frequency detector questa non è più simmetrica come
prima e quindi so che se ho un errore di fase positivo l’errore è positivo, una tensione positiva, se ho
un errore negativo il segnale è negativo e quindi posso sfruttare quest’informazione per andare ad
aggiornare il valore del VCO.
Come si faccia quest’operazione è abbastanza banale. Il segnale di controllo al VCO viene ottenuto
semplicemente attraverso quelle che sono due pompe di carica che vengono attivate con i due
segnali up-down.
Praticamente il segnale up quando è attivo carica il condensatore, il segnale down quando è attivo
scarica il condensatore. Il concetto è banale: se prevale up la tensione media sul condensatore tende
a incrementarsi, se prevale down la tensione media sul condensatore tende a decrementarsi. A
seconda del fatto che sia in anticipo o in ritardo riesco ad avere un segnale che va ad incidere poi sul
VCO, una volta filtrato col filtro di anello. Questo è il principio di funzionamento tradizionale di un
PLL. Quest’operazione di aggancio è un’operazione tutt’altro che veloce.
In slide si vede una simulazione di aggancio di un PLL in cui ho due segnali, il segnale di
riferimento e il segnale ottenuto mediante la divisione in frequenza, in partenza (sotto) non sono
agganciati, c’è una differenza di fase, allora in queste condizioni il mio meccanismo di filtro varia la
tensione di controllo, la tensione di controllo varia in modo non lineare continuo, nel senso che
l’aggancio impiega del tempo. Quanto impiega l’aggancio dipende dall’anello di reazione e si
dovrebbero andare a rivedere tutti i discorsi sui PLL. La cosa importante è che quest’operazione
impiega un po’ di tempo prima di arrivare a regime, quando io ho ottenuto fisicamente l’aggancio.
Se si va a vedere la scala il regime si ottiene dopo qualche us, questo vuol dire che se io devo
lavorare e agganciare due segnali a 1GHz, qualche us vuol dire che io devo aspettare parecchie
migliaia di colpi di clock prima che ci sia l’aggancio vero e proprio. Questo spiega tutta una serie di
cose: è tutt’altro che banale fare per esempio un microprocessore di quelli in cui la frequenza varia
in funzione del carico computazionale. Tutte le volte che io cambio la frequenza devo aspettare che
la frequenza si stabilizzi. Il sistema impiega un bel po’ di tempo prima di ottenere l’aggancio
completo. Questo per quanto riguarda il PLL. Il PLL ha quindi la struttura di frequenza di
riferimento, frequenza operativa che nasce dal VCO, divisione per n, Phase-detector, pompa di
carica, filtro e controllo del VCO. Questo è il metodo principe che viene usato per sincronizzare un
clock esterno in un circuito integrato con un clock interno al circuito stesso ed è il tipico caso di un
sistema di elaborazione dove io arrivo con una frequenza di clock bassa, e internamente il PLL mi
genera una frequenza di clock interna alta, ottenuta mediante questo meccanismo di reazione con la
moltiplicazione per n. Questa diventa la frequenza di riferimento e internamente può capitare che ci
siano due regimi, n regimi, in cui so che la frequenza deve essere la stessa ma voglio recuperare
quello sfasamento che avevo definito prima nei sistemi mesocroni. Capita di avere cioè dei regimi
di temporizzazione dove voglio che sia in qualche modo garantito l’aggancio in fase del clock
attraverso i due regimi. In queste condizioni il PLL non mi serve, il PLL mi serve tutte le volte che
io devo variare la frequenza. Qui la frequenza è la stessa, voglio semplicemente ottenere l’aggancio
in fase. Se voglio ottenere l’aggancio in fase il metodo consiste nell’utilizzare quelli che si
chiamano Delay Locked Loop, o DLL.
I DLL sfruttano quelle linee di ritardo che abbiamo visto all’inizio. Cioè io ho una linea di ritardo
controllata da un segnale che non fa nient’altro che prendere la frequenza di riferimento e mandarla
in uscita semplicemente shiftandola nel tempo. Il controllo di quanto shiftarla nel tempo avviene
mediante un anello del tutto simile a quello visto nel caso del PLL. Praticamente prendo il segnale
della frequenza operativa sul campo, vado a fare la differenza con la frequenza di riferimento,
questo mi da uno sfasamento, lo sfasamento lo utilizzo, con lo stesso circuito di prima, per avere
un’informazione se devo sfasare di più o di meno, e questo segnale se sfasare di più o di meno una
volta filtrato lo utilizzo semplicemente come segnale di controllo ad una linea di ritardo. Vado cioè
a spostare il fronte del segnale del mio regime, una volta che io conosco il segnale di partenza
semplicemente andando a spostare, a incrementare o a diminuire il ritardo introdotto da quella delay
line. Il tutto è controllato dallo stesso circuito, con lo stesso sfasamento ecc.. Questa struttura mi
garantisce che i due oggetti abbiano uno sfasamento nullo durante il funzionamento reale. I DLL
sono usatissimi nei sistemi digitali. Nell’esempio del processore della lezione scorsa ho un sistema
esterno con clock di sistema a bassa frequenza. Là dentro c’è un PLL che genera la frequenza di
funzionamento massima della parte del core, della CPU, e mediante tutta una serie di DLL genero i
segnali di temporizzazione, i segnali di clock che viaggiano, che garantiscono in qualche modo
l’aggancio in fase dei vari punti delle varie regioni. Quindi tutte le volte che io passo da un regime
all’altro io utilizzo questo meccanismo di aggancio in fase a frequenza costante per portare la fase a
zero. L’operazione anche per quanto riguarda i DLL è comunque un’operazione che richiede del
tempo per cui lo sfasamento nel tempo tende a ridursi per cui il segnale che va a variare la tensione
di controllo del delay line cambia nel tempo in modo abbastanza lento e di conseguenza cambia il
ritardo introdotto finchè ottengo l’aggancio e una volta che il sistema è agganciato questo aggancio
viene mantenuto. Tutta le volte che devo cambiare la frequenza di funzionamento della mia
macchina tutti i DLL si devono riagganciare prima di poter essere operativi. Questo spiega perché
ogni volta che cambio la frequenza di funzionamento di un processore tipicamente c’è un intervallo
di tempo in cui il processore non fa niente perché deve aspettare che il nuovo regime sia stabilizzato
prima di cominciare a lavorare altrimenti il rischio è di fare errori.
In slide si vede l’esempio di prima riportato a questo caso. C’è un clock globale, vari DLL che
vanno a lavorare con i vari blocchi, i vari moduli, garantendo l’aggancio ovvero garantendo il fatto
che tutti questi circuiti digitali abbiano lo stesso sfasamento nullo e quindi riduco anche i problemi
di skew, utilizzando questi metodi, mediante l’aggancio attraverso gli anelli di reazione di tutti i
sistemi di temporizzazione con cui ho a che fare. In realtà questi sistemi sono Pseudo-asincroni
perché ho un clock di riferimento.
Vediamo adesso effettivamente i sistemi completamente asincroni, cioè sistemi in cui il ricevitore e
il trasmettitore hanno frequenze assolutamente scorrelate tra di loro. Prima considerazione: se le
frequenze sono scorrelate non posso più avere nessuna relazione di fase tra chi trasmette e chi
riceve. Non posso più parlare di segnali validi, setup, hold time, ecc.. I segnali possono cambiare in
qualunque istante e possono essere campionati in qualunque istante. La domanda che uno si può
porre è: ma questi sistemi sono presenti spesso nei sistemi di elaborazione con cui abbiamo a che
fare o no? Risposta: Sì, ci stiamo muovendo sempre di più verso sistemi di questo genere. Cioè
avere dei processing element (PE) che lavorano a frequenze differenti e che si scambiano delle
informazioni quando serve. Gli si dà anche un nome: si parla di sistemi di tipo GALS (Globalmente
asincrono localmente sincrono).
Vuol dire che io ho delle macchine ognuna che lavora in modo sincrono e fa quello che deve fare
però queste macchine hanno regimi di temporizzazione assolutamente scorrelati per cui l’interfaccia
tra queste macchine è per definizione asincrona. Tutte le volte che si parla di sistemi GALS stiamo
parlando di sistemi con più regimi di clock assolutamente scorrelati tra di loro. Perché ci si juove
verso sistemi di tipo GALS? Principalmente per due motivi. Il primo motivo è il motivo dello skew
e della sincronizzazione. Più il sistema è grande più garantire che ci sia una differenza di fase nulla
tra tutti i punti del sistema diventa un’impresa, soprattutto se aumenta la frequenza di
funzionamento. Questo è il primo motivo.
Il secondo motivo è in realtà meno grave ma è in realtà il motivo per cui ci si sposta verso questi
tipi di sistema. Immaginiamo di lavorare su un circuito integrato ad una certa frequenza con un
milione di flip flop che commutano. Cosa succede quando abbiamo tutti questi flip flop che
commutano nello stesso istante? Si ha un assorbimento di corrente che è impulsivo ad ogni colpo di
clock. Mediamente la corrente è limitata, magari tra un colpo di clock e l’altro è quasi zero, e poi ci
ritroviamo dei picchi di centinaia di Ampere dovuti al fatto che tutti commutano esattamente in quel
momento. Questo vuol dire che si ha una potenza media che è quella che è e una potenza di picco
che può essere enorme. Potenza di picco vuol dire corrente di picco che si deve far passare da
qualche parte. Si rischia di avere circuiti piccolissimi che però necessitano di cavi di alimentazione
grossi così per riuscire a far passare quel centinaio di Ampere in qualche centinaio di ps!
Diventa un limite invalicabile: conduttore, passa corrente, caduta sul conduttore I∙R, risultato:
quando abbiamo l’impulso di sincronizzazione la tensione di alimentazione si siede e non funziona
più un tubo. Sistema di tipo GALS vuol dire che magari i miei cento milioni di flip flop li faccio
commutare con regimi differenti per cui questo vuol dire ridurre l’unico picco di prima e farlo
diventare tanti picchi più piccoli col risultato che diventa molto più facile andare a portare
l’alimentazione. Il sistema diventa anche molto meno rumoroso, alla fine si può dimostrare che si
va a consumare anche molto molto di meno. Cosa dobbiamo fare allora quando abbiamo due regimi
qualunque? Escludiamo il caso semplice in cui il ricevitore lavori a frequenza molto maggiore del
trasmettitore, capita quasi mai. Se il ricevitore lavora a frequenza molto maggiore del trasmettitore
cadiamo in piedi, perché vuol dire che il dato viene trasmesso con tempi molto più lunghi rispetto a
quelli con cui vado a campionare. Posso quindi campionare tante volte e scoprire quando cambia il
dato. È un caso abbastanza raro. In questo caso posso usare un sincronizzatore tradizionale, molto
semplice. Sincronizzatore tipico di un’interfaccia tra un mondo asincrono e un mondo sincrono, è su
questo che vado a muovermi: da una parte ho un segnale che viene generato in modo più o meno
casuale, gli si da una frequenza statistica, si dice in gergo, quella che chiamiamo fin, vuol dire che
mediamente quello commuta fin volte a secondo, ma mediamente. La variazione del segnale può
avvenire come riportato lì in tempi qualunque, però è importante sapere e avere una statistica del
segnale d’ingresso. Dall’altra parte ho il mondo sincrono con una frequenza di clock e quindi quello
che ci devo mettere in mezzo è un oggetto, quello che si chiama sincronizzatore che permetta di
trasformare il segnale asincrono in un segnale che il mondo, cloccato a fclk, sia in grado di
campionare in modo corretto. Quali sono i problemi di quest’interfaccia dal mondo asincrono al
mondo sincrono? Riassunto: com’è fatto un sincronizzatore? Il sincronizzatore funziona da arbitro,
cioè un tizio che deve riconoscere quale di due eventi si verifica per primo. Il sincronizzatore deve
semplicemente capire se è arrivato prima il clock o se è arrivata prima la variazione del segnale
esterno. Se è arrivato prima l’evento di clock non campiono, tengo il vecchio, se è arrivato prima il
segnale campiono il valore. Il problema è che deve decidere in un tempo fisso, ragionevolmente
piccolo. Il problema è che decidere in un tempo piccolo, in modo corretto, sempre, non è possibile.
È impossibile garantire un funzionamento corretto del sincronizzatore tanto più riduco il tempo di
intervento. Vuol dire che per definizione prima o poi esso sbaglierà. Quello che possiamo cercare di
fare è dare degli aiuti in modo che diminuisca la probabilità di errore, che esso sbagli. Qual è il
problema degli errori del sincronizzatore? Vediamo il più semplice sincronizzatore che possiamo
immaginare.
Ingresso D, transmission gate come interruttore che si apre e chiude, segnale che entra dentro
l’anello di reazione in cui vado a memorizzare. Il concetto è che quando l’interruttore è chiuso e il
dato in ingresso cambia, cambia il valore memorizzato dentro l’anello di reazione e funziona
benissimo. Il problema nasce quando io apro l’interruttore e in quel momento il dato stava
cambiando. Vuol dire che apro l’interruttore quando il dato non aveva ancora modificato lo stato del
nostro loop. Il caso peggiore in assoluto, per fortuna non realizzabile, è quando io ho i 2 inverter
esattamente identici e il dato stava cambiando ed era esattamente a metà strada durante la
transizione: Se io apro l’interruttore quando il dato interno è esattamente a metà strada entro in
quello stato metastabile per cui teoricamente, se i due inverter sono esattamente identici, stesse
condizioni operative, stessa tensione, stessa lavorazione di processo ecc ecc, quella condizione di
metastabilità vene mantenuta infinitamente. Vuol dire che quello funziona come una bilancia, allora
se io apro il circuito esattamente quando la bilancia è in equilibrio instabile e i due bracci sono
esattamente identici quello rimane lì fino alla fine dei secoli. Se quando apro l’interruttore i due
bracci non sono esattamente in equilibrio ma sono inclinati da una parte prima o poi finisco in uno
stato stabile. Quello che posso dire è che più sono squilibrato quando tolgo il clock e più in fretta
arrivo allo stato finale. Cosa che si può vedere dal diagramma seguente in cui si vede praticamente
che in funzione del valore d’ingresso quando io ho tolto il clock, questo è il valore che spero non
succeda mai, se sono esattamente in equilibrio quando tolgo il clock teoricamente rimango in
equilibrio per sempre. Tanto più vado lontano dalla situazione di equilibrio quando tolgo il clock
quanto più in fretta arrivo alla condizione stabile. Ci sono due curve perché stiamo parlando di due
inverter, qui è ovvio che un’uscita tende al valore alto e l’altra uscita tende al valore basso.
Queste due curve tendono ad allontanarsi tanto più in fretta quanto maggiore è la distanza dal valore
che si chiama metastabile nel momento in cui io ho aperto l’interruttore. Si può dimostrare, ma non
ci interessa più di tanto, che la variazione dei segnali segue un sistema del primo ordine, a un polo,
per cui vado a calcolare la costante di tempo del sistema e scopro che la tensione raggiunta da una
delle due uscite è la tensione metastabile + un esponenziale che dipende dalla differenza tra il valore
iniziale quando ho aperto l’interruttore e il valore metastabile, quindi che dipende in qualche modo
dalla distanza iniziale. Tanto maggiore è la distanza tanto più in fretta vado in fondo in uno stato
stabile. Tanto che se quel valore è sufficiente da avere una distanza che mi permette di avere un
segnale d’ingresso più piccolo di VIL o più grande di VIH, questo diventa la propagazione normale
del funzionamento tradizionale del mio sistema. Queste sono traiettorie che si chiamano traiettorie
di uscita del sincronizzatore. Qual è il problema? Il problema è questo: siccome io vado a
campionare un segnale che varia ma non so quanto varia esiste una probabilità non nulla che io
vada a campionarlo quando il segnale d’ingresso è vicino al valore metastabile per cui l’uscita è
vero che cambia, ma cambia così lentamente da non darmi un risultato valido nei tempi di
commutazione ritardo clock to output del mio sistema. In questo caso si dice che si va in uno stato
metastabile. Qual è il problema di uno stato metastabile? Il problema di uno stato metastabile è
banale: questo è il segnale d’ingresso, questo è il clock, questa è un’uscita che ha uno stato
metastabile. Problema: se io questa uscita la mando ad altri due flip flop per motivi vari, cloccati su
un clock comune che cloccano questo segnale quando non si è ancora stabilizzato, quello che può
capitare è che uno lo interpreti come un uno, l’altro come uno zero col risultato che qui faccio degli
errori. Sbaglio, uno dei due mi dice guarda il dato è arrivato fai delle cose, l’altro mi dice no guarda
il dato non è ancora arrivato non fare altre cose e il sistema s’impalla. Il problema di avere un dato
in metastabilità è che i circuiti a valle possono tranquillamente incominciare a sbagliare alla grande.
Quello che devo fare è garantire in qualche modo che la probabilità che questi circuiti prendano
decisioni differenti sia ragionevolmente piccola. Esiste tutta una teoria con formule ma non ci
interessa. Qual è la probabilità di essere in queste condizioni? È la probabilità di andare a
campionare il segnale d’ingresso nel momento in cui questo si trova nella zona proibita. Cioè
quando io vado a campionare il segnale in quest’intervallo in cui il segnale sta cambiando ed è nella
fascia proibita tra VIL e VIH, lì ho la probabilità di entrare in stato metastabile e quindi di avere
problemi. Si può andare a misurare questa probabilità. Qual è la probabilità di avere delle false
memorizzazioni? Basta andare a fare il rapporto tra la probabilità di essere in uno stato appunto
indefinito, rispetto al periodo in cui io vado a fare il campionamento. La probabilità di essere in uno
stato indefinito è uguale alla probabilità di essere quindi in questa zona, rispetto ad un periodo del
segnale. Tsignal è quella frequenza statistica, quindi il periodo statistico del segnale d’ingresso, c’è
un 2 di mezzo che è sparito ma non importa; quanto vale questo tempo in cui sono a rischio? Se so
in quanto tempo faccio tutto lo swing, che è il tempo di salita del segnale, questo salto vale VIH-
VIL, questo tempo lo posso fare con una proporzione quindi vale (VIH-VIL)/Vswing moltiplicato
per il tempo di salita e quindi quello mi da poi l’intervallo. Quell’intervallo diviso il tempo statistico
del segnale mi dà la probabilità di beccare il dato mentre questo sta cambiando, c’è un 2 che manca
ma non importa. Preso questo tempo, questa probabilità, diviso l’intervallo di campionamento del
segnale questo mi dà quest’informazione che è praticamente il numero medio di volte in cui mi
trovo in questo stato metastabile, in cui campiono il dato quando non dovrei campionarlo. Questo è
importante perché mi dice praticamente ogni quanto tempo rischio di campionare il dato nella
condizione di metastabilità. Come funzionano i sincronizzatori? I sincronizzatori sono una cosa di
questo genere: dicono ma, visto che questo signore non è così affidabile prima di mandarlo avanti
aspettiamo del tempo, un ritardo T e mandiamolo ad un altro flip flop esattamente identico che
campiona questo segnale dopo un ritardo T. Vuol dire che vado a campionare questo segnale
quando il valore del segnale si è già spostato dalla condizione di metastabilità e sta cercando di
convergere. Ovvero vuol dire che noi invece di andare a campionare in questo momento il segnale,
è come se noi ci spostassimo in avanti e lo campionassimo in un momento in cui quel segnale si sta
stabilizzando. È ovvio che più è grande T più sto arrivando in condizioni corrette e stabili. Siccome
l’evoluzione del segnale varia secondo quell’esponenziale e(-T/tau), vuol dire che se io aspetto un
tempo T quella probabilità viene moltiplicata per quell’esponenziale, ovvero, che è quello che
compare sotto, il numero medio di errori di sincronizzazione che ottengo lasciando passare un
tempo T è uguale al numero medio di errori che avevo campionando all’istante 0 moltiplicato per
e(-T/tau). Più passa il tempo più si riduce la probabilità di campionare un dato nel suo intervallo
proibito. Esempio: supponendo di lavorare a una frequenza di clock di 100MHz, quindi 10 ns di
tempo di clock, un segnale che statisticamente ha un tempo di ciclo di 50 ns, tempo di salita 1 ns,
tau = 310 ps, ecc viene fuori per esempio che se noi campioniamo il segnale così come arriva,
abbiamo praticamente questo MTF (Min Time to Fall), vuol dire il tempo che passa prima di
sbagliare è dell’ordine di 2.5 us; risultato: non funziona mai!
Se voi aspettate un colpo di clock il tempo medio diventa 2.6∙108 = 8.3 anni. Ovviamente parliamo
di tempo medio, non vuol dire che se partiamo adesso sbagliamo tra 8.3 anni, ma vuol dire che
mediamente facciamo un errore ogni 8.3 anni. Questa, detta in termini molto semplici, è la teoria
che sta alla base dei sincronizzatori. I sincronizzatori non sono nient’altro che una successione di
flip flop che vengono campionati sullo stesso clock e che fanno sì che il primo campioni e
statisticamente va sempre in metastabilità, il secondo vede già un segnale che è già più stabile
quindi farà molti meno errori e tipicamente quello dopo ne farà ancora di meno. Metto un numero di
flip flop in cascata sufficienti a garantire un MTF che sia sufficientemente grande da garantirmi che
quello sia un evento assolutamente improbabile.
L’esempio che si fa di solito è che se io sto facendo un sincronizzatore per un segnale che va
all’interno di un PC, con Windows, una volta che io vado a mettere un MTF confrontabile coi tempi
con cui si inchioda Windows vuol dire che il sistema lo prendo e lo butto!
Il numero di elementi da mettere dentro il sincronizzatore dipende dalla probabilità che io voglio
avere di errore, a valle del processo di sincronizzazione. Quindi dipende dal tipo di applicazione, se
quello è un segnale che è particolarmente critico e viene magari da un segnale di controllo di una
centrale nucleare, magari mettiamo qualche flip flop in più, chi se ne frega dello spreco l’importante
che siamo più sicuri. L’unico inconveniente del mettere tanti flip flop in cascata è il tempo di
reazione. È ovvio che se il mio segnale entra e poi va in 2500 fli flop e mi dice guarda che dieci
minuti fa è successo qualcosa, allora la cosa non funziona anche se la sincronizzazione è perfetta.
Bisogna dunque raggiungere un compromesso tra la probabilità che voglio ottenere e tempi di
risposta. A questo punto il problema è, è vero che io posso usare dei sincronizzatori per trasformare
il segnale asincrono in un segnale corretto dal punto di vista sincrono ma devono anche prendere
delle decisioni irrevocabili in un tempo limitato. La domanda che può nascere è questa: se viene
presa una decisione sbagliata? Può voler dire che per esempio il segnale stava cambiando e io sono
andato a campionare beccando il valore vecchio. Il concetto è: il segnale stava cambiando e il
sincronizzatore o si è accorto della variazione o non si è accorto della variazione. Non è un errore,
vuol dire semplicemente essermi accorto in tempo o al ciclo dopo che c’è stata una variazione del
segnale. Per mettere insieme queste cose ho bisogno di avere dei meccanismi di sincronizzazione
tra le due regioni che permettano di far si che il sincronizzatore possa lavorare in modo corretto.
Questi meccanismi sono i cosìddetti handshake, cioè tutti i protocolli di comunicazione tra due
regimi di clock, che permettono ai due regimi di fare lo scambio di dati in modo corretto
indipendentemente dalle frequenze in gioco. Come si gestiscono gli handshake? Vediamo solo
alcuni punti importanti per la sincronizzazione architetturale che ci interessa.
Vuol dire trovare delle regole tra chi trasmette i dati e chi riceve i dati in modo tale che la
trasmissione avvenga in modo corretto. Un protocollo di comunicazione è un insieme di regole che
permette ai due oggetti di scambiarsi delle informazioni in modo sicuro. Di protocolli ce ne sono
tanti. Una prima categoria di protocolli, quelli che si chiamano BUNDLED DATA (o single rail),
vuol dire che ogni segnale fisico è associato ad un segnale logico. Ogni linea porta
un’informazione. Se i due devono scambiarsi le informazioni e vanno ognuno per i fatti propri le
regole del protocollo rappresentano le azioni che i due si scambiano per ottenere il risultato.
Più che partire a vedere questo vediamo prima quello più conosciuto che è l’HANDSHAKE A 4
FRONTI:
È un protocollo in cui ho n dati, n bit che viaggiano, ogni bit una line, un segnale e due segnali di
controllo, un segnale di Request e uno di Acknowledge. Questi due segnali sono quelli che
permettono di giocare col protocollo. Il concetto base è: metto i dati, il fronte di Request è il primo
fronte che mi dice ehi guarda che ho messo i dati, allora l’altro risponde con l’Acknowledge, che
vuol dire ah ho capito che hai messo i dati, ho preso i dati, sono stati accettati. Terzo fronte, ah ho
capito che hai accettato i dati allora aspetta, guarda che li tolgo perché non servono più. Quarto
fronte: ho capito che hai tolto i dati, per me il ciclo è chiuso qua, possiamo ripartire col prossimo.
Si chiama protocollo a 4 fronti perché richiede la propagazione di 4 fronti lungo il canale, due
gestiti dalla sorgente, in questo caso il segnale Req, due gestiti dalla destinazione, in questo caso il
segnale di Acknowledge. Questo permette che i due parlino e sempre e comunque riescano a
mandare un’informazione comprensibile all’altro. Il principio base di questo protocollo è che ogni
attore fa una sola azione e poi aspetta un’azione dell’altro. Valido i dati, poi non faccio niente
finché non mi ritorna qualcosa indietro. Accetto i dati, non faccio niente finchè l’altro non mi da un
segnale. È un continuo balletto tra sorgente e destinazione che fanno un’operazione per volta, ma
questa alternanza forzata garantisce che la sincronizzazione avviene coi tempi necessari. Sono
lentissimo nel mettere i dati, e chi se ne frega, gli altri aspettano. Ho dato i dati subito, io sono
lentissimo nell’accettarli, va bene tanto la sorgente in ogni caso non si schioda finché io non ho
preso i dati. Questo permette di adattare la velocità del trasmettitore e del ricevitore.
Protocollo con dati Bundled (o single rail): ogni segnale fisico è un bit. Il protocollo a 4 fasi è
sicuramente il più sicuro per scambiare informazioni tra due regimi qualunque. Ha solo un
inconveniente: ogni volta che trasmetto un’informazione ho 4 fronti che si propagano. Ogni fronte
si propaga impiegando del tempo, va tutto bene se non voglio andare a frequenze molto elevate,
perché se no il rischio è che il tempo maggiore impiegato dal protocollo è quello che impiegano i
segnali di controllo a dire ehi i dati sono validi, ehi i dati sono stati accettati, ah va bene tolgo i dati,
ah...
Per rendere più veloce il protocollo posso, a scapito di una complicazione della mia architettura,
passare a un protocollo a 2 fronti o a 2 fasi.
Ho sempre un’alternanza trasmettitore, ricevitore, trasmettitore, ricevitore però questa volta le
informazioni che viaggiano sono semplicemente le informazioni: dato valido, dato accettato. È
implicito il fatto di togliere un dato e sostituirlo. Se io faccio in questo modo, metto i dati, li valido,
il ricevitore li riceve, questo fa si che io automaticamente cambio subito i dati e la prossima
validazione è un altro fronte sullo stesso segnale ed è il fronte complementare. Tutti i fronti del
segnale Req rappresentano un dato valido, tutti i fronti sul segnale Ack rappresentano un dato
accettato. Il vantaggio è che raddoppio la velocità se l’elemento critico era la propagazione dei
controlli. Ogni volta che trasmetto un segnale ho solo due transizioni sulle linee di controllo, ho
dimezzato il numero di linee di controllo e il dimezzamento mi porta come conseguenza il fatto di
andare più in fretta. Pago esclusivamente il fatto che diventa più complicata la logica di controllo.
Perché la logica sia di invio dell’informazione che di ricezione non è più legata su un fronte, un
livello, ma è legata su una qualunque variazione. Quindi ho bisogno di un circuito che sia in grado
di riconoscere qualunque transizione. Quindi ad ogni transizione c’è un evento.
Questi protocolli si basano su un principio: il principio base è che la successione di azioni fatta dal
trasmettitore e dal ricevitore deve essere sempre la stessa. Vuol dire che se io metto i dati, il segnale
di validazione deve essere mandato dal trasmettitore dopo un tempo opportuno. La cosa
fondamentale è che anche al ricevitore prima devono arrivare i dati almeno un tempo di setup prima
di quando arriva il segnale di validazione. Devo garantire un certo timing al trasmettitore e al
ricevitore durante il funzionamento normale della mia macchina. Questa sembra una cosa
abbastanza normale vista così, peccato che ci siano tutti i problemi di skew dei segnali dovuti al
fatto che i segnali fanno percorsi differenti, sono più o meno caricati. È dovuto al fatto che magari i
segnali di temporizzazione vengono da un clock, direttamente e indirettamente e ho un jitter. Queste
incertezze devono comunque non violare la successione di azioni previste dal protocollo. Questo
non è detto che sia così banale. Soprattutto quando vado a velocità elevate. Più vado su in
frequenza, più lo skew diventa dell’ordine dei tempi con cui ho a che fare. Il limite verso l’alto di
questi protocolli è di fermarmi ad un certo punto perché non sono più in grado di garantire questa
successione corretta di azioni durante il trasferimento. Per evitare questi problemi o diciamo per
attenuarne un po’ l’effetto, sono nati dei protocolli che non sono più di tipo bundled, ovvero sono
nati dei protocolli in cui si cerca di codificare la validità del segnale direttamente dentro le linee di
segnale. Io cerco di codificare l’informazione che voglio inviare più la validazione dentro le linee
del segnale. Il primo effetto è questo: per mandare un bit non mi basta più una pista, un segnale, ma
ne ho bisogno di almeno due. Questo è il primo esempio in cui codifico ogni bit di dato
direttamente su due fili. Questa è una codifica che ha avuto molta fortuna in una serie di
applicazioni di nanoelettronica, per esempio l’NCL (Non Conventional Logic). Essa è una codifica
di questo tipo: il mio dato, il mio bit, viene codificato su due segnali, in cui mando l’informazione
nella forma vera e falsa, t ed f, secondo questa caratterizzazione: quando non mando niente mando 2
zeri, se voglio mandare uno zero mando zero su vero e 1 su falso, se voglio mandare un 1 mando un
1 su vero e zero su falso. Per il momento assumiamo la codifica 11 vietata. Vuol dire che su quelle
linee di segnale viaggia anche l’informazione di validità. Quando vedo arrivare 00 non ho bisogno
di una validazione, vuol dire guarda che non c’è dato, non è stato inviato il dato. Quando invece
mando dei dati veri e propri, automaticamente l’invio dei dati ha dentro di se il concetto di
validazione. Non ho più bisogno di un segnale di valid, la validazione è annegata, embedded, dentro
il segnale che viaggia. L’unico segnale di controllo a questo punto è l’acknowledge che torna
indietro. Non ho più bisogno di garantire la tempistica tra la validazione e i dati perché la
validazione è intrinseca. Questo è il protocollo: Empty -> siamo pronti, inizia il prossimo ciclo,
metto i dati e sono automaticamente validi, ok ho preso i dati, allora li tolgo, empty, ah va bene ho
capito, siamo pronti e ricominciamo.
Questo è l’equivalente del protocollo a 4 fronti, usando questa tecnica a 4 fasi. Dual rail nel senso
che ho bisogno di due segnali per ogni bit trasmesso. Il vantaggio è che non devo più garantire la
relazione tra i dati inviati e la validazione. Non ho più questo tipo di problema. Lo svantaggio è che
siccome il valid è annegato dentro il segnale, devo fare attenzione a far sì che i segnali quando
viaggiano non abbiano mai dei glitch. Un glitch equivale ahimè ad un cambiamento di stato. Un
glitch diventa pericolosissimo perché se il segnale era a zero, empty, è arrivato un glitch, e quel
glitch viene riconosciuto come se il segnale era valido e viene già tolto. Quindi i problemi di glitch
e di hazard qua sono più critici rispetto ai casi precedenti. Se facciamo viaggiare tanti bit in
parallelo, nell’esempio ho due segnali A e B che viaggiano, il segnale di richiesta diventa locale,
non viaggia più lungo la linea, quando la richiesta è attiva questo segnale d’ingresso provoca la
propagazione del segnale da una parte all’altra. In ricezione so che B è arrivato quando o il valore
vero o il valore falso è a 1. Basta un’operazione di OR per capire che questo bit è arrivato. Itero
quest’operazione su tutti i bit in arrivo, faccio una specie di AND particolare e il concetto è che io
rilevo il segnale di richiesta direttamente dai dati senza la validazione che viaggia. Questo permette
di avere una validazione che è inserita dentro il flusso di dati e quindi il segnale di controllo è nei
miei dati. Quella visibile in figura con il simbolo C non è una porta AND vera e propria, è una porta
particolare che si chiama C-Muller; è una porta con memoria che è utilizzata moltissimo con sistemi
di sincronizzazioni asincroni. Non entriamo nei dettagli ma vediamo il concetto: questa porta ha
memoria della storia precedente. Si può vedere anche la regola con cui si genera l’uscita Z. Essa è
uguale ad a*b, quindi quando entrambi sono a uno l’uscita va subito a 1. Nel caso precedente
quando tutti i bit sono arrivati sicuramente il dato è completo. Però, l’uscita Z non cambia il valore
precedente finchè almeno uno degli ingressi è attivo. Se guardiamo la tabella di verità risulta più
chiaro: quando gli ingressi sono entrambi a zero l’uscita è sempre zero. Gli ingressi possono
cambiare, uno per volta, prima uno poi l’altro, non importa. La mia porta non cambia. Solo quando
tutti sono a 1 l’uscita va a 1, mi da la validazione e rimane valida anche quando gli ingressi
ritornano a zero finchè tutti sono vuoti, quindi questa è una porta che va bene per riconoscere quelle
due configurazioni di valid (tutti i bit sono attivi) ed empty (tutti i bit sono a zero).
Questa può essere realizzata con una specie di doppio inverter con una reazione. Su questa fase è
possibile realizzare controlli ed handshake tra i vari oggetti senza particolari problemi. Prima
abbiamo visto il caso in cui ogni bit era codificato su due linee, per mandare due bit ho bisogno di 4
segnali. Una codifica alternativa è quella che si chiama 1-of-4 Signaling:
In questo caso faccio semplicemente una codifica diversa di due bit su quattro. Vuol dire non stai
mandando niente -> null, tutti 0; mandi 00 -> 1000; mandi 01 -> 0100; e così via secondo quanto
visibile in slide. È una codifica One-hot. Anche in questo caso vale il meccanismo visto prima. Vale
il meccanismo per cui empty equivale a tutti zeri. In questo caso valid equivale ad almeno un bit ad
uno. Il dato è valido inviato quando ho almeno un bit. Il vantaggio rispetto alla codifica precedente
è tipicamente uno solo: ho molte meno transizioni rispetto alla codifica vista prima. Meno
transizioni vuol dire meno potenza in gioco. Sempre 4 linee sono, solo che invece di avere la
codifica in cui ogni singolo bit è codificato su due, quindi ogni volta che ho una transizione in
ingresso in realtà cambia o l’uno o l’altro in opposizione, qui sono messi in qualche modo insieme e
si ha una codifica unica per i bit che io voglio inviare. Numero di fili identico, più fili per segnale,
metà delle transizioni più una serie di altre cose in cui non val la pena meditare in questo corso. Il
circuito di ricezione e trasmissione sono ovviamente leggermente differenti, perché uno deve fare la
decodifica, c’è un decoder abilitato in qualche modo, e in ricezione devo semplicemente fare l’OR
dei vari segnali su cui può essere codificato il segnale. Tutto questo poi va alla solita C-Muller per
scoprire quando sono in uno stato Valid, oppure in uno stato empty.
Concludiamo il discorso sulla C-Muller facendo un esempio per capire come questo concetto di
empty e valid è utilissimo per realizzare gli handshake. Il primo protocollo che abbiamo visto è un
protocollo a 2 fasi, c’è un sender e un receiver, in questo caso i due si scambiano i segnali di
richiesta e di Acknowledge e sappiamo che un protocollo a due fasi fa sì che ci sia un trasferimento
ogni coppia di transizioni sui componenti. Quindi dato valido, dato accettato, dato valido, dato
accettato, ecc.. Le azioni sono sempre alternate tra il sender e il ricevitore.
Vediamo questo tipo di operazione come può essere realizzata dal punto di vista hardware. Dal
punto di vista hardware si può realizzare quest’operazione esattamente con la C-Muller. Il nostro
oggetto realizza la logica di handshake. Allora, il segnale di acknowledge arriva e prima di andare
nella C-Muller viene invertito. La C-Muller è un oggetto che fa commutare l’uscita quando i suoi
ingressi sono tutti a zero, diventano tutti e due a uno, allora lei commuta, e poi quando ritornano
tutti e due a zero. La configurazione di tutti a zero o di tutti a uno è la configurazione in cui ho il
segnale di data ready, il segnale di Acknowledge che ritorna invertito di fase, tale per cui questa
struttura quando c’è il data ready e l’Acknowledge a zero, zero diventa uno, ho due uni quindi
quello commuta e quindi il protocollo va avanti. A quel punto a un certo punto torna il segnale di
acknowledge e quando il data ready è cambiato, cioè c’è un nuovo dato, e l’acknowledge ha
cambiato stato diventano due zeri e quindi l’uscita commuta. Due commutazioni sugli ingressi,
quindi, provocano la commutazione sull’uscita. Questo genera la sequenza corretta del protocollo.
Questa struttura, col meccanismo di passare da tutti zeri, tutti uni, è il modo migliore per
gestire le macchine che lavorano ad handshake. Per fare un esempio, MA QUESTO NON VE
LO CHIEDO, in slide si vede una FIFO autotemporizzata in cui queste C-Muller lavorano proprio
come macchine di handshake tra un oggetto e il successivo. Non entriamo nei dettagli, però si vede
come prima lavora e faccia una scrittura nel primo oggetto, dopodichè questo si propaga ed entra in
funzione il secondo, poi entra il terzo che da un feedback al primo allora il primo è pronto per
lavorare di nuovo allora parte e va avanti e intanto il terzo dato esce e così via. La cosa che ci
interessa è il concetto di un oggetto semplice che permette di sincronizzare degli eventi basandosi
sul riconoscimento di due condizioni: quando tutti gli ingressi sono zero (condizione empty) e
quando tutti gli ingressi sono a 1 (condizione valid o full). Questa è una struttura più complicata
per realizzare per esempio un protocollo a 4 fasi:
Da notare che negli esempi fatti non c’è il clock. Sono circuiti assolutamente asincroni.
L’ultimo passo che ci resta da vedere è dire: Se noi vogliamo realizzare quest’oggetto ma con due
regimi, quindi col clock, come può avvenire la sincronizzazione a livello di controlli e di dati?
Parliamo di quello che si chiama SINCRONIZZATORE GENERALE O UNIVERSALE. Cioè un
oggetto che permetta di sincronizzare degli eventi tra due macchine a frequenze qualunque
garantendo di non avere metastabilità ovvero che la sua probabilità sia così piccola da renderla
trascurabile e garantendo l’evoluzione temporale delle azioni tra chi trasmette e chi riceve dei dati.
Il sincronizzatore generale è una macchina fatta in questo modo: regime numero 1, col suo clock
ck1, regime numero 2 col suo clock ck2. Questi due regimi vogliono scambiarsi delle informazioni
di controllo garantendo il fatto che non ci sia mai metastabilità. Allora abbiamo detto mando i dati,
poi abbiamo magari il segnale di request, o di valid, c’è un fronte. Questo fronte viaggia dalla
sorgente verso la destinazione che deve leggere questo segnale.
Il problema qual è? Che se la destinazione è una macchina sincrona col suo clock, leggere quel
segnale vuol dire che quel segnale entrerà nella macchina e sarà campionato. Rientriamo nei
problemi che abbiamo appena abbandonato perché se io vado a campionare il segnale delayed, lo
campiono col mio clock, non so quando cambi, risultato: posso campionare il segnale quando sta
cambiando, lo sto campionando in metastabilità, col risultato che posso prendere lucciole per
lanterne. Ho bisogno di un meccanismo che mi garantisca che il segnale di controllo arriva alla
destinazione sincronizzato con il regime di arrivo. Anche il segnale che torna indietro che è il
segnale di Acknowledge parte dal regime numero 2, torna verso il regime numero 1 e deve arrivare
in fase con il regime in questione. Questo vuol dire che sicuramente tutti i segnali che entrano nel
rispettivo regime di sincronizzazione devono prima passare attraverso quei sincronizzatori che
abbiamo identificato precedentemente. Questi oggetti che ho chiamato synch sono quella
successione di flip flop che permettono di far si che quel segnale, che cambia quando vuole, venga
in qualche modo adattato alla frequenza di arrivo. Siccome io voglio un segnale d’ingresso qui e
uno lì, ecco che ognuno ha il suo sincronizzatore. Notate che ogni sincronizzatore viene
sincronizzato col regime di clock d’arrivo. Quindi il sincronizzatore di qua viene sincronizzato col
clock 1, l’altro sincronizzatore è cloccato col clock 2. Adesso entriamo nei dettagli e supponiamo
che quelle due macchine vogliono scambiarsi delle azioni del tipo: ehi c’è il dato valido, ehi ho
capito. Sono informazioni che ognuno dei due genera, quindi la sorgente manderà un’informazione,
tipicamente un fronte, qui per farla semplice supponiamo che sia un fronte di salita, questo fronte
deve generare poi un qualche cosa che viene riconosciuto dall’altra parte. Quando lui risponde sul
suo segnale di uscita manderà un altro fronte, che prima o poi deve generare qualcosa dall’altra
parte. Questo è il concetto. Ora entriamo nei dettagli. Per capire come lavora il tutto, immaginiamo
che i due oggetti lavorino praticamente su un segnale di controllo che ha due stati, uno stato forzato
da chi trasmette e un altro stato forzato da chi riceve. Quando io ho dei dati validi forzo questa
bandierina, quella che si chiama flag. La forzo io, io sono l’unico che può forzare a 1 quella
bandierina. Il ricevitore è l’unico che può forzare a zero quella bandierina. L’operazione di forzare a
uno o a zero la bandierina è un’operazione che viene fatta con un set-reset in cui io ho le chiavi del
set e l’altro ha le chiavi del reset. Siccome io lavoro su fronti nell’esempio, perché funzioni questo
sincronizzatore interno è necessario che il mio fronte sia trasformato in un impulso. Per trasformare
un fronte in un impulso basta prendere il segnale così com’è, mandarlo ad un inverter, quando ho un
impulso in ingresso questo mi da un impulso in uscita di larghezza pari al ritardo dell’inverter.
Quindi il pulse generator è tutto sommato abbastanza semplice. Possiamo farlo un pochino più
complicato mettendoci dentro anche il clock per garantire che abbia una larghezza minima da
tenere. Quindi ho bisogno di un oggetto che mi trasformi il mio fronte di salita in un impulso.
Risultato: il sincronizzatore di controllo parte da zero, quando forzo l’uscita di set questo mi genera
un impulso sul set che fa andare a 1 flag. Allora io ho tirato su la bandierina (flag) questo va in
qualunque istante, in modo assolutamente scorrelato col mondo. Bisogna che questo segnale venga
riconosciuto dal ricevitore. Questo segnale passa attraverso il sincronizzatore sul clock di arrivo, per
generarmi questo che chiamo output-flag. È il valore del flag riconosciuto in uscita. Questo vuol
dire che quando io decido che il dato è valido do il fronte, si trasforma in impulso, setta il flip flop,
questa operazione viene sincronizzata in ricezione e a un certo punto output-flag diventa attivo.
Output-flag sincrono col clock 2. A questo punto in ricezione dice ehi guarda c’è il dato pronto,
oppure, la bandierina è su, allora facciamo qualche cosa. In ricezione faccio quello che devo fare e
quando ho finito devo avvertire dall’altra parte, cioè dire ehi io ho finito. Come avviene questa
azione al contrario? Avviene con un’attivazione del reset, questo fronte diventa un impulso,
l’impulso va sul reset, questo mi provoca il passaggio del flag a zero, questo andare a zero viene
mandato al sincronizzatore da tutte e due le parti, dopo un ritardo opportuno l’input flag dalla mia
parte riconosce che è cambiato lo stato. Questa macchina quindi, è una macchina che permette ai
due regimi di scambiarsi delle azioni in sequenza. Faccio un’azione, ne faccio un’altra, faccio
un’azione, ne faccio un’altra.. in modo sicuro qualunque sia il regime di clock, qualunque sia il
momento in cui avviene l’operazione, quindi questo è un generico sincronizzatore di comandi tra
due regimi qualunque. Ciò mi garantisce se i due oggetti sono progettati ad hoc che ci sia un flusso
sicuro di azioni una da una parte, una dall’altra, una da una parte, una dall’altra.. Ogni attore fa
un’azione e sta in attesa di una controazione dell’altro prima di ripartire.
Se noi vogliamo applicare questo sincronizzatore di comandi a un invio di dati andiamo alla slide
vista prima: il segnale di set è quello che chiamiamo di load ed è un segnale che va sul registro ed è
la validazione da parte del trasmettitore. Il segnale di load, quindi di set, è il segnale che mi agisce
in qualche modo sul campionamento dei dati verso il ricevitore. Nell’esempio tale segnale è quello
che mi attiva il flag. Dopo un po’ di tempo quel segnale diventa attivo al ricevitore. Quel segnale
dopo un po’ di tempo arriva al ricevitore ed è quello che il ricevitore percepisce come un data
available. Quindi quando il ricevitore vede il segnale data available attivo dice: oh guarda c’è un
dato disponibile. Quando arriva data available i dati sono già validi da un po’, perché io li ho scritti
qua dentro, quindi data available diventa attivo e il ricevitore dice tò guarda c’è posta per te. Lui
prende i dati, fa quello che deve fare e quando i dati sono stati accettati dice va bene, ok, puoi
rimuoverli se vuoi -> segnale di reset. Il segnale di reset è un segnale di remove, torna indietro; il
segnale di flag aveva la funzione di full che vuol dire ok, guarda che in questo momento il dato, il
buffer o quello che è, è pieno. Quando lui dà il remove questo genera l’andar via del full. Quindi il
trasmettitore dice tò guarda non è più pieno, posso mandare un altro dato. Questo automaticamente
mi toglie il data available per cui in ricezione automaticamente sa che non ci sono più dati
disponibili e quindi sta in attesa. Quindi questo è il flusso con cui trasforma il sincronizzatore di
controllo in sincronizzatore di dati. Questo genera gli handshake, la scrittura è generata dal segnale
di hold e tutto il resto viene garantito da chi parla e chi riceve i dati, lavorando in modo asincrono,
ognuno col proprio regime però in piena sicurezza. Nel senso che ho i sincronizzatori in ingresso
che garantiscono che i controlli che arrivano, arrivano sempre con setup e hold time validi durante
la ricezione. Il full serve dal mio lato per sapere che non posso mandare nient’altro perché
l’interfaccia è piena. Quando questo è pieno il segnale di full che torna indietro mi dice ok,
semaforo rosso, l’interfaccia è piena. Quest’interfaccia è piena diventa ehi ci sono dati disponibili
dall’altra parte. Quando l’altra parte prende i dati il remove cosa fa, mi tira giù la bandierina, mi
dice ehi ho preso il dato quindi ehi è vuota ormai l’interfaccia, perché il full va a zero e la stessa
cosa serve dall’altro lato per dire ehi guarda non ci sono altri dati disponibili. Circuiti del genere
sono usati dappertutto ogni volta che ci si trova a lavorare con differenti regimi di temporizzazione.
Questi oggetti esistono come celle di libreria e servono appunto se si vanno a fare circuiti integrati
che lavorano con diversi regimi di temporizzazione. Tutto sommato è semplice, ce la caviamo con
7-8 registri. C’è solo un piccolo inconveniente con questa struttura che è il tipico problema
produttore-consumatore. C’è uno che produce i dati e l’altro che li consuma. Questo problema
funziona e porta a risultati ottimi quando la statistica associata alla produzione e al consumo sono
più o meno simili. Se le statistiche non hanno gli stessi parametri e sono differenti il sistema visto
entra in crisi. In questo caso questo sistema mette in crisi la capacità di trasporto dell’informazione.
Vediamo un esempio: il mio sistema sta ricevendo dati a pacchetti (a burst), vuol dire che io ricevo
200 pacchetti e poi basta. Ma non posso dire al bit in ricezione no aspetta fermati che prima cerco di
capire quest’altro bit e poi dopo andiamo avanti. Quando c’è una scorrelazione notevole tra la
statistica del trasmettitore e quella del ricevitore il meccanismi di trasferimento e di handshake su
singolo dato entra in crisi. Il meccanismo che si usa allora è l’equivalente di una FIFO d’interfaccia
tra i due regimi. Il trasmettitore trasmette i dati riempiendo la FIFO che fa da tampone, con tutta
l’interfaccia dei segnali di temporizzazione. Finchè la fifo non è piena io posso continuare a
scrivere, questo è gestito col clock del trasmettitore che gestisce le scritture dentro la fifo. Il
consumatore lavora sulla parte di controllo: se la fifo è vuota non scaricare niente, oppure la fifo
non è empty, cioè non è vuota, allora ti dico io quando leggere il prossimo dato. Il tutto è gestito col
clock del ricevitore.
Il concetto di fifo è un concetto che permette di separare la statistica del trasmettitore da quella del
ricevitore. È ovvio che parliamo di considerazioni statistiche ma i valori medi della ricezione e della
trasmissione ovviamente devono essere gli stessi. Questo è un problema complesso nei sistemi
digitali e ci sono corsi interi dedicati all’argomento. È un problema di dimensionamento della FIFO.
La fifo ha un costo in termini di area, di potenza. È inutile dire mettiamo una fifo da 2 GB così
siamo sicuri. Devo andare a dimensionare tutte le fifo d’interfaccia tra i regimi. In funzione di
quanto sono scorrelate le statistiche di chi parla e di chi riceve. Tanto maggiore è la differenza tra il
burst di chi produce e la frequenza di chi riceve e tanto maggiore deve essere la Fifo. Il
dimensionamento della fifo è uno dei punti cruciali più difficile da gestire. L’interfaccia Fifo,
quindi, la devo usare quando le due statistiche degli interlocutori sono scorrelate e quindi è ovvio
che usare il protocollo sulla singola informazione rischia di abbassare la velocità del tutto. Il nostro
caso prevede un canale unidirezionale tra trasmettitore e ricevitore. Se entrambi i dispositivi
funzionassero alternativamente da trasmettitore/ricevitore avrei un canale bidirezionale e quindi
ogni canale deve avere una FIFO associata. È possibile che in questi casi su alcuni canali io debba
avere la fifo, su altri non ce n’è di bisogno, dipende tutto dalle statistiche dei comunicatori.
Apriamo adesso, in ultima analisi, una finestra introduttiva su un certo mondo, il quale bandisce il
concetto di clock. Cioè preoccupiamoci di realizzare degli oggetti che non solo scambiano
informazioni ma lavorano in modo asincrono (o self timed), cioè senza clock. Vediamo qual è la
funzione del clock in un sistema sincrono. Il clock agisce come evento di fine operazione, io so che
i dati viaggiano e quando arriva il clock devono essere stabili. Il clock è il garante ed il mezzo con
cui vengono verificate la corretta sequenza di azioni. Il clock è un evento e ad ogni vento faccio
qualcosa. Vantaggi: facile da fare, facile da debuggare, non si hanno particolari sorprese, si riesce
ad andare a frequenze anche molto elevate. In sistemi complessi ha un sacco di vantaggi positivi.
L’unico svantaggio del mondo sincrono è che tutti commutano in quel momento: picchi di corrente,
picchi di rumore, problema di portare la corrente, ecc..
Universo opposto: mondo completamente asincrono. Il timing è garantito unicamente dal progetto
preciso e sicuro dei ritardi dei vari blocchi. Il concetto di completamento di un’azione viene
garantito esclusivamente da un’analisi perfettamente corretta del timing e dei ritardi. La stessa
analisi dei ritardi, ogni singolo progetto di un blocco che ritarda, è garante del fatto che la sequenza
di azioni è quella voluta. Il fatto che tutto funzioni esclusivamente tramite i ritardi è il motivo per
cui tutti i circuiti con cui abbiamo a che fare sono sincroni. Un processore completamente asincrono
non si è ancora visto. Nel mondo asincrono si va a lavorare di lima sulla singola porta e subentrano
tanti fattori quali temperatura, umidità, rumore, ecc ecc.. per cui è tutto molto complicato.
Il vantaggio del mondo asincrono è che ogni segnale cambia quando vuole. Vuol dire che se vado a
fare misure di assorbimento di corrente in un circuito asincrono questa è ragionevolmente costante
nel tempo, è statisticamente mediamente distribuita. Il tutto però diventa irrealizzabile se non in
sistemi molto semplici. Esiste però un mondo intermedio che è usato molto spesso anche dal punto
di vista commerciale e che si basa su quello che si chiama Self Timed Design. Cioè il concetto di ho
finito è un segnale che può avere una sua valenza su un fronte e che può essere usato da un oggetto
che è a valle. La differenza dal mondo sincrono è che i segnali vengono generati localmente in
modo completamente asincrono. Esiste anche qui tun mondo di handshake e protocolli.
Per capire il concetto della logica Self timed partiamo dal concetto che ben conosciamo di un
sistema sincrono. Un sistema sincrono è fatto da registri, logica combinatoria e tutto va avanti;
pipeline: ad ogni colpo di clock prendo il dato in ingresso e lo porto in uscita.
Sistema Self Timed: esiste sempre la logica del pipeline, però il caricamento del registro viene
generato da una macchinetta di handshake tipo quella che abbiamo vista prima, che lavora
localmente con una coppia di segnali. Un segnale di start quando il dato è disponibile e quando
questo ha finito lui dà un segnale di done, finito che va alla macchinetta che lo utilizzerà per
interfacciarsi col resto del mondo. Il concetto è Start: parto, faccio quello che devo fare; ho finito,
questo ho finito genererà sicuramente un segnale al blocco dopo per dire ehi può partire e allora
questo farà campionare il registro R2 e darà un segnale a quello che c’è prima per dire dammi il
prossimo dato perché io sono qui con le mani in mano. Questo ha il vantaggio che ogni blocco
lavora ad una frequenza che è legata alla complessità di quello che deve fare. Questo è un mondo in
cui se ognuno ha una sua idea del tempo che impiega per fare la sua operazione è un mondo che
permette di far sì che localmente ogni oggetto capisce, da quando parte l’elaborazione, quando può
finire in modo corretto l’operazione stessa ed è un mondo che ha tutti i vantaggi sia del mondo
sincrono che del mondo asincrono. Vantaggi: tutto è gestito da un flusso di temporizzazioni locali
che vengono garantite dai segnali di temporizzazione. Vantaggio: l’handshake gestisce la
successione degli eventi in modo opportuno. Vantaggio: ognuno lavora alla massima frequenza,
mentre in un sistema sincrono una rete combinatoria, più veloce delle altre, finisce e poi se ne sta
con le mani in mano ad aspettare il clock. Ultimo vantaggio: ognuno commuta quando vuole, non
tutti insieme: non ho i picchi di corrente. Allora il Self Timed dove applicabile è sicuramente il
migliore dei mondi possibili. L’inconveniente è quello che devo accorgermi quando ho finito. Ogni
blocco logico, quindi, deve essere in grado di accorgersi quando ha finito. In alcuni casi questo è
abbastanza facile, in altri casi posso trovare delle soluzioni ad Hoc. Avere la percezione completa
del termine di un’operazione può essere estremamente complicato. Mi spiego: se prendo un
moltiplicatore da un punto di vista logico come faccio a sapere quando è finita la moltiplicazione?
Ciò è tutt’altro che banale. Il limite più grande delle logiche Self Timed è che per tutta una serie di
operatori capire quando ha finito può essere più complicato che fare l’operazione stessa. Lo si fa
quando è facile oppure lo si fa usando dei trucchi. Un trucco molto semplice è questo: faccio
un’indagine della rete logica per capire qual è il percorso più lungo e mi ricreo il percorso più lungo
in un mondo, quello che si chiama dummy line (linea fantasma), che praticamente ricalca/ricopia il
percorso più lungo del mio blocco. Son sicuro che quando parte lo start, e qua percorre il percorso
più lungo prima di uscire e qui percorre una copia di questo percorso più lungo, questo mi
garantisce che quando arrivo in fondo il segnale è done, finito. È un progetto fisico che consiste
nell’andare a vedere qual è il percorso più lungo del mio blocco e generare un ritardo pari al più
lungo. Un’applicazione tipica di questi oggetti sono le memorie. La memoria è in qualche modo un
array in cui ci sono dei driver, bitline e in qualche modo prendo la parola e la tiro fuori.
Si mette un’extra line alla memoria che fa il percorso più lungo. Allora tutti i dati saranno
sicuramente già in uscita prima che arrivi il percorso più lungo. Questo è un metodo molto usato
nelle memorie sia per prestazioni, nel senso che la posso fare lavorare effettivamente al massimo
della velocità, sia dal punto di vista dei consumi. È inutile lasciare attivi tutti i circuiti interni
quando so di essere arrivato in fondo. In altri casi è molto più facile capire che ho finito usando
codifiche ridondanti.
Tipo quella che abbiamo visto prima dell’empty e valid. So subito che ho finito quando cambiano
dei bit. Questa è un tipo di codifica Self Timed. Uso cioè una codifica tale per cui all’interno dei
dati c’è l’informazione di validità. L’NCL vista prima è una logica Self Timed che funziona in
questo modo. Io faccio la mia elaborazione locale, quando finisco codifico i dati in uscita dal
moltiplicatore con questa codifica per cui mi basta andare a vedere che cosa ho in uscita e quando in
uscita vedo che tutti i bit sono validi vuol dire che il moltiplicatore ha finito.
L’ultimo esempio è un esempio molto più semplice che si usa nelle logiche dinamiche nonché in
alcuni tipi di memoria. Immaginiamo di avere una struttura logica che lavora in due fasi , una prima
fase in cui precarico le uscite e una seconda fase in cui vado in valutazione. Quanto deve durare la
precarica? La precarica dura il tempo necessario in cui quelle due uscite diventano tutti 1. Quando
diventano tutti 1, OR sui negati vuol dire che è una Nand, dico: fine della precarica, tutti hanno
finito e possiamo passare al ciclo successivo. Questo è un altro metodo, questa volta logico, che
permette di accorgermi quando un’operazione è finita.
L’ultimo metodo si basa sull’avere a che fare con una logica di tipo CMOS, dove il consumo di
corrente, se non ho problemi di conduzione sotto soglia e altre robe che al momento non ci
interessano, si ha solo durante le commutazioni. Finite le commutazioni il circuito non consuma
più. In questo circuito CMOS statico metto un sensore di corrente, che sta a vedere quando il
sistema consuma. È partita la commutazione, lo start, aspetto un tempo minimo per evitare di dire
ho finito perché lui in realtà non aveva neanche cominciato a consumare, cioè aspetto un tempo
minimo per garantire che lui sia andato in conduzione e che quindi stia consumando. Nel momento
in cui finisce il consumo dico ok guarda..
Se c’è il leakage lo risolvo mettendo semplicemente un sensore di corrente a una soglia. Un sensore
di corrente non è detto che misuri solo un livello ma può anche andare a misurare la derivata della
corrente. Comunque il 98% dei progetti industriali sono sincroni quindi noi lavoreremo su quelli
perché è importante saper fare bene quelli. La butterfly potrebbe benissimo essere fatta in modo
Self Timed: entrano i dati codificati in qualche modo, automaticamente quando i dati in uscita sono
validi all’interno dei dati metto la validazione per andare ad esempio alla butterfly successiva.