42
Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC Progetto di Apparati Elettronici A.A. 2009-10 Giovanni Accongiagioco Simone Brienza Marco Camilli Daniele Giannetti

Progettazione di una rete di calcolo dell'arcotangente ... · CORDIC (COordinate Rotation DIgital Computer) è un algoritmo che permette di calcolare in modo ... Se si suppone che

Embed Size (px)

Citation preview

Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Progetto di Apparati Elettronici

A.A. 2009-10

Giovanni Accongiagioco Simone Brienza

Marco Camilli Daniele Giannetti

2 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

SOMMARIO

1. INTRODUZIONE ..............................................................................................................................3

1.1 Algoritmo CORDIC ............................................................................................................................... 3

1.2 Architetture possibili per l'algoritmo CORDIC ..................................................................................... 7

1.2.1 Architettura combinatoria CORDIC feed-forward ........................................................................................ 7

1.2.2 Architettura pipeline CORDIC feed-forward ................................................................................................ 8

1.2.3 Architettura CORDIC con feedback .............................................................................................................. 9

1.2.4 Architetture CORDIC seriali ........................................................................................................................ 11

2. ARCHITETTURA PROPOSTA ........................................................................................................... 12

2.1 Struttura a Blocchi ............................................................................................................................. 12

2.1.1 Componenti elementari ............................................................................................................................. 14

2.1.2 CORDIC_INV ............................................................................................................................................... 15

2.1.3 CORDIC_ADDER .......................................................................................................................................... 16

2.1.4 CONTROLLER .............................................................................................................................................. 17

2.2 Dimensionamento del circuito .......................................................................................................... 18

3. CODICE VHDL ................................................................................................................................ 25

3.1 inv.vhd ............................................................................................................................................................... 26

3.2 cordic_adder.vhd ............................................................................................................................................... 27

3.3 adder.vhd ........................................................................................................................................................... 29

3.4 dff.vhd ................................................................................................................................................................ 30

3.5 barrel_shifter.vhd .............................................................................................................................................. 31

3.6 rom.vhd ............................................................................................................................................................. 33

3.7 CORDIC.vhd ........................................................................................................................................................ 34

4. TEST BENCH .................................................................................................................................. 38

5. CONCLUSIONI ............................................................................................................................... 41

RIFERIMENTI ................................................................................................................................................... 42

3 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

1. INTRODUZIONE

Prima di affrontare la progettazione del circuito, risulta utile fare una breve introduzione all'algoritmo che verrà utilizzato per il calcolo dell'arcotangente. In generale il calcolo delle funzioni trigonometriche non è un argomento banale, soprattutto quando si vogliono raggiungere prestazioni ragionevolmente sufficienti da suggerire l’integrazione del circuito all'interno di macchine sincrone complesse.

1.1 ALGORITMO CORDIC [1]

CORDIC (COordinate Rotation DIgital Computer) è un algoritmo che permette di calcolare in modo semplice e efficiente funzioni trigonometriche ed iperboliche. Il suo grande vantaggio rispetto ad altri algoritmi è che non ha alcun bisogno di disporre di moltiplicatori hardware e per questo viene tipicamente implementato in dispositivi dove non si prevedono moltiplicatori o altre unità per operazioni aritmetiche complesse. In letteratura [2] è anche noto come Algoritmo di Volder o Metodo digit-by-digit e, come vedremo successivamente, per una sua implementazione circuitale è sufficiente avere a disposizione sommatori, inverter, shifter, multiplexer ed una rom che contenga una tabella di dati significativi. In questa trattazione vedremo il CORDIC per il calcolo dell'arcotangente di un rapporto di numeri interi [1][3], i dettagli su come effettivamente si decide di rappresentare i numeri negativi ed altri aspetti implementativi verranno forniti successivamente. Dati due numeri interi e vogliamo che il nostro circuito stimi il valore dell’arcotangente del loro rapporto, ovvero esprimendolo matematicamente:

Se si suppone che queste due quantità siano parte reale e parte immaginaria di un numero complesso:

allora l’angolo che si vuol calcolare non è altro che la fase di tale numero sotto opportune condizioni:

4 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Dove per fase si intende l’angolo tra il vettore ed il semiasse reale positivo del piano di Gauss, come mostrato nella figura seguente.

E' bene notare fin da subito che vi sono alcune condizioni particolari che bisogna trattare in modo diverso perché l'algoritmo non è applicabile (né conviene applicarlo) in tutti i casi.

• Se l'algoritmo non funziona, ma la tangente cercata è la stessa che si ottiene sfruttando (al posto di e den) e , dunque si procede invertendo i segni di entrambi gli ingressi.

• Se allora il numero complesso giace sull'asse immaginario e dunque col segno di b siamo in grado immediatamente di dare la tangente risultante:

o implica o implica

• Se si ha immediatamente che il numero complesso giace sull'asse reale e dunque: .

Dunque per illustrare l'algoritmo possiamo supporre:

Osserviamo adesso una proprietà dei numeri complessi che sta alla base del funzionamento dell'algoritmo. Per due numeri complessi e vale sempre:

Dove α1 e α2 sono le fasi dei due numeri complessi. Ne deriva allora che moltiplicando un numero complesso per un altro numero complesso avente fase si effettua la rotazione di attorno all'origine degli assi di un angolo . Di qui si può capire il principio di funzionamento dell'algoritmo:

5 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Si calcola l'angolo tra il numero complesso e l'asse reale positivo calcolando di quanto è necessario ruotare tale numero affinché il vettore che rappresenta si trovi sufficientemente vicino all'asse reale, e cambiando di segno il risultato.

Quel che in pratica si fa è ruotare il numero complesso di angoli sempre più piccoli cercando di farlo avvicinare all'asse reale positivo. Quando si è sufficientemente vicini si interrompe il ciclo, fornendo il risultato come sommatoria degli angoli di cui si è ruotato, cambiati di segno.

Per realizzare questa procedura in un circuito efficiente è necessario avere a che fare con numeri complessi semplici. In particolare, nella versione che verrà implementata in questo elaborato si fanno al massimo 8 iterazioni, e ad ogni iterazione si va a moltiplicare per uno dei seguenti numeri complessi, che corrisponde a ruotare di diversi angoli:

6 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Dunque si va a moltiplicare per numeri complessi che sono sempre dati da parte reale pari ad 1 e parte

immaginaria pari a , dove è il numero dell'iterazione. Ad ogni iterazione si decide se moltiplicare per il complesso con parte immaginaria positiva oppure per il suo complesso coniugato con lo scopo di avvicinare sempre di più all'asse reale il prodotto dei due. Notiamo inoltre che il modulo del numero complesso che si va a moltiplicare al risultato dell'iterazione precedente è sempre maggiore di 1, per cui il modulo del prodotto tende sempre ad aumentare. Alla fine delle 8 iterazioni si è ruotato il numero iniziale svariate volte sempre di angoli più piccoli (siano questi angoli gli ), e si ha che (essendo α l'angolo originale):

Per implementare il prodotto tra numeri complessi (ristretto ai casi che ci interessano) in modo semplice notiamo che:

Allora possiamo dare l'algoritmo descrivendolo in pseudocodice [4]:

// den // num

// angolo alfa

// si ruota nel verso positivo (verso lo zero)

;

;

; // si sottrae l'angolo // si ruota nel verso negativo (verso lo zero)

;

;

; // si aggiunge l'angolo // siamo esattamente sull'asse reale // si esce dal ciclo while

;

7 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Notiamo che, per come è fatto l'algoritmo, ad ogni ciclo la aumenta mentre la diminuisce in modulo. Naturalmente, maggiore è il numero di iterazioni che si fanno dell'algoritmo (nel caso specifico al massimo 8) e più preciso sarà il risultato in media. Al solito si sono fatte le ipotesi di e .

Va osservato inoltre che non sempre ad ogni iterazione si raffina maggiormente il risultato, in realtà è possibile che si raggiunga un numero complesso molto vicino all'asse reale ma avente : in questo caso l'algoritmo non si ferma in quanto non è soddisfatta la condizione di terminazione, procedendo nelle iterazioni è assai probabile che il numero complesso utilizzato nell'ultima iterazione (con 8 iterazioni sarà ) sia più distante dall'asse reale rispetto al numero complesso ricavato precedentemente, questo conduce inevitabilmente ad un risultato meno accurato rispetto a quello che si sarebbe ottenuto fermandosi all'iterazione dove si è prodotto .

1.2 ARCHITETTURE POSSIBILI PER L'ALGORITMO CORDIC [5]

Vediamo ora un insieme di possibili architetture per l’implementazione dell’algoritmo CORDIC così come descritto nel paragrafo 1.1, mettendo in evidenza vantaggi e svantaggi per ciascuna di esse.

1.2.1 Architettura combinatoria CORDIC feed-forward

Questa è l'architettura più semplice che si possa immaginare: dati i due ingressi a e b dell'algoritmo si provvede a sommare oppure sottrarre ad ogni ingresso l'ingresso opposto opportunamente shiftato (in particolare si shifta di un numero fisso di locazioni ad ogni stadio). La figura seguente da una esemplificazione in cui compaiono due stadi.

8 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Il primo stadio non effettua uno shift, il secondo stadio effettua lo shift di una posizione, il terzo di due e così via fino ad arrivare all'ottavo stadio.

Ovviamente nell’architettura che presentiamo in questa sezione mancano delle parti di circuito abbastanza significative, in particolare manca tutta una parte di controllo che si occupa di gestire somme e sottrazioni e della terminazione precoce dell'algoritmo. Inoltre il circuito di controllo deve esaminare ogni volta il segno del secondo ingresso allo stadio ed in base a tale segno decidere se aggiungere oppure sottrarre i valori immagazzinati in ROM alla cumulata finale.

Questa architettura è molto semplice e da luogo ad una rete completamente combinatoria: questo significa che non consente ottimi valori di frequenza di pilotaggio qualora inserita in dei circuiti complessi, in quanto da vita ad un percorso critico molto lungo.

1.2.2 Architettura pipeline CORDIC feed-forward

In questo caso si riprende il modello della precedente architettura e si risolve il problema dei percorsi critici inserendo in ogni stadio dei registri di pipeline (stadi di pipeline). L'architettura risultante è la seguente.

Questa soluzione consente di aumentare la frequenza di clock che non risulta più un problema qualunque sia il numero di iterazioni dell'algoritmo che si vuole compiere. Con questa architettura è evidente che il throughput aumenta, anche se è altrettanto evidente che aumenta la latenza.

Valgono le stesse considerazioni fatte per l'architettura precedente riguardo la mancanza di un circuito di controllo e di tutta la parte che si occupa di eseguire la cumulata dei valori memorizzati in ROM.

9 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

1.2.3 Architettura CORDIC con feedback

Riconoscendo una catena di stadi nell'architettura precedente è possibile eseguire il "rolling" della architettura a pipeline in una architettura decisamente meno complessa in termini di componenti circuitali, la quale ha (sempre ad alto livello) la seguente struttura.

In linea di principio si tratta di creare una reazione che si occupi di effettuare le giuste operazioni su parte reale ed immaginaria del numero complesso senza dover ripetere i componenti elettronici, con le seguenti implicazioni:

• enorme risparmio di area in quanto non si ripetono i componenti per ogni iterazione che si vuole eseguire;

• elevata configurabilità dell'architettura, che si può utilizzare anche per un numero variabile di passi dell'algoritmo CORDIC;

• la frequenza di clock massima consentita non ha variazioni significative rispetto al caso pipeline;

• il throughput rispetto al caso precedente si riduce, e il risultato finale è pronto solamente in un numero di cicli di clock pari al numero di iterazioni che si vuol far fare all'algoritmo (se non si ha uscita precoce).

E' inoltre interessante notare che in questo caso gli shifter non possono shiftare di un numero fisso di locazioni, in realtà infatti tale numero è dato da quale iterazione dell'algoritmo si sta eseguendo, per questo gli shifter dovranno essere sostituiti con dei barrel shifter (reti completamente combinatorie in grado di shiftare l'ingresso di un numero fornito di locazioni in un unico ciclo di clock).

E' questa l'architettura utilizzata nello schema circuitale esemplificativo fornitoci da specifiche, che riportiamo sotto.

10 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Dove però si devono fare alcune considerazioni:

interi negativi rappresentati in complemento ad 1. La scelta di questo formato di rappresentazione si deve al fatto che è l’unico in cui si realizza la perfetta equivalenza fra l’operazione di shift e la divisione/moltiplicazione per 2, necessaria per implementare CORDIC [6];

con shifter si intendono in realtà dei barrel shifter, configurabili in base al numero di shift che si vuole effettuare (vedi sezione 2);

diversamente dai casi precedenti viene riportata anche la parte circuitale che esegue la cumulata dei valori in ROM facendo somme o differenze a seconda della parte immaginaria del numero complesso all'iterazione attuale.

11 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

In questo documento verrà progettata e descritta tramite VHDL un'architettura simile a quella sopra dove però sono state fatte alcune modifiche (vedere sezione 2).

1.2.4 Architetture CORDIC seriali

Si possono anche pensare delle architetture seriali che elaborano un bit per volta degli ingressi e che restituiscono l'uscita bit per bit, tuttavia questo tipo di architetture non sono di interesse nella presente trattazione per cui non le esamineremo.

12 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

2. ARCHITETTURA PROPOSTA

2.1 STRUTTURA A BLOCCHI

L’architettura che è stata pensata per risolvere il problema è simile a quella vista in sezione 1 come esempio di architettura CORDIC con feedback (e che era suggerita da specifiche). Nel descrivere la struttura useremo un approccio Top-Down che passa attraverso vari schemi a blocchi, dal livello di astrazione più alto a quello dei componenti elementari.

Al livello più alto la struttura del circuito da utilizzare per il calcolo dell'arcotangente del rapporto di interi è la seguente:

Dove:

• numh numeratore della frazione di cui si vuole calcolare l'arcotangente, è un intero in complemento ad 1 rappresentato su h bit (nell'esempio 12);

• denh denominatore della frazione di cui si vuole calcolare l'arcotangente, è un intero in complemento ad 1 rappresentato su h bit (nell'esempio 12);

• s (sample) linea attiva alta, da portare ad 1 quando gli ingressi sono pronti ad essere prelevati per cominciare l'esecuzione dell'algoritmo;

• clk clock;

• risk risultato dell'algoritmo, è un numero intero rappresentato in complemento ad 1 e in virgola fissa su k bit (nell'esempio 12).

• v (valid) linea di uscita che assume valore logico alto quando il risultato presentato in risk ha il valore cercato (risultato dell'esecuzione dell'algoritmo CORDIC).

13 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

A questo punto possiamo allora scendere di un livello di astrazione vedendo il funzionamento a macroblocchi, il quale ricalca la suddivisione naturale che si ottiene partendo dallo schema circuitale d'esempio fornito per l'architettura CORDIC con feedback.

Durante la descrizione in VHDL dell'architettura mostrata, useremo una forma strutturale.

Osserviamo la presenza di un ingresso i, portato sia ai barrel shifter che alla ROM, che rappresenta l'indice dell'iterazione attuale dell'algoritmo. Nella versione presentata l'algoritmo esegue al massimo 8 cicli e dunque i sarà trasportato nella rete tramite 3 collegamenti (va da 0 a 7).

Analizziamo dunque singolarmente i componenti adoperati all’interno dell’architettura in esame, partendo dalla descrizione di quelli fondamentali.

14 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

2.1.1 Componenti elementari

Si suppone di avere a disposizione:

• Inverter ad N bit.

• Multiplexer ad N bit: dati due bus di ingresso ad N bit, il multiplexer decide quale dei due ingressi collegare all'unico bus di uscita a seconda di un bit di controllo che viene fornito come ulteriore ingresso.

• Registro ad N bit: realizzato come un insieme di flip-flop d-positive-edge-triggered, è in grado di memorizzare una parola di N bit ad ogni ciclo di clock [9].

• Sommatore ad N bit in complemento ad 1: Supponendo di lavorare coi naturali, il sommatore più semplice che si utilizza nella pratica è il Ripple Carry Adder [9]. Quando si lavora con gli interi però, usando la rappresentazione in complemento ad 1, il sommatore classico deve essere modificato usando il riporto prodotto in uscita come riporto di ingresso (end-around carry) [7]. Questo eventualmente può duplicare i livelli di logica che è necessario attraversare prima di avere l'uscita corretta. Un esempio di Ripple Carry Adder capace di lavorare con interi in complemento ad 1 è il seguente:

Naturalmente, non è necessario realizzarlo tramite un RCA, si può infatti utilizzare un qualunque metodo per realizzare un sommatore (ad esempio reti tipo CLA), l'importante è che sia una rete puramente combinatoria e che si abbia cura di riutilizzare il riporto in ingresso.

• Barrel Shifter ad N bit: Il barrel shifter è una rete combinatoria nota in letteratura [8], capace di shiftare la parola in ingresso di un numero variabile di bit a seconda del valore di un ingresso di controllo. Supponendo di voler realizzare un barrel shifter che shifta al massimo di 15 locazioni, la parola di controllo deve essere su 4 bit e ogni bit della parola di controllo viene utilizzato per attivare o disattivare uno dei quattro shifter interni, i quali shiftano di un numero di bit fisso e diverso tra loro. Una struttura esemplificativa è fornita sotto:

15 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Analogamente un barrel shifter che shifta al massimo di 7 locazioni (come quello che useremo nella progettazione del circuito in esame), avrà all'interno 3 shifter che shiftano rispettivamente di 1, 2 e 4 locazioni.

• ROM: Memoria contenente i valori degli angoli utilizzati dal circuito per eseguire la cumulata finale da presentare poi all'utente, vengono dunque memorizzate le arctan(2-i) per i che va da 0 al valore voluto di iterazioni -1 (nel nostro caso da 0 a 7) [9].

Tutti i componenti sopra sono stati descritti in modo puramente comportamentale tramite VHDL, e utilizzati per la simulazione post-design. Esaminiamo invece ora gli altri blocchi, che sono stati implementati utilizzando gli elementi di base descritti precedentemente (per cui anche la loro descrizione è strutturale):

2.1.2 CORDIC_INV

Questo componente molto semplice è fatto come segue:

E il suo scopo è praticamente quello di invertire il segnale di ingresso yin quando xin assume valore '1', altrimenti lasciar passare il valore yin inalterato.

16 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

2.1.3 CORDIC_ADDER

Il componente ha la struttura seguente:

Si noti che, rispetto allo schema esemplificativo per una architettura con feedback dell'algoritmo cordic, il sommatore e il registro sono stati scambiati di posizione. Nella schema esemplificativo si può infatti evidenziare il difetto che l'uscita dal sommatore del primo ingresso è collegata al barrel shifter e tramite inverter e multiplexer finisce in ingresso al secondo sommatore, facendone variare l’uscita. Questa si propaga attraverso un'altra coppia barrel shifter - multiplexer nuovamente sul primo sommatore: si è creato un ciclo di reti combinatorie (vedi figura).

17 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Gli strumenti per la simulazione post-design che si sono utilizzati (Active HDL) non sono in grado di gestire opportunamente la situazione fino a raggiungere l'equilibrio, per cui si spezza il ciclo post-ponendo il registro al sommatore. Questa operazione aggiunge stabilità al circuito finale, è necessaria per la simulazione, ed è anche necessaria per il funzionamento del circuito in quanto i valori che si vanno a shiftare tramite il barrel shifter devono essere quelli dell'iterazione precedente, che poi si vanno a sommare a quelli precedenti dell'altro ingresso, producendo il risultato dell'iterazione corrente: i risultati corrente e precedente devono quindi essere opportunamente separati tramite un registro.

2.1.4 CONTROLLER

questo componente è di fondamentale importanza, data la complessità logica delle sue operazioni esso è stato descritto tramite un VHDL puramente comportamentale. Nello schema complessivo precedente non vengono evidenziati tutti i suoi ingressi e le sue uscite, che quindi mostriamo in quest’altra figura:

Le sue funzioni principali sono le seguenti:

• gestione dei cicli e dell'incremento della variabile interna i (indice di ciclo), in uscita verso la ROM e i barrel shifter. Questa è stata indicata con "counter" nella descrizione VHDL in sezione 3;

• controllo della condizione di uscita precoce quando la parte immaginaria del numero complesso assume valore nullo (per questo c’è y come ingresso);

• gestione delle condizioni particolari presentate al circuito, ossia i casi in cui num = 0 o den = 0 (il caso in cui den < 0 viene gestito automaticamente dal circuito grazie ai due cordic_inv subito collegati agli ingressi). Per questo entrambi gli ingressi del circuito vanno al controller;

• generazione del segnale v (valid) quando l'uscita del circuito assume il valore finale risultato dell'esecuzione dell'algoritmo. Il valore v deve essere ritenuto valido dall'utilizzatore solamente quando il sample è stato già messo a '0';

• gestione del risultato e suo allineamento ad un certo numero di bit (vedi paragrafo successivo). In effetti è il controller a mandare in uscita ris e non il resto del circuito come mostrato nello schema complessivo precedente;

• imposizione di valori opportuni su alcuni collegamenti per la corretta partenza dell'algoritmo quando l'utente mette sample ad 1 prima dell'arrivo del fronte in salita del clock (si sfruttano tre bit in uscita dal controller. Si faccia riferimento al codice per una completa comprensione del meccanismo di funzionamento).

18 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Riguardo quest'ultima osservazione, notiamo che nel circuito che si è progettato non vi sono segnali di reset. Infatti lo stato iniziale dei registri utilizzati non è significativo in quanto viene sovrascritto appena arriva un fronte in salita del clock mentre sample è ad '1'. Tuttavia non bisogna dimenticare che prima di arrivare al registro vi è un sommatore dopo l'uscita del multiplexer: questo comporta che bisogna tenere fisso a zero il secondo ingresso ai blocchi cordic_adder quando sample è ad '1' (anche il terzo blocco che si occupa di fare la cumulata dei valori deve inizialmente avere il secondo ingresso pari a 0 per evitare di cominciare la cumulata, al posto che da 0, dal primo valore in ROM).

A questo livello è bene evidenziare che, mentre i vari componenti dell'architettura sono stati realizzati secondo uno stile di progettazione VHDL configurabile tramite generic, la struttura finale data dalla composizione delle varie parti è fornita nel caso particolare in cui:

h = 12 (#bit degli ingressi) k = 12 (#bit dell’uscita) M = 18 (#bit dei dati elaborati nel circuito)

Questa scelta è dovuta a considerazioni che faremo nel prossimo paragrafo.

2.2 DIMENSIONAMENTO DEL CIRCUITO [10] [11]

Nel seguito prendiamo in analisi il caso, preso dalle specifiche, in cui i valori di ingresso e di uscita siano interi rappresentati su 12 bit. Nelle specifiche la ROM ha dimensioni 8x12, per cui conterrà parole da 12 bit.

In particolare i valori memorizzati nella ROM e di cui bisogna fare la cumulata sono i valori delle arcotangenti (valori ovviamente non interi) memorizzati come reali positivi espressi in virgola fissa con 12 bit complessivi di cui 6 sono bit frazionari. Per fare degli esempi:

45 (arctan(1)) -> 45*64 = 2880 in ROM 26.56505... (arctan(0.5)) -> 26.56505*64 = 1700.163... -> 1700 (valore troncato) in ROM ...

Il fatto che si esegua un troncamento mostra da subito un primo errore che si commette per l'esecuzione dell'algoritmo CORDIC, tuttavia non si tratta dell’unico; gli errori che si commettono nell'algoritmo implementato con l'architettura di cui sopra sono di tre tipi:

a) Troncamento dei valori immagazzinati in ROM; b) Errore insito nell'algoritmo in quanto si eseguono solamente 8 cicli al massimo mentre in realtà

l'algoritmo converge sempre solo in un numero infinito di cicli; c) Errore derivante dal troncamento dei valori shiftati.

19 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

L'errore di cui al punto “a” deriva solamente dalla necessità di rappresentare in ROM i valori in virgola fissa su un numero finito (nel nostro caso 12) di bit ed è ineliminabile, così come quello del punto “b”, che dipende dal fatto di aver scelto di fare al massimo 8 cicli.

Cerchiamo di analizzare il punto “c”, che non è di facile comprensione.

L'algoritmo CORDIC prevede di effettuare somme e sottrazioni di valori moltiplicati per una potenza negativa del 2. Questo viene operativamente fatto prima della somma o tramite dei barrel shifter che dividono per una potenza di due shiftando a destra il valore (shift in complemento ad 1) di un numero variabile di locazioni. Tuttavia è bene notare che si può tranquillamente verificare una condizione di underflow (ossia come risultato dello shift si perdono alcuni bit meno significativi).

Il problema è risolvibile aggiungendo dei bit meno significativi al numero, e lavorando dunque con più di h bit nelle somme e negli shift. Basta dunque capire quanti bit è necessario aggiungere per risolvere o almeno ridurre l'impatto del problema.

E' facile capire che:

• Al primo ciclo i bit non vengono shiftati (i = 0) e dunque la somma o sottrazione avviene senza usare i bit meno significativi aggiuntivi;

• Al secondo ciclo si shifta a destra di 1 bit (i = 1) e dunque la somma o sottrazione al massimo utilizza 1 dei bit aggiunti;

• Al terzo ciclo si shifta a destra di 2 bit (i = 2) il risultato dell'iterazione precedente e dunque la somma o sottrazione al massimo utilizza 3 dei bit aggiunti;

• Al quarto ciclo si shifta a destra di 3 bit (i = 3) il risultato dell'iterazione precedente e dunque la somma o sottrazione al massimo utilizza 6 dei bit aggiunti;

• Al quinto ciclo si shifta a destra di 4 bit (i = 4) il risultato dell'iterazione precedente e dunque la somma o sottrazione al massimo utilizza 10 dei bit aggiunti;

• Al sesto ciclo si shifta a destra di 5 bit (i = 5) il risultato dell'iterazione precedente e dunque la somma o sottrazione al massimo utilizza 15 dei bit aggiunti;

• Al settimo ciclo si shifta a destra di 6 bit (i = 6) il risultato dell'iterazione precedente e dunque la somma o sottrazione al massimo utilizza 21 dei bit aggiunti;

• All'ottavo ciclo si shifta a destra di 7 bit (i = 7) il risultato dell'iterazione precedente, ma l'unica cosa che conta è il risultato dell'iterazione precedente e non quello di questa iterazione.

Per ridurre allora a 0 l'errore di troncamento dei valori shiftati bisognerebbe aggiungere 21 bit meno significativi. Questo ovviamente non è in alcun modo fattibile. Ciò che veramente ci interessa in quanto progettisti è che l'errore dovuto al troncamento non vada ad influenzare pesantemente il risultato, cioè che non faccia cambiare il segno della parte immaginaria del numero complesso (facendo sbagliare il verso dell'angolo) o che non riduca a 0 la parte immaginaria stessa quando in realtà non sarebbe tale.

Per capire quanto effettivamente l'errore di troncamento dei valori shiftati va a pesare sul risultato, è stato scritto un semplice algoritmo in linguaggio C++ capace di provare in pochi secondi tutte le possibili

20 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

combinazioni di ingressi a 12 bit (tranne ovviamente quelle particolari per num o den pari a 0) e di dare per ogni coppia il risultato ottenuto usando un numero di bit meno significativi aggiuntivi passato come parametro all'algoritmo. L'uscita dell'algoritmo viene filtrata mostrando soltanto il dato quando effettivamente l'errore di troncamento supera una soglia data come secondo parametro all'algoritmo.

L'errore di troncamento (separato da altri errori propri dell'algoritmo o dovuti alla ROM) è calcolato eseguendo in parallelo il calcolo con gli interi (situazione che emula quella nel circuito) e con i reali (tipo "double" del C). Il risultato ottenuto nel caso dei double ha un errore di troncamento (infatti sono sempre rappresentati su un numero finito di bit nel calcolatore) però sicuramente esso risulta trascurabile e non può originare errori di segno nell'algoritmo CORDIC. Al termine dell'esecuzione, l'eseguibile restituisce anche il massimo errore commesso (in gradi).

Il codice che è stato scritto a questo proposito è riportato sotto.

/**

* >> Underflow CORDIC error calculator <<

*

* Compiler "g++ version 4.4.2"

* Authors Giovanni Accongiagioco, Simone Brienza, Marco Camilli, Daniele Giannetti

* Date 12/17/2009

*

*

* This program calculates the maximum error committed using a

* given number of additional least significant bits.

* It considers all possible x and y values on 12 bits 1-complement representation

* (from -2047 to 2047). It accepts the following parameters:

* @param argv[1] Number of additional LSBs (Number of wires to add in the circuit)

* @param argv[2] Maximum error permitted (The program prints all

* x,y couples that experiment an error greater than the given one)

* It returns the number of errors and the maximum error reported among all values.

**/

#include <cstdlib>

#include <iostream>

#include <math.h>

using namespace std;

#define PI 3.14159265

int main(int argc, char* argv[]) {

21 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

int pow2[8] = {1,2,4,8,16,32,64,128};

// atan values are stored in a 8x12 rom. To improve accuracy

// using integers the values are multiplied by 64

int atandisc[8] = {2880, 1700, 898, 456, 229, 115, 57, 29};

// 2^-i atan values (i from 0 to 7)

double atanreal[8] = {45.00000, 26.56505, 14.03624, 7.12502, 3.57633, 1.78991, 0.89517, 0.44761};

int x[9]; double dx[9];

int y[9]; double dy[9];

int z[9]; double dz[9];

z[0] = 0; dz[0] = 0;

int iter1, iter2;

int scale = pow(2,atoi(argv[1])); //number of bits at right

double max_error = atof(argv[2]);

double max_enc_error = 0.0;

double temp;

int numerror=0;

/**

* These two for() generate all x,y possible values

**/

for(int f=1; f<2048; f++) {

x[0]=dx[0]=scale*f;

for(int j=-2047; j<2048; j++) {

if (j == 0) continue;

y[0]=dy[0]=scale*j;

iter1=iter2=0;

/**

* Cordic with integers (effective)

**/

for (int i = 0; i < 8; i++) {

if(y[i]!=0) iter1=i;

if (y[i] < 0) {

x[i+1] = x[i] - y[i]/pow2[i];

y[i+1] = y[i] + x[i]/pow2[i];

z[i+1] = z[i] - atandisc[i];

22 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

} else if (y[i] > 0) {

x[i+1] = x[i] + y[i]/pow2[i];

y[i+1] = y[i] - x[i]/pow2[i];

z[i+1] = z[i] + atandisc[i];

}

else {

z[8] = z[i];

break;

}

}

/**

* Cordic with doubles (accurate)

**/

for (int i = 0; i < 8; i++) {

if(dy[i]!=0) iter2=i;

if (dy[i] < 0){

dx[i+1] = dx[i] - dy[i]/pow2[i];

dy[i+1] = dy[i] + dx[i]/pow2[i];

dz[i+1] = dz[i] - atanreal[i];

} else if (dy[i] > 0){

dx[i+1] = dx[i] + dy[i]/pow2[i];

dy[i+1] = dy[i] - dx[i]/pow2[i];

dz[i+1] = dz[i] + atanreal[i];

} else {

dz[8] = dz[i];

break;

}

}

temp=fabs(double(z[8])/64.0-dz[8]);

max_enc_error = temp > max_enc_error ? temp : max_enc_error;

if(temp > max_error){

cout<< "x = " << dx[0] << ", y = " << dy[0] <<", z = "

<< double(z[8])/64.0 << " iter1 = " << iter1 << ", dz = "

<< dz[8] << ", iter2 = " << iter2 << endl;

numerror++;

}

}

}

23 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

cout << endl << "Number of errors: "<< numerror << endl

<< "Final maximum error: " << max_enc_error << endl;

return 0;

Facendo varie prove con il semplice programma C++ di cui sopra, abbiamo visto che l'errore di troncamento, qualora non si metta alcun bit meno significativo aggiuntivo, supera i 3 gradi nel caso peggiore: questo errore è inaccettabilmente grande. Aggiungendo dei bit si è visto che l'errore massimo cala, tuttavia non è banale capire quale sia la sua legge. In particolare, si hanno dei valori significativi dove l'errore subisce un calo netto, si è allora deciso di usare uno di questi valori. Evidenziamo comunque che l'algoritmo di cui sopra funziona al massimo con 18 bit aggiuntivi, aggiungendone degli altri i risultati non sono più significativi in quanto si supera la rappresentazione dei dati in memoria del compilatore che si è utilizzato.

Si è allora deciso di utilizzare 4 bit meno significativi aggiuntivi, per un errore di troncamento massimo totale dato da 0.935227 gradi.

E' fondamentale capire che tutte queste simulazioni sono state fatte per ingressi su 12 bit, per capire quale sarebbe il numero di bit aggiuntivi ideale per diversi numeri di bit di ingresso bisognerebbe ripetere le simulazioni. Per questo il circuito finale è stato realizzato usando opportuni valori di h, k ed M, anche se ovviamente una sua configurazione per altre applicazioni è banale una volta stabilito il numero di bit aggiuntivi (in quanto i componenti sono stati fatti in modo generico, e l'unica cosa da cambiare è il file VHDL che assembla i componenti stessi).

Un altro aspetto di fondamentale importanza per il funzionamento del circuito è però la necessita (una volta prelevati gli ingressi) di aggiungere dei bit più significativi nel sommatore in modo tale da riuscire a contenere i valori sempre crescenti della parte reale anche qualora l'utente fornisca i massimi numeri possibili in ingresso.

Notiamo che la parte reale aumenta sempre, e alla prima iterazione viene dunque sempre sommata alla parte immaginaria in valore assoluto. Se la parte immaginaria è il massimo numero possibile in modulo (2047) o simili e se la parte reale allo stesso modo è il massimo numero possibile (2047) o simili, allora:

• Alla prima iterazione la parte reale raddoppia

• Alle iterazioni successive alla prima la parte reale continua a salire, idealmente se y rimanesse costante allora ad ogni iterazione si aggiungerebbe la metà di quanto aggiunto all'iterazione precedente.

E' allora ovvio che, pur crescendo, la parte reale non raggiungerà mai 4 volte il valore originale. Questo significa che si può ovviare al problema dell'aumento aggiungendo (oltre a 4 bit come bit meno significativi) anche 2 nuovi bit più significativi quando si prelevano gli ingressi.

I componenti come sommatori, shifter e registri lavorano allora non su 12 bit, ma su 18 bit (12+4+2), ed ecco perché abbiamo fissato M pari a 18.

24 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Questi bit aggiuntivi sono leggermente sprecati quando si usano per operare sulle uscite della ROM, tuttavia questo compromesso è sicuramente meno costoso in quanto comporta il riutilizzo di un componente già fatto.

Come ultimo aspetto evidenziamo che l'uscita è fornita su 12 bit in virgola fissa e in complemento ad 1. Notiamo inoltre che il massimo valore che dobbiamo essere in grado di esprimere in uscita (sia positivo che negativo) è superiore a 64 in valore assoluto (90 gradi): questo significa che vi devono essere sufficienti bit di parte intera per esprimere il numero 90 (almeno 7 bit). E' inoltre necessario un bit per il segno, e dunque rimangono solamente 4 bit per la parte frazionaria del numero in uscita. In questo senso l’uscita perde due bit di precisione rispetto ai valori memorizzati in ROM: uno per il segno e l’altro perché la parte intera può essere più grande (nella ROM il massimo era di 45).

Dunque, dopo aver effettuato tutto il conto, il blocco controller si occupa di rimuovere i bit di testa (preservando il segno), due dei 6 bit frazionari presenti nella cumulata finale in coda, e quindi mandare in uscita il risultato così ottenuto prima di dare il segnale valid.

25 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

3. CODICE VHDL [12] [13]

Si riporta il codice VHDL utilizzato e/o realizzato per la descrizione dell’architettura proposta, nel seguente ordine:

inv.vhd descrive il circuito precedentemente indicato come CORDIC_INV;

cordic_adder.vhd descrive il circuito precedentemente indicato come CORDIC_ADDER;

adder.vhd descrive un N bit Ripple Carry Adder;

dff.vhd descrive un D Edged Triggered Flip Flop – Synchronous Reset;

barrel_shifter descrive un barrel shifter su 18 bit con 3 bit di comando (shift massimo di 7 posizioni);

rom.vhd descrive una ROM 8x12 contenente i valori dell’arcotangente di 2-i (con i da 0 a 7);

CORDIC.vhd descrive globalmente il circuito che realizza l’algoritmo; in particolare contiene la descrizione del circuito precedentemente indicato come CONTROLLER.

Per quanto concerne i codici contenuti nei file adder.vhd e dff.vhd (non di nostra realizzazione), questi sono stati impiegati e trascritti verbatim, così come ci sono stati forniti.

INV

inv.vhd

----------------------------------------------------------------------------------- Title : Inv-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza -- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file describes a circuit that gives as output either a -- copy of the input "yin" or its negation "NOT yin". The description of the-- multiplexer is behavioural.--------------------------------------------------------------------------------- LIBRARY IEEE;USE IEEE.std_logic_1164.all;

ENTITY inv is generic (N : INTEGER:=18); port( yin : in std_logic_VECTOR (N-1 downto 0); xin : in std_logic; yout : out std_logic_VECTOR (N-1 downto 0) );END inv;

architecture BEHAVIOURAL of inv is

BEGIN inv:process(xin,yin) begin IF (xin = '1') THEN yout <= NOT yin; else yout <= yin; END IF; END process inv;

END BEHAVIOURAL;

Simon Bryanz
Font monospazio
26

CORDIC_ADDER

cordic_adder.vhd

----------------------------------------------------------------------------------- Title : cordic_adder-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza -- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file describes this circuit: -- -- | sample clock-- _v_ | ___-- ingress1 | | ___ | | |-- ------>|\1 | | | -->| |-- | \_|------->| + |------->|DFF|---o-----> egress-- | / | |___| | | |-- --->|/0 | | |___| _|-- | |___| ----------------|_------< ingress2-- |_____________________________________|-- -- -- Fundamentally it does two things:-- -> If sample is high grabs input from ingress1. In this case we must ensure -- that input from ingress2 is clean to properly store data in the DFF-- -> Otherwise sums input from ingress2 with previous output, thus giving a-- a new output.---------------------------------------------------------------------------------

LIBRARY IEEE;USE IEEE.std_logic_1164.all;

ENTITY cordic_adder is generic (N : INTEGER:=18); port( ingress1 : in std_logic_VECTOR (N-1 downto 0); ingress2 : in std_logic_VECTOR (N-1 downto 0); egress : out std_logic_VECTOR (N-1 downto 0); clk : in std_logic; sample : in std_logic; reset : in std_logic );END cordic_adder;

architecture str of cordic_adder is

component dff is -- flip flop for old data storage port(d,clk,reset : in std_logic ; q : out std_logic); end component dff; component adder is -- ripple carry adder port(a,b : in std_logic_VECTOR (N-1 downto 0);carry_in : in std_logic ;s : out

std_logic_VECTOR (N-1 downto 0); carry_out : out std_logic ); end component adder;

signal after_multi: std_logic_vector (N-1 downto 0); --local signalsignal after_dff: std_logic_vector (N-1 downto 0); --local signalsignal after_adder: std_logic_vector (N-1 downto 0); --local signalsignal carry :std_logic; --local signal

BEGIN

Simon Bryanz
Font monospazio
27

CORDIC_ADDER

cordic_adder.vhd

GENDFF: for i in 0 to N-1 generate DFF: dff port map(after_adder(i),clk,reset,after_dff(i)); end generate GENDFF; ADD: adder port map(after_multi,ingress2,carry,after_adder,carry);

MULTI:process(ingress1,after_dff,sample) begin IF (sample = '1') THEN after_multi <= ingress1; else after_multi <= after_dff; END IF; END process MULTI; egress <= after_dff; END str;

Simon Bryanz
Font monospazio
28

ADDER

adder.vhd

--------------------------------------------------------------------------------- (Behavioral)---- File name : adder.vhdl-- Purpose : N bit Ripple Carry Adder-- :-- Library : IEEE-- Author(s) : Luca Fanucci-- Copyrigth : CSMDR-CNR 2001. No part may be reproduced-- : in any form without the prior written permission by CNR.---- Simulator : Synopsys VSS v. 1999.10, on SUN Solaris 8--------------------------------------------------------------------------------- Revision List-- Version Author Date Changes---- 1.0 LFanu 24 April 2001 New version-------------------------------------------------------------------------------

LIBRARY IEEE;USE IEEE.std_logic_1164.all;

ENTITY adder is

generic (N : INTEGER:=18); port( a : in std_logic_VECTOR (N-1 downto 0); b : in std_logic_VECTOR (N-1 downto 0); carry_in : in std_logic; s : out std_logic_VECTOR (N-1 downto 0); carry_out : out std_logic);END adder;

architecture BEHAVIOURAL of adder is

BEGIN SUM:process(a,b,carry_in) variable C:std_logic; begin C:=carry_in; FOR i IN 0 TO N-1 LOOP -- Calculate bit sum using carry from previous step, then carry out s(i)<= a(i) XOR b(i) XOR C; C:= (a(i) AND b(i)) OR (a(i) AND C) OR (b(i) AND C); END LOOP; carry_out <= C; END process SUM;

END BEHAVIOURAL;

Simon Bryanz
Font monospazio
29

DFF

dff.vhd

--------------------------------------------------------------------------------- (Behavioral)---- File name : dff.vhdl-- Purpose : D Edged Triggered Flip Flop - Synchronous Reset-- :-- Library : IEEE-- Author(s) : Luca Fanucci-- Copyrigth : CSMDR-CNR 2001. No part may be reproduced-- : in any form without the prior written permission by CNR.---- Simulator : Synopsys VSS v. 1999.10, on SUN Solaris 8--------------------------------------------------------------------------------- Revision List-- Version Author Date Changes---- 1.0 LFanu 24 April 2001 New version-------------------------------------------------------------------------------

LIBRARY IEEE;USE IEEE.std_logic_1164.all;

ENTITY dff is

port( d : in std_logic; clk : in std_logic; reset : in std_logic; q : out std_logic);END dff;

architecture BEHAVIOURAL of dff is

BEGIN DFF:process(clk) begin IF (clk'EVENT AND clk='1') THEN q <= reset AND d; END IF; END process DFF;

END BEHAVIOURAL;

Simon Bryanz
Font monospazio
30

BARREL_SHIFTER

barrel_shifter.vhd

----------------------------------------------------------------------------------- Title : Barrel_shifter-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza -- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file describes the structure of a barrel shifter using-- a fully behavioural architecture. It returns as output the input signal-- shifted of loc_num positions to the right. The control signal loc_num is-- used in the following manner to perform the right number of shifts:-- -> if the first bit is high enables the "shift-by-one"-- -> if the first bit is high enables the "shift-by-two"-- -> if the first bit is high enables the "shift-by-four"---- So, for example, if the shift is "by-five" loc_num=101, so the circuit-- enables the "shift-by-one" and "shift-by-four" to perform the operation.---------------------------------------------------------------------------------

library IEEE;use IEEE.STD_LOGIC_1164.all;

entity barrel_shifter is generic (N : INTEGER:=18); port( input : in std_logic_vector (N-1 downto 0); output : out std_logic_vector (N-1 downto 0); loc_num : in std_logic_vector (2 downto 0) );end barrel_shifter;

architecture behavioural of barrel_shifter is signal con1, con2: STD_LOGIC_VECTOR (N-1 downto 0);begin shift_one : process(input,loc_num(0)) begin if(loc_num(0)='1') then -- shift-by-one con1(N-1) <= input(N-1); con1(N-2 downto 0) <= input(N-1 downto 1); else con1(N-1 downto 0) <= input(N-1 downto 0); end if; end process; shift_two : process(con1,loc_num(1)) begin if(loc_num(1)='1') then -- shift-by-two con2(N-1) <= con1(N-1); con2(N-2) <= con1(N-1); con2(N-3 downto 0) <= con1(N-1 downto 2); else con2(N-1 downto 0) <= con1(N-1 downto 0); end if; end process; shift_four : process(con2,loc_num(2)) begin if(loc_num(2)='1') then -- shift-by-four output(N-1) <= con2(N-1); output(N-2) <= con2(N-1); output(N-3) <= con2(N-1);

Simon Bryanz
Font monospazio
31

BARREL_SHIFTER

barrel_shifter.vhd

output(N-4) <= con2(N-1); output(N-5 downto 0) <= con2(N-1 downto 4); else output(N-1 downto 0) <= con2(N-1 downto 0); end if; end process;end behavioural;

Simon Bryanz
Font monospazio
32

ROM

rom.vhd

----------------------------------------------------------------------------------- Title : rom 8x12-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza,-- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file describes the rom that will be used to hold-- the arctan(2^-i) samples, where i goes from 0 to 7. The values are -- represented by positive fixed point reals where the last 6 bits are the-- fractional part and the first 6 bits the integer part (sign is always positive)-- The memory is presented through a simple structural description.---------------------------------------------------------------------------------

library IEEE;use IEEE.STD_LOGIC_1164.all;use ieee.std_logic_unsigned.all;

entity rom is port( addr : in STD_LOGIC_VECTOR(2 downto 0); dout : out STD_LOGIC_VECTOR(11 downto 0) );end rom;

architecture structural of rom is constant DIMROM: natural := 8;constant DIMWORD: natural := 12; type ROM_IMAGE is array (natural range 0 to DIMROM-1) of STD_LOGIC_VECTOR(DIMWORD-1 downto 0);

constant ROM: ROM_IMAGE :=( -- INTEGER values -- FRACTIONAL values0 =>X"B40", -- 2880 -- 451 =>X"6A4", -- 1700 -- 26,5622 =>X"382", -- 898 -- 14,0313 =>X"1C8", -- 456 -- 7,1254 =>X"0E5", -- 229 -- 3,5785 =>X"073", -- 115 -- 1,7966 =>X"039", -- 57 -- 0,8907 =>X"01D" -- 29 -- 0,453); begin dout <= ROM(conv_integer(addr));end structural;

Simon Bryanz
Font monospazio
33

CORDIC

CORDIC.vhd

----------------------------------------------------------------------------------- Title : CORDIC-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza -- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file describes a circuit that executes the CORDIC -- algorithm for the calculation of arctan. Look at the documentation for-- the logical diagram. -- Inputs are on 12 bits and codified in one's complementary.-- The output is on 12 bits and codified in one's complementary, where -- the less significant four bits represent decimals. -- Eg. 90 degrees == 1440 / 2^4---------------------------------------------------------------------------------

LIBRARY IEEE;USE IEEE.std_logic_1164.all;USE IEEE.numeric_std.all;

ENTITY CORDIC is port( cordic_ingress1 : in std_logic_VECTOR (11 downto 0); -- den cordic_ingress2 : in std_logic_VECTOR (11 downto 0); -- num cordic_egress : out std_logic_VECTOR (11 downto 0); -- ris cordic_clk : in std_logic; cordic_sample : in std_logic; cordic_valid : out std_logic );END CORDIC;

architecture str of CORDIC is

component inv is -- CORDIC_INV port( yin : in std_logic_VECTOR (17 downto 0); xin : in std_logic; yout : out std_logic_VECTOR (17 downto 0) );end component inv;

component cordic_adder is -- CORDIC_ADDER port( ingress1 : in std_logic_VECTOR (17 downto 0); ingress2 : in std_logic_VECTOR (17 downto 0); egress : out std_logic_VECTOR (17 downto 0); clk : in std_logic; sample : in std_logic; reset : in std_logic );end component cordic_adder;

component barrel_shifter is -- SHIFTER port ( input : in std_logic_VECTOR(17 downto 0); output : out std_logic_VECTOR(17 downto 0); loc_num : in std_logic_VECTOR(2 downto 0) );end component barrel_shifter;

Simon Bryanz
Font monospazio
34

CORDIC

CORDIC.vhd

component rom is -- ROM port( addr : in std_logic_VECTOR(2 downto 0); dout : out std_logic_VECTOR(11 downto 0) );end component rom;

-- The following signals connect the blocks specified inside their namesignal inv1_cordic_adder1: std_logic_VECTOR (17 downto 0); signal inv2_cordic_adder2: std_logic_VECTOR (17 downto 0); signal signx :std_logic; signal clock :std_logic;signal signy :std_logic;signal notsigny :std_logic;signal cordic_adder1_shifter1: std_logic_VECTOR (17 downto 0); signal shifter1_inv4: std_logic_VECTOR (17 downto 0); signal inv4_cordic_adder2: std_logic_VECTOR (17 downto 0); signal cordic_adder2_shifter2: std_logic_VECTOR (17 downto 0); signal shifter2_inv3: std_logic_VECTOR (17 downto 0); signal inv3_cordic_adder1: std_logic_VECTOR (17 downto 0); signal cordic_adder3_out: std_logic_VECTOR (17 downto 0); signal inv5_cordic_adder3: std_logic_VECTOR (17 downto 0); signal inv5_cordic_adder3_2: std_logic_VECTOR (17 downto 0); signal rom_inv5: std_logic_VECTOR (17 downto 0); signal numshift: std_logic_VECTOR (2 downto 0); signal ingr1I: std_logic_VECTOR (17 downto 0); signal ingr2I: std_logic_VECTOR (17 downto 0); signal inv3_cordic_adder1_2: std_logic_VECTOR (17 downto 0); signal inv4_cordic_adder2_2: std_logic_VECTOR (17 downto 0);

BEGIN

-- for further informations about port mapping refer to the scheme-- inside paragraph 2.1 of the attached document

INV1: inv port map(ingr1I,signx,inv1_cordic_adder1); INV2: inv port map(ingr2I,signx,inv2_cordic_adder2); INV3: inv port map(shifter2_inv3,signy,inv3_cordic_adder1); INV4: inv port map(shifter1_inv4,notsigny,inv4_cordic_adder2); INV5: inv port map(rom_inv5,signy,inv5_cordic_adder3); CORDIC_ADDER1: cordic_adder port

map(inv1_cordic_adder1,inv3_cordic_adder1_2,cordic_adder1_shifter1,clock,cordic_sample,'1'); CORDIC_ADDER2: cordic_adder port

map(inv2_cordic_adder2,inv4_cordic_adder2_2,cordic_adder2_shifter2,clock,cordic_sample,'1'); CORDIC_ADDER3: cordic_adder port

map(B"000000000000000000",inv5_cordic_adder3_2,cordic_adder3_out,clock,cordic_sample,'1'); SHIFTER1: barrel_shifter port map(cordic_adder1_shifter1,shifter1_inv4,numshift); SHIFTER2: barrel_shifter port map(cordic_adder2_shifter2,shifter2_inv3,numshift); ROM1: rom port map(numshift,rom_inv5(11 downto 0));

-- All the 3 multiplexers below are used to keep ingress2-- of the cordic_adders clean when the user provides input-- through the cordic_sample signal

-- Multiplexer between inv3 and cordic_adder1 MULT1:process(cordic_sample,inv3_cordic_adder1) begin if(cordic_sample = '1') then inv3_cordic_adder1_2 <= (others => '0'); else

Simon Bryanz
Font monospazio
35

CORDIC

CORDIC.vhd

inv3_cordic_adder1_2 <= inv3_cordic_adder1; end if; end process MULT1;

-- Multiplexer between inv4 and cordic_adder2 MULT2:process(cordic_sample,inv4_cordic_adder2) begin if(cordic_sample = '1') then inv4_cordic_adder2_2 <= (others => '0'); else inv4_cordic_adder2_2 <= inv4_cordic_adder2; end if; end process MULT2; -- Multiplexer between inv5 and cordic_adder3 MULT3:process(cordic_sample,inv5_cordic_adder3) begin if(cordic_sample = '1') then inv5_cordic_adder3_2 <= (others => '0'); else inv5_cordic_adder3_2 <= inv5_cordic_adder3; end if; end process MULT3; -- Controller circuit: it controls iterations deciding when to return the result. CONTROLLER:process(cordic_clk) variable start:std_logic := '0'; -- this variable estabilishes the beginning of elaboration variable counter: integer range 0 to 15; -- is used to count the number of iterations begin -- Enter here only on positive edge of the clock if (cordic_clk = '1') then -- sample is used to communicate that the user has given valid input values if (cordic_sample ='1') then -- if num is equal to +0 or -0 then return directly 0 if (cordic_ingress2 = B"000000000000" or cordic_ingress2 = B"111111111111") then start := '0'; cordic_egress <= B"000000000000"; cordic_valid <= '1'; -- if den is equal to +0 or -0 then return directly +90 or -90 according to the sign of

num else if (cordic_ingress1 = B"000000000000" or cordic_ingress1 = B"111111111111") then start := '0'; if(cordic_ingress2(11) = '0') then cordic_egress <= B"010110100000"; -- 1440 == +90 degrees cordic_valid <= '1'; else cordic_egress <= B"101001011111"; -- -1440 == -90 degrees cordic_valid <= '1'; end if; else -- (sample ='1' and den != B"000000000000" and num != B"000000000000" ) then

start iterating start := '1'; counter := 0; cordic_valid <= '0'; -- To access ROM correctly and control shifters numshift <=std_logic_vector(to_unsigned(counter,3)); clock <= cordic_clk; end if; end if; -- sample is zero and start is one else if ( start = '1' and counter < 8) then -- The result has been found (num == 0)

Simon Bryanz
Font monospazio
36

CORDIC

CORDIC.vhd

if ( cordic_adder2_shifter2 = B"111111111111111111" or cordic_adder2_shifter2 = B"000000000000000000") then counter := 8; else -- Result not found yet: do one more iteration (max 8) counter := counter +1; clock <= cordic_clk; numshift <= std_logic_vector(to_unsigned(counter,3)); end if; else if ( start = '1') then -- Iterations finished: return the result start := '0'; counter := 0;

-- Result is manipuleted before sending to output by-- cutting last two bits

cordic_egress (10 downto 0) <= cordic_adder3_out(12 downto 2); cordic_egress (11) <= cordic_adder3_out(17); cordic_valid <= '1'; end if; end if; end if; -- Enter here only on negative edge of the clock else clock <= cordic_clk; end if; end process CONTROLLER; -- Extract signs of num and den: those are used

-- to control the cordic_invs signx <= signy <= cordic_adder2_shifter2(17); notsigny <= NOT signy; -- Extend den from 12 to 18 wires (bits) ingr1I(15 downto 4) <= cordic_ingress1; ingr1I(17) <= cordic_ingress1(11); ingr1I(16) <= cordic_ingress1(11); ingr1I(3) <= cordic_ingress1(11); ingr1I(2) <= cordic_ingress1(11); ingr1I(1) <= cordic_ingress1(11); ingr1I(0) <= cordic_ingress1(11); -- Extend num from 12 to 18 wires (bits) ingr2I(15 downto 4) <= cordic_ingress2; ingr2I(17) <= cordic_ingress2(11); ingr2I(16) <= cordic_ingress2(11); ingr2I(3) <= cordic_ingress2(11); ingr2I(2) <= cordic_ingress2(11); ingr2I(1) <= cordic_ingress2(11); ingr2I(0) <= cordic_ingress2(11); -- Extend rom output from 12 to 18 wires (bits) rom_inv5(17 downto 12) <= B"000000"; END str;

cordic_ingress1(11);

Simon Bryanz
Font monospazio
37

38 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Per verificare la correttezza dei risultati forniti dal modulo progettato, si è scritto del codice VHDL che permettesse di simulare il pilotaggio del componente CORDIC.

Nell’ambiente realizzato, forniamo in ingresso, al solito, due numeri interi su 12 bit indicati col nome ingress_X e ingress_Y che rappresentano rispettivamente den e num nell’architettura presentata. Gli altri ingressi (segnali di clock e sample) vengono opportunamente pilotati, come descritto in precedenza, in modo da garantire il funzionamento del circuito. Il file di test bench proposto permette di settare a piacimento i valori dei due interi e di controllare pertanto le uscite restituite dal modulo; in particolare l’uscita egress_Z, ancora su 12 bit, dovrebbe contenere, a meno di errori, il risultato cercato. Si presentano adesso alcuni risultati ottenuti nella fase di testing del circuito, mostrando i valori di den e num dati in input e l’output ris ottenuto; i valori sono stati scelti in modo da coprire approssimativamente tutti i casi d’uso possibili. Il funzionamento del modulo CORDIC è stato verificato per:

4. TEST BENCH [12] [13]

• den o num uguale a 0;

• den e num uguali a 0 (0+ e 0-);

• den e num discordi;

• den e num concordi (sia positivi che negativi);

• den e num prossimi al valore minimo in modulo (0), al valore massimo in modulo (±2047) e per valori intermedi.

Si ricorda che la rappresentazione degli interi è in complemento ad uno; questo comporta una doppia rappresentazione dello 0 (come ‘000000000000’ e ‘111111111111’) e un range di rappresentazione che spazia da -212-1 a +212

-1 (il bit più significativo individua il segno). Inoltre l’uscita egress_Z, deve essere divisa per 16 per ottenere l’angolo cercato, espresso in gradi; questo perché, come già illustrato, ris è un numero su 12 bit, i cui 4 bit meno significativi sono impiegati per memorizzare le cifre decimali del risultato secondo una memorizzazione a virgola fissa (dunque è necessario effettuare uno shift di 4 posizioni).

Si tenga presente che il risultato è considerato corretto se, per motivi già presentati, la distanza dal valore reale è inferiore o uguale a 0.935227 gradi.

39 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Tabella 1 - Risultati della fase di testing

den num ris angolo ottenuto

errore

0 2 1440 90 0 Divisione per zero: angolo retto 0 -1 -1440 -90 0 2 0 0 0 0 Tangente pari a zero: angolo nullo -1 0 0 0 0 2 2 720 45 0 Il risultato è esatto perché alla prima iterazione 2 -2 -720 -45 0 usciamo e l’angolo di 45 ° è memorizzato nella ROM 2 1 412 25.75 0.8151 2 -1 -412 -25.75 0.8151 3 -1 -295 -18.4375 0.0026 3 1 295 18.4375 0.0026 3 2 526 32.875 0.8151 3 512 1433 89.5625 0.1018

35 512 1376 86 0.0894 -35 512 -1376 -86 0.0894 -35 -512 1376 86 0.0894

-2047 1 -6 -0.375 0.3470 2047 1 6 0.375 0.3470 2047 2047 720 45 0

1 2047 1433 89.5625 0.4095 1 -2047 -1433 -89.5625 0.4095

0+ 0+ 0 0 0 Se entrambi gli ingressi sono nulli restituiamo 0- 0- 0 0 0 un’uscita nulla

-680 1582 -1070 0.1348 Caso d’esempio nel codice di test bench Di seguito riportiamo il codice VHDL usato per il test bench.

CORDIC TESTBENCH

CORDIC_tb.vhd

----------------------------------------------------------------------------------- Title : CORDIC_testbench-- Design : cordic_arctan-- Author : Giovanni Accongiagioco, Simone Brienza -- Marco Camilli, Daniele Giannetti------------------------------------------------------------------------------------- Description : This file is a test for the CORDIC module---------------------------------------------------------------------------------

library IEEE; use IEEE.std_logic_1164.all;

entity CORDIC_tb isend entity CORDIC_tb;

architecture CORDIC_test of CORDIC_tb is

component CORDIC is port( cordic_ingress1 : in std_logic_VECTOR (11 downto 0); cordic_ingress2 : in std_logic_VECTOR (11 downto 0); cordic_egress : out std_logic_VECTOR (11 downto 0); cordic_clk : in std_logic; cordic_sample : in std_logic; cordic_valid : out std_logic );end component CORDIC;

signal ingress_X : std_logic_vector (11 downto 0) := B"110101010111"; -- WRITE HERE THE VALUE FOR DENsignal ingress_Y : std_logic_vector (11 downto 0) := B"011000101110"; -- WRITE HERE THE VALUE FOR NUMsignal egress_Z : std_logic_vector (11 downto 0); signal clock : std_logic := '0';signal req_sample : std_logic := '0'; signal egress_valid : std_logic;

begin BS1: CORDIC port map(ingress_X,ingress_Y,egress_Z,clock,req_sample,egress_valid); clock <= NOT clock after 100ns; req_sample <= '1' after 200ns,'0' after 600ns ; end architecture CORDIC_test;

Simon Bryanz
Font monospazio
40

41 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

L’ algoritmo CORDIC in passato veniva spesso implementato a livello hardware grazie alla sua semplicità, visto che per il calcolo di funzioni trigonometriche ed iperboliche richiede solo l’uso di sommatori e shifter. Il prezzo da pagare è ovviamente nel fatto che i risultati forniti hanno un certo grado di approssimazione.

Va osservato che l’approssimazione maggiore si deve al troncamento sulle quantità x ed y manipolate durante l’algoritmo, difatti si può far vedere tramite dei test che lavorando su 32 bit (anziché i 12 forniti) l’errore in uscita si riduce. In questo caso anziché estendere l’ingresso aggiungendo dei bit meno significativi è possibile utilizzare un approccio alternativo, leggermente più complesso dal punto di vista implementativo: i valori in ingresso (su 32 bit) vengono shiftati a sinistra quanto più possibile. In pratica si moltiplica per 2 sia num che den finché non diventano i più grandi possibile (tenendo ovviamente conto dei vincoli di overflow): in questo modo si cerca di minimizzare i problemi di underflow che si verificano durante l’esecuzione dell’algoritmo a causa dei numerosi shift a destra.

Il file C++ mostrato in precedenza può essere modificato inserendo il seguente codice subito sopra la sezione “cordic with integers” per svolgere quanto sopra:

x[0] = dx[0];

while(( y[0] < pow(2,28) ) && ( -y[0] < (pow(2,28) ) && ( x[0] < pow(2,28) ) {

x[0] *= 2; y[0] *= 2;

}

Il programma può a questo punto essere eseguito avendo cura di impostare come primo parametro il valore 0 (numero di LSB da aggiungere). In questo caso i test mostrano che l’errore è inferiore a 0.027 gradi, tuttavia sono stati eseguiti solo su un set limitato di ingressi (per problemi di tempo di simulazione, visto che stavolta ci sarebbero potenzialmente oltre ingressi possibili).

Si tenga comunque conto che quando gli ingressi diventano particolarmente “grandi”, gli shift preventivi a sinistra che è possibile fare prima di andare in overflow saranno pochi, insufficienti ad evitare i troncamenti nella successiva esecuzione dell’ algoritmo, e quindi gli errori precedentemente evidenziati tornano ad affliggerci.

Oggigiorno la disponibilità di moltiplicatori all’interno dei DSP, fa spesso preferire l’uso di algoritmi di approssimazione polinomiale piuttosto che del CORDIC per il calcolo delle funzioni trigonometriche.

5. CONCLUSIONI

42 Progettazione di una rete di calcolo dell'arcotangente basata su CORDIC

Riferimenti

[1] A survey of Cordic algorithms for FPGA based computers, Ray Andraka - http://www.andraka.com

[2] Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/CORDIC

[3] Arctan(x) using cordic - http://www.convict.lu/Jeunes/Math/arctan.htm

[4] Cordic Methods - http://www.voidware.com/cordic.htm

[5] Cordic Alghorithms and Architectures, Sabin H. Gerez –

http://wwwhome.cs.utwente.nl/~gerezsh/sendfile/sendfile.php/idsp-cordic.pdf?sendfile=idsp- cordic.pdf

[6] Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Arithmetic_shift

[7] Components and Design Techniques for Digital Systems –

http://www.eecs.berkeley.edu/~newton/Classes/CS150sp98/lectures/week6_2/sld026.htm

[8] Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Barrel_shifter

[9] Source Files by Prof. Luca Fanucci - http://vlsi.iet.unipi.it/~fanucci/ae

[10] Cordic Digital Calculating Apparatus - http://www.freepatentsonline.com/3746849.html

[11] Cordic Reference Design Application Notes - http://www.altera.com/literature/an/an263.pdf

[12] Digital Systems Design using VHDL, Luca Fanucci

[13] The VHDL Cookbook, Peter J. Ashenden