giovedì 1 ottobre 2015

FPGA: Numeri a virgola fissa (prima parte)


Il linguaggio VHDL originariamente non prevede alcun supporto ai numeri decimali, con la ratificazione della versione VHDL-2008 si arricchisce finalmente del supporto per i numeri a virgola fissa ed a virgola mobile.

Sfortunatamente la diffusione del nuovo standard non è rapida e ancora oggi la maggior parte degli strumenti di sviluppo supporta un subset più o meno ampio di tutte le funzionalità.

Quartus e Vivado attualmente non supportano la sintesi dei numeri a virgola fissa (fixed-point) o mobile (floating-point), è però possibile scaricare da https://github.com/FPHDL/fphdl una libreria di supporto che permette di fare da ponte da VHDL-93 a VHDL-2008, permettendone così la sintesi con gli strumenti attuali.

Per poter sintetizzare i fixed point è sufficiente aggiungere al progetto i file fixed_pkg_c.vhdl e fixed_float_types_c.vhdl e dichiarare l'utilizzo del package fixed_pkg tramite

library ieee_proposed;
use ieee_proposed.fixed_pkg.all;

Vediamo in questo articolo un'introduzione ai numeri in virgola fissa e le prestazioni delle principali operazioni aritmetiche.

I numeri a virgola fissa vengono rappresentati stabilendo in una posizione ben definita la virgola, ad esempio è possibile rappresentare il valore 3,25 tramite una stringa di bit composta da 1 bit per il segno, 4 bit per la parte intera e 3 bit per la parte decimale.


00011010 rappresenterà proprio 3,25 in quanto:
- il bit 0 indicherà il segno positivo
- 0011 il valore intero 3 con la classica codifica binaria
- 010 il valore decimale 0.25 in quanto l'unità sarà suddivisa in 3 bit, quindi 2^3=8 valori ed 1/8=0.125 e di conseguenza il valore 010, 2 in codifica binaria, rappresenterà 2*0.125=0.25

Per i numeri a virgola fissa i nuovi tipi introdotti sono sfixed ed ufixed, rispettivamente per i numeri con e senza segno.

I numeri negativi sono rappresentati come usuale in complemento due, invertendo i bit ed aggiungendo 1 al risultato.

Vediamo come dichiarare un segnale di tipo sfixed:

signal x : sfixed (4 downto -3);

Tramite il range possiamo specificare come suddividere la parte intera da quella decimale.
La virgola è situata tra l'indice 0 e -1, avremo quindi 5 bit per la parte intera (compreso il bit di segno) e 3 bit per la parte decimale.

NB: E' importante ricordarsi che la parte intera comprende l'indice 0, quindi la sintassi è più semplicemente: sfixed (INTEG-1 downto -DECIM) dove INTEG è il numero di bit per la parte intera (compreso il bit di segno) e DECIM il numero di bit per la parte decimale.

Vediamo adesso un semplice blocco sommatore:



Dove in particolare, oltre a quanto abbiamo visto, notiamo che la dimensione del risultato z è superiore di un bit rispetto ai due addendi per evitare overflow (ed errori in compilazione).

Vedremo in prossimi articoli ulteriori dettagli sui fixed-point.

Concludo l'articolo con una tabella relativa alle prestazioni dei numeri a virgola fissa con segno, estratte in riferimento ad un dispositivo Cyclone V 5CEFA9F23C8, come quello presente nella scheda BEMICRO CV A9.


NB: L'occupazione di risorse logiche (ALM) è stata misurata con logica puramente asincrona mentre le frequenze massime (in MHz) sono state ottenute registrando gli ingressi e le uscite, ovvero inserendo dei flip-flop per evitare di includere nella misurazione i tempi di propagazione verso i pin esterni e poter utilizzare l'analisi statica di TimeQuest per circuiti sincroni.

I bit sono stati equamente suddivisi tra parte intera+segno e parte decimale, ad esempio se la lunghezza dati in tabella è 8 bit per operando 4 bit rappresentano parte intera e segno e 4 bit la parte decimale. La lunghezza dati del risultata varia in base al tipo di operazione eseguita per non permettere overflow.

Dalla tabella è possibile notare come le prestazioni per somma e moltiplicazione siano in linea con le prestazioni degli interi.

I blocchi DSP della FPGA in oggetto non permettono un trattamento di dati di dimensione 32x32, per questo motivo per tale operazione sono stati utilizzati più blocchi DSP.

La divisione rappresenta sicuramente la nota dolente, in quanto è sintetizzata inferendo l'IP LPM_DIVIDE senza pipeline, per prestazioni di throughput migliori è consigliabile istanziare manualmente tale blocco specificando una pipeline di profondità adeguata per la propria frequenza target o sfruttare le funzionalità di retiming di sintetizzatori in grado di spostare i flip-flop all'interno della logica di divisione, come ad esempio Synplify.