Scuola Politecnica e delle Scienze di Base
Corso di Laurea in Ingegneria Informatica
Elaborato finale in Calcolatori Elettronici I
Confronto tra architetture di calcolatori
impiegati in sistemi dedicati
Anno Accademico 2014/2015
Candidato:
Gabriella Esposito
Matr. N46/1421
[]
Indice
Indice .................................................................................................................................................. III
Introduzione ......................................................................................................................................... 4
Capitolo 1: Dispositivi FPGA .............................................................................................................. 5
1.1 Introduzione ai dispositivi programmabili ................................................................................. 5
1.2 Tecnologie di FPGA .................................................................................................................. 6
1.2.1 La tecnologia Antifuse ................................................................................................... 8
1.2.2 La tecnologia Flash ........................................................................................................ 9
1.2.3 La tecnologia RAM statica .......................................................................................... 10
1.3 Architettura di un FPGA ..................................................................................................... 11
1.3.1 Blocchi logici configurabili ......................................................................................... 12
1.3.2 Blocchi di ingresso/uscita ............................................................................................ 12
1.3.3 Linee di interconnessione............................................................................................. 12
1.4 FPGA Design e Programming............................................................................................. 13
1.4.1 Design Entry ................................................................................................................ 14
1.4.2 Simulazione.................................................................................................................. 14
1.4.3 Sintesi ........................................................................................................................... 14
1.4.4 Place & Route .............................................................................................................. 15
1.4.5 Programmazione e debug in-circuit ............................................................................. 15
Capitolo 2: Dispositivi GPU .............................................................................................................. 17
2.1 Architettura di un dispositivo GPU.......................................................................................... 18
2.2 Modello GPGPU ...................................................................................................................... 20
2.3 Approcci alla programmazione GP-GPU ................................................................................ 22
Capitolo 3: OpenCL ........................................................................................................................... 23
3.1 Programmazione eterogenea .................................................................................................... 25
3.2 Breve panoramica della specifica di OpenCL.......................................................................... 26
3.3 OpenCL su FPGA .................................................................................................................... 27
Conclusioni ........................................................................................................................................ 33
Bibliografia ........................................................................................................................................ 34
Introduzione
Questa tesi si prefissa l’obiettivo di analizzare e confrontare le strutture di due dispositivi
quali i sistemi FPGA e GPU. Nel primo capitolo verrà analizzata l’architettura ed il
funzionamento di un FPGA introducendo dapprima le sue origini, ovvero l’evoluzione dei
dispositivi programmabili, e descrivendo in seconda battuta le architetture più comuni
presenti sul mercato. Infine, a valle della loro natura altamente programmabile, verrà
illustrato il tipico flusso di design e programmazione di questi device.
Nel secondo capitolo verrà descritta l’architettura di una GPU, in particolare andando a
vedere quali sono state le sue evoluzioni sia tecnologiche che di impiego. D’altronde,
proprio grazie al diverso utilizzo di questi dispositivi, nasce il modello GPGPU illustrato
successivamente. Quindi, nel terzo capitolo, verrà mostrata la nascita e lo sviluppo di un
nuovo linguaggio di programmazione, quale OpenCL, utile nei sistemi eterogenei, i quali
spesso vedono la presenza di un FPGA o di una GPU come un acceleratore di calcolo.
Infatti la nascita di questo standard nuovo ha rivoluzionato l’utilizzo dei dispositivi di
calcolo e ha portato alla nascita di una nuova tecnica di programmazione, chiamata
appunto programmazione eterogenea. L’utilizzo di questo standard in particolare verrà
illustrato sia in un modello generale che prevede spesso la presenza di una GPU, che in
particolare su dispositivi FPGA, illustrando in questo modo le differenze tra queste due
applicazioni.
4
Capitolo 1: Dispositivi FPGA
In questo capitolo verrà fatta una panoramica generale sui dispositivi programmabili e in
particolare sulle tecnologie FPGA, device particolarmente interessanti poiché altamente
prestanti e largamente impiegati nei sistemi embedded moderni.
1.1 Introduzione ai dispositivi programmabili
Sappiamo che alla base dell’elettronica digitale vi sono le porte logiche le quali, se
interconnesse in diverse configurazioni, producono funzioni logiche descritte dalla logica
booleana.
In generale si possono individuare diverse famiglie logiche che a loro volta possono essere
divise in due macro gruppi: le famiglie logiche bipolari (TTL, ECL, I2L) le quali per
realizzare un singolo elemento logico utilizzano i diodi e i transistor a giunzione, e quelle
unipolari (MOS e CMOS) che invece utilizzano i MOSFET (Metal Oxide Semiconductor
Field Effect Transistor).
La nascita dei Dispositivi a Logica Programmabile (PLD) deriva dal forte limite che
impongono le porte logiche per l’implementazione delle funzioni logiche, ovvero la non
riprogrammabilità dei dispositivi prodotti.
Per questo motivo nella seconda metà degli anni ‘60 nascono i primi PLD (Programmable
Logic Device), circuiti integrati programmabili che implementano le stesse funzioni
logiche prodotte dalle porte, ma al momento della fabbricazione esse non sono configurate
per svolgere una specifica funzione. Essi infatti necessitano di essere programmati ad hoc
prima di essere utilizzati in un circuito. Questo tipo di circuito, quindi, rappresenta una
sorta di antenato dei moderni dispositivi FPGA; infatti con l’evoluzione delle tecnologie si
5
è avuta una netta crescita del numero di celle e la possibilità di lasciare all’utente il
compito di programmare questo tipo di dispositivo.
1.2 Tecnologie di FPGA
Un dispositivo FPGA, acronimo di Field Programmable Gate Array, è un circuito
integrato che offre delle funzionalità programmabili tramite software caratterizzato da
un’elevata scalabilità. Esso in sostanza è una matrice di elementi più o meno articolati - i
blocchi logici - e tramite una configurazione di questi ultimi è possibile realizzare una
qualsiasi funzione logica desiderata molto complessa o anche una semplice porta come la
AND o la XOR. I blocchi, inoltre, includono elementi di memoria semplici, ad esempio i
flip-flop, o anche più complessi. Tutti gli elementi sono connessi tra loro e, a loro volta,
queste connessioni possono essere programmate per creare diverse strutture di alto livello.
Questa tipologia di dispositivi è assimilabile, quindi, ai microcontrollori in quanto
riprogrammabili e, nonostante l’architettura resti invariata, le funzionalità implementate
variano. D’altra parte, però, essi si avvicinano anche ai dispositivi ASIC poiché le sopra
citate funzionalità sono realizzate mediante l’interconnessione delle porte logiche, quindi
per via hardware, e non eseguendo uno specifico codice applicativo. Rispetto agli ASIC
però introducono alcuni importanti vantaggi: in primo luogo essi sono dispositivi standard
le cui funzionalità non vengono imposte dal produttore permettendo, quindi, una
produzione più economica e un time to market minore. Tale caratteristica di genericità
permette l’impiego di tali dispositivi in applicazioni consumer, comunicazioni, automotive
eccetera. [1] D’altronde questo vantaggio riguarda anche l’utente finale, il quale avrà il
compito di programmare il circuito, abbattendo così anche i tempi di progettazione,
verifica e prova sul campo. Un ulteriore vantaggio è rappresentato dalla possibilità di
riprogrammare il dispositivo in qualsiasi momento per effettuare modifiche e correggere
gli errori facilmente. Ciò comporta un largo impiego in fase prototipale. Infine l’ambiente
di sviluppo è più user-friendly e facile da apprendere. [2]
Prima di mostrare nel dettaglio quali sono le attuali tecnologie impiegate per la
realizzazione dei circuiti FPGA, passiamo in rassegna quali sono state le sue fasi di
6
evoluzione negli scorsi decenni.
L’industria FPGA nasce dalle memorie programmabili di sola lettura (PROM) e dai
dispositivi logici programmabili (PLD). Entrambi avevano la possibilità di essere
programmati in lotti in fabbrica o “sul campo” (da qui deriva field-programmable).
Tuttavia, la logica programmabile era cablata tra le porte logiche. Alla fine del 1980, la
Naval Surface Warfare Center finanziò un esperimento proposto da Steve Casselman che
prevedeva lo sviluppo di un computer che poteva implementare 600,000 porte
riprogrammabili. Casselman ebbe successo e nel 1992 fu emesso un brevetto relativo al
sistema.
Alcuni dei concetti e tecnologie costituenti l’industria per le reti logiche, porte e blocchi
logici programmabili sono basati sui brevetti assegnati a David W. Page e LuVerne R.
Peterson nel 1985.
Altera fu fondata nel 1983 e consegnò il primo dispositivo logico riprogrammabile del
settore nel 1984 – la EP300 – che prevedeva la presenza di una finestra di quarzo
nell’imballaggio che permetteva agli utenti di irradiare il die con una lampada ultravioletta
per cancellare le celle EPROM che memorizzavano la configurazione del dispositivo.
I cofondatori di Xilinx, Ross Freeman e Bernard Vondershmitt, inventarono nel 1985 il
primo Field Programmable Gate Array commercialmente redditizio – l’XC2046. Esso
aveva sia le porte che le connessioni tra porte programmabili, diede i natali ad una nuova
tecnologia e ad un nuovo mercato. L’XC2064 aveva 64 blocchi logici configurabili
(CLB), con due look-up-table (LUT) a tre ingressi. Più di 20 anni dopo, Freeman entrò
nella National Inventors Hall of Fame per la sua invenzione.
Altera e Xilinx continuarono incontrastati e crebbero rapidamente dal 1985 fino alla metà
del 1990, quando nacquero alcuni competitors, che guadagnarono significative quote di
mercato. Nel 1993 Actel (ora Microsemi) riforniva circa il 18% del mercato. Nel 2010,
Altera (31%), Actel (10%) e Xilinx (36%) insieme rappresentavano circa il 77% del
mercato degli FPGA. [1]
Gli anni ‘90 infatti furono un periodo esplosivo per gli FPGA, sia per quanto riguarda la
sofisticazione che per il volume di produzione. Nei primi anni ’90 vennero utilizzati
7
principalmente nel settore delle telecomunicazioni e del networking ed entro la fine del
decennio trovarono la loro strada anche tra i consumatori e nelle applicazioni industriali.
[3]
Andiamo ora a vedere nel dettaglio quali sono le tecnologie che permettono la
realizzazione di questo tipo di circuiti.
Tre sono le tipologie principalmente utilizzate:

Antifuse

RAM statica

Flash
Le architetture e le funzionalità, in taluni casi, possono anche essere molto differenti.
Esaminiamole una ad una di seguito.
1.2.1 La tecnologia Antifuse
I dispositivi che utilizzano questo tipo di tecnologia implementativa presentano un
numero elevatissimo di moduli logici abbastanza semplici, ma con funzionalità
prestabilite, interconnessi tramite degli antifusibili.
Essi, diversamente dai fusibili, sono di base dei circuiti aperti che si trasformano in
cortocircuiti quando sono attraversati da una corrente maggiore di un determinato valore
di soglia e, in questo modo, realizzano le interconnessioni tra i vari moduli. Da questo
meccanismo di programmazione delle interconnessioni si eredita una proprietà di nonvolatilità dei dispositivi i quali, quindi, conservano la loro configurazione anche in assenza
di alimentazione; d’altra parte però questa caratteristica implica anche la non-
riprogrammabilità. Diversi sono i vantaggi portati da questo tipo di tecnologia:
un’operatività immediata all’accensione, una riduzione dei ritardi di propagazione delle
connessioni, della potenza dissipata e dell’area di silicio occupata. Inoltre questo tipo di
dispositivi viene ritenuto più affidabile e più protetto da attacchi di hacking o cloning
grazie all’assenza di memorie esterne per la configurazione.
D’altro canto comporta anche diversi svantaggi: primo tra tutti l’utilizzo di un processo di
fabbricazione non standardizzato che ha portato ad una lenta crescita prestazionale. Inoltre
8
una minore capacità logica e la necessità di un programmatore dedicato per la
configurazione del dispositivo.
Questo tipo di tecnologia rientra nei tipi di FPGA detti OTP, ovvero One Time
Programmable.
Il principale produttore di FPGA con tecnologia antifuse è Actel che con la serie
Axcelerator produce dei dispositivi con capacità logica fino a 4 milioni di porte. Offrono
due tipi di moduli logici collegati tra loro tramite una matrice di interconnessioni
realizzata con una tecnologia antifusibile proprietaria mostrata in figura. [1]
Figura 1.1 Tecnologia antifuse proprietaria per i dispositivi Actel Axelerator.
1.2.2 La tecnologia Flash
Questo tipo di tecnologia è concettualmente simile a quella antifuse. Infatti le
interconnessioni programmabili sono basate su switch realizzati con una tecnologia flash.
In particolare, ogni switch è composto da un transistor con floating-gate, ossia un
materiale conduttivo rivestito da un isolante ed interposto tra gate e canale, utile a
memorizzare la carica. Un altro transistor viene invece usato per effettuare la verifica della
configurazione e il read-back dello switch. Quando esso è abilitato garantisce una
connessione a bassa resistenza. Questo tipo di struttura permette l’occupazione di un’area
minore rispetto a quella impiegata nei dispositivi con tecnologia SRAM. Inoltre in questo
caso è assicurata un’immunità del dispositivo ai firm-error.
Anche questa tecnologia possiede la proprietà di non volatilità della configurazione ma, a
differenza della antifuse, può essere riprogrammabile grazie a tensioni aggiuntive a quelle
9
utili per il normale funzionamento. Ma per lo stesso motivo il numero di
riprogrammazioni non è infinito. Infine per proteggere questo tipo di dispositivi da
attacchi di tipo cloning è possibile utilizzare delle chiavi di cifratura del bitstream di
configurazione perdendo però la possibilità di riprogrammare l’FPGA. [1]
1.2.3 La tecnologia RAM statica
Questa invece è una tecnologia ben diversa da quelle viste in precedenza. In questo caso le
funzioni logiche vengono realizzate utilizzando le LUT (look-up-table). Le LUT infatti
sono memorie statiche provviste di n bit di indirizzamento; nota una funzione logica e la
rispettiva tabella di verità, il valore della funzione per una qualunque combinazione di
input viene configurato nella LUT all’indirizzo corrispondente alla stessa combinazione di
ingressi. Di conseguenza una LUT a n ingressi può realizzare una qualsivoglia funzione
logica a n variabili.
Per realizzare le LUT, quindi, vengono utilizzate delle celle di memoria riprogrammabili.
Esse poi sono connesse tramite una fitta rete di linee di distribuzione di segnali.
Questo tipo di tecnologia porta con sé alcuni vantaggi: una maggiore capacità logica e un
maggiore livello di integrazione; migliori prestazioni in termini di frequenza di
funzionamento e minori costi a parità di risorse e funzionalità offerte. Inoltre da non
sottovalutare l’importanza della possibilità di riprogrammare il dispositivo in fase di debug
per poter correggere gli errori di progettazione e permettere di modificare le funzionalità
del dispositivo sul campo.
D’altro canto però questi dispositivi sono volatili e quindi ad ogni accensione richiedono il
caricamento della configurazione da una memoria esterna. Anche qui sono previsti dei
meccanismi di cifratura del bitstream per evitare il cloning. I meccanismi di
programmazione all’avvio impiegano dei tempi variabili impendendo al dispositivo di
essere operativo al power-up. Come ulteriore svantaggio poi c’è una minore tolleranza ai
firm-error e una maggiore dissipazione di potenza.
Questo tipo di tecnologia viene impiegato dai principali produttori citati prima, ovvero
Xilinx e Altera. [1]
10
1.3 Architettura di un FPGA
Andiamo ora ad esaminare l’architettura interna generale di un dispositivo FPGA. Si parla
di architettura generale poiché i dettagli delle celle logiche possono variare in base al
produttore e al modello. In generale si può immaginare che ogni FPGA sia costituito da un
numero finito di risorse predefinite con interconnessioni programmabili, che vanno a
costituire l’intero circuito digitale riconfigurabile, e da blocchi di I/O per consentire al
circuito di comunicare con il mondo esterno. [3]
Solitamente l’architettura si basa sul concetto di riga e di colonna. Infatti i suoi blocchi
logici configurabili – i CLB (Configurable Logic Block) - sono organizzati con una
struttura a matrice. Ai margini di tale matrice si distribuiscono i blocchi di ingresso/uscita
- gli IOB (Input Output Block). I CLB si occupano delle funzioni logiche mentre gli IOB
si occupano dell’interfacciamento con il mondo esterno. In questa matrice ci sono anche
altri tipi di risorse, come i DCM (Digital Clock Manager), la rete che trasporta il segnale
di clock ai CLB proveniente dai flip-flop ed altre risorse di calcolo. Ogni elemento ha un
proprio modello di funzionamento che permette di comprendere a fondo il corretto
funzionamento del circuito. [2]
Figura 1.2 Architettura di un FPGA
11
1.3.1 Blocchi logici configurabili
Il CLB è l’unità logica fondamentale di un FPGA. Il numero esatto e le funzionalità di
questi blocchi variano da dispositivo a dispositivo, ma ogni CLB è costituito da una
matrice di comunicazione configurabile con 4 o 6 ingressi, alcuni circuiti di selezione (e.g.
Multiplexer) e flip-flop. La matrice di scambio è molto flessibile e può essere configurata
per gestire la logica combinatoria, i registri a scorrimento o le RAM. [4]
In generale essi sono costituiti da alcune
celle logiche (chiamate ALM, LE, slice
etc.). Una tipica cella consiste in una LUT
a 4 ingressi, un Full Adder (FA) e un flipFigura 1.3 Struttura interna di una CLB
flop di tipo D, come mostrato in figura.
Le LUT in figura sono divise in due LUT
a 3 ingressi. In modalità normale esse sono combinate come un'unica LUT a 4 ingressi
attraverso un multiplexer posto a sinistra. In modalità aritmetica, i loro output alimentano
il Full Adder. La selezione della modalità è programmata dal multiplexer posto nel mezzo.
L’output può essere sia sincrono che asincrono, a seconda della programmazione del
multiplexer a destra mostrato nella figura. In pratica, tutto o parte del FA partecipa come
funzione nelle LUT per risparmiare spazio. [3]
1.3.2 Blocchi di ingresso/uscita
Questi blocchi si occupano della gestione dei segnali da e verso l’esterno del circuito
grazie al controllo dei pin del chip. Essi sono posizionati sul perimetro della matrice dei
CLB e in particolare possono essere composti da flip-flop, multiplexer e buffer. Inoltre
sono presenti delle resistenze di pull-up/pull-down per caratterizzare lo stato del pin in
situazioni di alta impedenza. [2]
1.3.3
Linee di interconnessione
Queste componenti hanno il compito di far comunicare le altre risorse del dispositivo.
Esse si dividono in due tipologie [2]:

le linee fisse, che a loro volta si dividono in corte e lunghe. Le prime sono
12
deputate alla connessione di CLB adiacenti in modo da minimizzare il
ritardo di propagazione dei segnali. Le linee lunghe invece connettono
risorse tra loro distanti 6 CLB con dei percorsi non configurabili che non

attraversano le linee di scambio, quindi non introducono ritardi significativi.
Le matrici di scambio invece sono reti di pass-transistor programmabili e
comunicanti. Nei dispositivi con tecnologia SRAM queste linee sono fisse e
vengono programmate le connessioni tra linee diverse. Ragion per cui alcune
interconnessioni avvengono tra due lati diversi delle matrici di scambio e
vengono abilitate da pass-transistor.
1.4 FPGA Design e Programming
In questa sezione verrà illustrata per linee generali l’evoluzione del flusso di progetto che
riguarda la programmazione di un FPGA. Solitamente questo flusso segue uno schema
come quello illustrato in figura.
Figura 1.4 Flusso di progettazione di un FPGA
13
Esaminiamo una ad una queste fasi:
1.4.1 Design Entry
Questa prima fase consiste nella descrizione delle funzioni logiche da implementare. Per
definire il comportamento dell’FPGA, l’utente dovrà quindi provvedere un progetto in un
linguaggio di descrizione dell’hardware (HDL, Hardware Description Language) o sotto
forma di schema. I progetti espressi in HDL sono preferibili nel caso in cui si voglia
lavorare con strutture di grandi dimensioni poiché è possibile anche solo specificare il
numero anziché dover disegnare ogni singolo componente. [3] Il metodo schematico
invece, attraverso l’uso di primitive (e.g. flip-flop, porte logiche etc.), prevede la stesura di
un vero e proprio disegno del sistema e permette una visione globale e più semplice del
progetto. Questa tecnica all’inizio era molto diffusa, ma oggi la progettazione è
interamente basata sui linguaggi HDL; tra i più diffusi troviamo Verilog e VHDL. [1] Ciò
è dovuto alla crescente complessità dei dispositivi e alla necessità di tempi di sviluppo
sempre più piccoli. Esistono anche librerie e supporti che permettono la descrizione dei
circuiti logici in Matlab/Simulink con la generazione automatica del codice HDL. Inoltre il
SystemC, libreria per il C++, introduce in questo linguaggio il tempo, il parallelismo e la
concorrenza, permettendo, quindi, di creare modelli veri e propri di circuiti logici. [1]
1.4.2 Simulazione
Subito dopo la fase di design entry abbiamo questa fase, deputata alla verifica che la
descrizione effettuata nella fase precedente rispetti il comportamento atteso. Verranno
quindi stimolati gli ingressi e si verificherà se i segnali in uscita seguano l’andamento
atteso. I tool più diffusi per effettuare questo tipo di test sono ModelSim e ActiveHDL.
D’altra parte le descrizioni in SystemC possono essere simulate con una semplice catena
di debugger C++ come quella GNU/GCC o Microsoft Visual Studio. [1]
1.4.3 Sintesi
In questa fase invece è prevista la traduzione della descrizione del circuito in un formato
interpretabile dai tool delle case produttrici di FPGA i quali produrranno il bitstream di
14
programmazione. Questo processo di sintesi estrae le funzionalità logiche dalla
descrizione e le traduce in porte logiche, flip-flop e connessioni; si dice che viene generata
una netlist il cui formato più diffuso è EDIF. Questa fase ci fornisce anche una indicazione
preliminare di risorse occupate e frequenza massima di utilizzo che potrà essere raggiunta.
Viene quindi prodotta un'altra descrizione in HDL che si basa direttamente su elementi
circuitali, quindi se opportuno si può effettuare di nuovo la fase di simulazione. [1]
1.4.4 Place & Route
Dopo la sintesi si effettua un’associazione tra gli elementi della netlist alle risorse del
dispositivo, vengono quindi definiti i path (percorsi appunto) di connessione tra queste. Si
possono inoltre definire dei vincoli sulla disposizione delle risorse sull’FPGA,
sull’associazione dei segnali di i/o o sulla frequenza di funzionamento. Esistono anche qui
dei tool per effettuare il place & route che si basano su algoritmi randomici per la
disposizione delle risorse. Dopo questa fase viene creato un database contenente i ritardi
dei segnali che può essere estratto in un file formato SDF. Questo layout può subire delle
verifiche in termini di parametri temporali imposti o requisiti di dissipazione della
potenza. [1]
1.4.5 Programmazione e debug in-circuit
Ultima fase della progettazione che prevede la generazione dei file di programmazione di
FPGA. Per la tecnica antifuse vista in precedenza sarà necessario uno specifico
programmatore per configurare il circuito, mentre per la tecnica flash, ad esempio, il file
viene convertito e trasferito nella memoria di configurazione del dispositivo attraverso
un’interfaccia seriale. Una volta programmato il dispositivo bisognerà solo testarlo.
Spesso, a causa della crescente complessità, è impossibile coprire tutti i casi di utilizzo del
dispositivo e quindi correggere i bug. Quindi si ricorre ad un debug-in-circuit che permette
di osservare l’andamento dei segnali nel tempo durante il reale funzionamento dell’FPGA.
Con questa osservazione è possibile risalire all’origine del malfunzionamento per
risolverlo. [1]
15
In conclusione in un tipico flusso di progettazione, uno sviluppatore di applicazioni FPGA
simulerà il progetto nelle diverse fasi durante tutto il processo di progettazione.
Inizialmente la descrizione in VHDL o Verilog viene testata creando dei test benches per
simulare il sistema e osservare i risultati. Poi, dopo che il motore di sintesi ha mappato il
disegno della netlist, quest’ultima viene tradotta in un livello di descrizione delle porte
dove la simulazione viene ripetuta per confermare che la sintesi eseguita sia senza errori.
Infine, il progetto viene riportato sull’FPGA a cui possono essere aggiunti dei punti di
ritardo di propagazione e viene eseguita di nuovo la simulazione con questi valori che
erano stati annotati sulla netlist.
Più di recente OpenCL, di cui parleremo più avanti, viene utilizzato dai programmatori per
sfruttare le prestazioni e l’efficienza che forniscono gli FPGA. OpenCL permette ai
programmatori di sviluppare un codice nel linguaggio di programmazione C e indirizzare
le funzioni dell’FPGA come un kernel OpenCL utilizzando i suoi costrutti. [3]
16
Capitolo 2: Dispositivi GPU
In questo capitolo verrà illustrata l’architettura e il funzionamento dei dispositivi GPU, in
particolare verrà analizzato un suo modello di programmazione che utilizza il linguaggio
OpenCL, utile alla programmazione eterogenea.
Il concetto di GPU moderno è stato approfondito in gran parte negli ultimi 15 anni. Prima
l’elaborazione grafica veniva effettuata con i dispositivi VGA (Video Graphic Array),
ovvero dei controller di memoria interfacciati con un’uscita video e dotati di DRAM. Essi
praticamente ricevevano i dati delle immagini e dopo averle elaborate le fornivano in
uscita attraverso un monitor. Successivamente vennero aggiunti degli acceleratori grafici
che permettevano di effettuare operazioni più articolate, ad esempio rasterization, shading
e texture mapping. Nel 1999 infatti Nvidia rende popolare il termine GPU
commercializzando la GeForce 256 come "prima GPU del mondo", o Graphics
Processing Unit, un processore single-chip con integrato sia il transform, clipping and
lighting del triangolo, sia macchine per il rendering, capaci di processare un minimo di 10
milioni di poligoni al secondo. La rivale ATI Technologies poi ha coniato il termine unità
di elaborazione visiva o VPU con il rilascio della Radeon 9700 nel 2002.
Una GPU (Graphics Processing Unit), anche chiamata Visual Processing Unit (VPU), è un
circuito elettronico specializzato. Esso è stato progettato per manipolare e modificare
rapidamente la memoria per accelerare la creazione di immagini in un frame buffer
destinato all’output a video.
Le GPU vengono principalmente utilizzate nei sistemi embedded, telefoni cellulare,
personal computer, workstation e console di gioco. Le moderne GPU sono molto efficienti
per la computer grafica e il processing di immagini, inoltre la loro struttura altamente
17
parallela le rende più efficaci rispetto alle CPU general-purpose per gli algoritmi in cui
l’elaborazione di grandi blocchi di dati visivi è fatto in parallelo. Un esempio di impiego
di una GPU può essere ad esempio in un personal computer sia su una scheda video che
integrato sulla scheda madre o sul die della CPU stesso. [6]
2.1 Architettura di un dispositivo GPU
L’evoluzione veloce dell’hardware e le diverse tecniche di progettazione dei vari
produttori non ci consentono di definire un concetto di architettura generale di GPU, ma
nonostante ciò le somiglianze tra i vari modelli sono molte.
Verrà quindi illustrata una struttura di una generica GPU moderna mostrata in figura 2.1
Figura 2.1 Architettura di una moderna GPU
Il primo concetto da chiarire è che le GPU sono dei coprocessori e per questo motivo
necessitano di essere connesse ad un host, ovvero un processore tradizionale. Questo
collegamento avviene attraverso un bus PCI Express ad alta velocità che garantisce il
trasferimento dei dati dalla memoria della CPU a quella della GPU e viceversa.
C’è da dire anche che i processori grafici raggiungono un alto livello di parallelismo, in
particolare nell’architettura dei dispositivi di memorizzazione e nell’organizzazione dei
18
core d’esecuzione. Ogni GPU infatti è costituita da svariate unità di elaborazione dette SM
(Streaming Multiprocessor) che rappresentano il primo livello logico di parallelismo e
appunto per questo ognuna di queste unità riesce a lavorare contemporaneamente e
indipendentemente dalle altre.
Ogni SM è a sua volta composto da un insieme di SP (Streaming Processor), ognuno dei
quali è un core d’esecuzione reale che può eseguire un thread sequenzialmente. Questi
elementi rappresentano l’unità logica d’esecuzione più piccola e rappresentano il livello di
parallelismo più fine. Oltre alla divisione di natura strutturale tra SP e SM, possiamo
delineare un’altra organizzazione logica tra gli SP di una stessa GPU; essi infatti sono
raggruppati in blocchi logici che eseguono in una particolare modalità, ovvero tutti i core
di un gruppo eseguono contemporaneamente la stessa istruzione similmente a quanto
avviene nel modello SIMD (Single Instruction, Multiple Data).
Possiamo aggiungere inoltre che ogni processore grafico possiede diversi tipi di memoria;
ognuna di queste è posizionata in aree diverse del dispositivo, ha delle caratteristiche
diverse e può essere sfruttata per eseguire operazioni differenti.
L’unità di memoria con maggiore capacità è la memoria globale, la quale può raggiungere
una dimensione di diversi GB ma con un’alta latenza. A questa memoria possono accedere
tutti i core della GPU e anche l’host, il quale è collegato in maniera diretta al dispositivo di
memorizzazione. Essa infatti è spesso usata per memorizzare grandi quantità di dati
trasferiti dalla memoria del processore e, non essendo molto veloce, possiede meccanismi
di ottimizzazione degli accessi o livelli di cache che rendono più efficienti le
comunicazioni con la GPU.
Un ulteriore spazio di memoria è la texture memory, accessibile a tutti i core e utilizzabile
dalla GPU in sola lettura. In principio è stata progettata per memorizzare immagini
particolari, appunto le texture, che venivano utilizzate per la rappresentazione di elementi
tridimensionali. Essa dispone di meccanismi specifici di caching che la rendono efficiente
nell’accesso ad elementi vicini e nella memorizzazione di strutture bidimensionali.
All’interno degli SM troviamo uno spazio di memoria condiviso tra i core costituenti un
workgroup, essa è più piccola (dell’ordine di alcune decine di MB) e veloce rispetto a
19
quella globale. Usualmente viene utilizzata per conservare valori utilizzati frequentemente
da core diversi in maniera da minimizzare gli accessi alla memoria globale. Questo spazio
di memoria però è afflitto da i soliti problemi di concorrenza, quindi bisogna assicurare la
coerenza dei valori memorizzati a costo di sequenzializzare a volte gli accessi alle stesse
locazioni di memoria.
Infine ogni SM dispone di determinati registri, che svolgono il ruolo di aree di memoria ad
accesso rapido, non condiviso tra i core (locale), temporaneo e di dimensioni limitate; essi
infatti vengono utilizzati per memorizzare valori utilizzati frequentemente da un singolo
core. [7]
2.2 Modello GPGPU
La GPGPU, acronimo di general-purpose computing on graphics processing units, è un
settore dell’informatica che si pone l’obiettivo di utilizzare la GPU non solo per la
tradizionale creazione di immagini tridimensionali, ma di impiegarla per elaborazioni che
richiedono un’elevata potenza di elaborazione e che una CPU non è in grado di soddisfare.
Tali elaborazioni sono di tipo parallelo e per questo sono in grado di sfruttare a pieno
l’architettura tipica della GPU illustrata in precedenza che, essendo completamente diversa
da quella dei tradizionali processori, richiede tecniche di programmazione differenti.
Accanto a questo parallelismo abbiamo anche un’estrema programmabilità, introdotta
nell’ultimo decennio, che ha portato un aumento di potenza elaborativa e di versatilità.
[11]
La caratteristica più importante che contraddistingue le GPU è l’elevato numero di core
disponibili che permette un’esecuzione concorrente di più thread parzialmente
sincronizzati nell’esecuzione della stessa istruzione. Questo vantaggio però è traibile solo
quando è possibile suddividere il lavoro in tante parti e effettuando le stesse operazioni su
dati diversi; se ciò non è verificato o se esistono un ordine logico e una sequenzialità nelle
operazioni da svolgere, questa architettura non è utilizzabile al meglio. Le applicazioni di
questo tipo sono solo una parte ristretta del panorama software poiché richiedono
un’elevata parallelizzazione del codice, caratteristica tipica di alcuni problemi scientifici
20
ma non di tutti.
Per questo motivo si dice che le istruzioni della GPU sono SIMD (Single Instruction,
Multiple Data) e con un piccolo esempio possiamo mostrare una transizione di codice da
CPU a GPU: ipotizziamo di voler incrementare di uno tutti gli elementi di una matrice
3x3. Con approccio tradizionale il problema si risolve con due cicli for innestati che
scorrendo tutti gli elementi li modificano incrementandoli. Su una GPU invece una singola
operazione SIMD può modificare tutti gli elementi contemporaneamente.
Un punto debole invece delle GPU sono i branch poiché, nel caso in cui non si vuole far
eseguire lo stesso salto a tutti gli elementi di un blocco computazionale, l’operazione
diventa seriale, causando un drastico rallentamento dell’esecuzione.
Diversi sono i benefici che si possono trarre dall’utilizzo di una GPU per svolgere
elaborazioni diverse da quelle grafiche. Le più importanti sono: [11]

Vantaggi prestazionali: come detto prima la potenza teorica offerta da questo
tipo di utilizzo delle GPU è uno dei principali vantaggi e porta in sé una
riduzione del tempo di elaborazione rispetto all’utilizzo di una CPU.
Sicuramente
l’elaborazione
del
codice
verso
la
GPU
richiede
un’ottimizzazione dell’applicazione che in casi estremi si traduce in una
riscrittura totale del codice, ovviamente gli sviluppatori devono essere a
conoscenza di tale “rischio” ma in termini di prestazioni si possono ottenere

miglioramenti anche del 100% con rispettive riduzioni dei tempi di calcolo.
Costo d’acquisto: i costi di una GPU e di una CPU della stessa fascia di
mercato sono abbastanza simili però c’è da considerare che, a parità di
prezzo, la GPU offre delle prestazioni teoriche superiori. Ciò mostra che
ottimizzare i software per il funzionamento su queste architetture porta un
miglioramento notevole delle prestazioni e dell’efficienza pur mantenendo i

costi costanti.
Tasso di aggiornamento tecnologico: rispetto alle CPU i processori grafici
hanno un’evoluzione tecnologica più veloce. Infatti una nuova architettura di
GPU dura dai 12 ai 18 mesi massimo, contro i quasi 2 anni delle CPU, e
21
introduce un raddoppio puro della potenza elaborativa ad ogni nuova
generazione, mentre per le CPU abbiamo un aumento delle prestazioni solo

del 20-30%.
Consumo/prestazioni: l’alta potenza elaborativa teorica compensa l’alto
livello di consumo energetico e quindi questo rapporto è migliore rispetto a
quello delle CPU nonostante i produttori di quest’ultime si siano impegnate
molto negli ultimi anni sul fronte dell’efficienza energetica.
2.3 Approcci alla programmazione GP-GPU
Il fatto che i processori grafici abbiano introdotto questo grande aumento di performance
ha fatto pensare agli sviluppatori di poter impiegare le GPU come piattaforme per
effettuare calcoli general purpose. Ciò ha dato il via alla nascita e alla rapida evoluzione di
nuovi linguaggi di programmazione per questo ambiente. Appunto per questo nel 2006
furono presentati CUDA e Stream, ovvero due interfacce per la programmazione grafica
ideate dai due maggiori costruttori di GPU ovvero NVIDIA e AMD. CUDA in particolare
offre estensioni per i più utilizzati linguaggi di programmazione e anche integrazioni negli
IDE come Eclipse e Visual Studio. Dal 2010 al 2012 esso ha triplicato il numero dei suoi
sviluppatori attivi, eloquente segno dell’interesse sempre crescente che questa tecnologia
sta sviluppando nell’ambiente della programmazione parallela.
Qualche anno dopo l’uscita di CUDA venne avviato il progetto OpenCL il cui scopo era
quello di creare un framework d’esecuzione eterogeneo sul quale potessero lavorare, non
solo GPU di differenti produttori, ma anche CPU di diverse tipologie. Questa soluzione
pur essendo la più versatile e completa non ha la stessa facilità di utilizzo e maturità di
CUDA.
Con il passare degli anni questi linguaggi si sono evoluti avvicinandosi sempre di più ai
comuni linguaggi di programmazione general purpose e aggiungendo dei meccanismi di
controllo sempre più efficaci. Attualmente OpenCL e CUDA sono le soluzioni più
utilizzate e efficienti per poter usufruire a pieno della potenza di calcolo delle GPU. In
particolare nel capitolo successivo ci occuperemo di esclusivamente OpenCL.
22
Capitolo 3: OpenCL
Negli ultimi anni l'informatica è entrata in una nuova era denominata dell'informatica
eterogenea, la quale si basa sul concetto di unire in un singolo dispositivo il meglio delle
CPU e delle GPU. Da un lato i progettisti creano una sempre più vasta gamma di
macchine eterogenee, e dall’altra i rivenditori di supporti hardware li rendono ampiamente
disponibili. Questo cambiamento di hardware offre grandi piattaforme per applicazioni
nuove e interessanti. Ma, poiché i progetti sono molto diversi, i classici modelli di
programmazione non sono adatti ed è quindi risultato importante apprenderne di nuovi
come quelli in OpenCL. [8]
Quando il progetto di OpenCL partì, i progettisti notarono che per una classe di algoritmi
focalizzati sulla latenza, gli sviluppatori scrivevano il codice in C e C++ e lo eseguivano
su una CPU, ma per un'altra classe di algoritmi focalizzati sul throughput, gli sviluppatori
spesso scrivevano il codice in CUDA e usavano le GPU: questi due approcci sono legati
tra loro ma d’altro canto anche separati dal fatto che ciascuno lavora su un tipo di
processore; il C++ non esegue su una GPU, CUDA non esegue su una CPU. Quindi gli
sviluppatori dovevano specializzarsi in uno dei due linguaggi ignorando l'altro. Proprio per
questo la vera potenza di un dispositivo eterogeneo è che esso può eseguire
efficientemente applicazioni che mischiano entrambe le classi di algoritmi. Il problema è:
come programmare tali macchine?
Una soluzione è quella di aggiungere nuove funzionalità alle piattaforme già esistenti; sia
C++ che CUDA si stanno evolvendo attivamente per affrontare la sfida di un nuovo
hardware. Un'altra soluzione è stata quella di creare un nuovo set di astrazioni di
programmazione mirate specificamente per l'informatica eterogenea. Apple infatti si
23
avvicinò con una prima proposta per tale nuovo paradigma. Questa proposta poi è stata
raffinata da team tecnici di diverse aziende, diventando infine OpenCL.
Ci furono tantissimi miglioramenti per il linguaggio kernel:

Permettere agli sviluppatori di scrivere i kernel in un unico linguaggio di

Permettere a questi kernel di essere funzionalmente portabili rispetto alle


partenza.
CPU, GPU, FPGA e altri tipi di dispositivi.
Restare di basso livello così che gli sviluppatori possano dipanare tutte le
performance di ogni dispositivo.
Mantenere il modello abbastanza astratto, per fare in modo che lo stesso
codice lavori correttamente su macchine prodotte da diverse società.
Naturalmente, come qualsiasi progetto informatico, tutto ciò è stato fatto velocemente. Per
velocizzare l'implementazione, è stato scelto di basare il linguaggio su C99. In meno di 6
mesi sono state prodotte le specifiche per OpenCL 1.0, entro 1 anno sono apparse le prime
implementazioni, dopodiché OpenCL incontrò i veri sviluppatori.
In primo luogo, gli sviluppatori C hanno evidenziato tutte le grandi caratteristiche del C++
che lo hanno reso più produttivo. Gli sviluppatori CUDA invece hanno indicato tutte le
nuove caratteristiche che NVIDIA aveva introdotto in CUDA, le quali resero i programmi
più semplici e veloci.
In secondo luogo, hanno esplorato l'informatica eterogenea come architetti e hanno capito
come rimuovere le prime restrizioni le quali richiedevano che CPU e GPU avessero
memorie separate. Un grande cambiamento hardware fu infatti lo sviluppo di dispositivi
integrati, i quali forniscono su un unico die sia una GPU e una CPU.
Inoltre, anche se la specifica è stata scritta con molta cura e con un ambiente conforme, gli
implementatori del compilatore non sempre leggono le specifiche alla stessa maniera,
ovvero a volte lo stesso programma può offrire risposte diverse su dispositivi differenti.
Tutto ciò portò ad una rivisitata e, più matura, specifica ossia OpenCL 2.0.
La nuova specifica infatti ha rappresentato un’evoluzione significativa e ha permesso agli
24
sviluppatori di trarre vantaggio dai nuovi processori integrati GPU/CPU.
I grandi cambiamenti sono stati i seguenti:

Memoria virtuale condivisa: per fare in modo che il codice dell’host e del
dispositivo possono condividere strutture pointer-based complesse come gli
alberi e le liste linkate, liberandosi del costoso trasferimento di dati tra host e



dispositivi.
Parallelismo dinamico: per fare in modo che i kernel del dispositivo possono
lanciare dei task allo stesso dispositivo senza l'interazione dell'host,
sbarazzandosi dei colli di bottiglia.
Spazi di indirizzamento generici: per permettere alle singole funzioni di
poter operare sia su dati della GPU che della CPU, rendendo la
programmazione più facile.
Atomiche stile C++: così che i work-item possono condividere dati
attraverso i work-group e i dispositivi, consentendo ad una più ampia classe
di algoritmi di essere realizzati in OpenCL.
3.1 Programmazione eterogenea
In questo paragrafo introdurremo un nuovo campo dell'informatica nato proprio
dall'esigenza di poter utilizzare device più prestanti, come ad esempio le GPU, per scopi
computazionali.
La programmazione eterogenea include in sé sia l’elaborazione parallela che concorrente.
Con essa infatti i task che compongono un’applicazione sono mappati sul migliore
dispositivo di elaborazione disponibile nel sistema. La presenza di più device in un
sistema offre l’opportunità ai programmi di utilizzare la concorrenza e il parallelismo per
migliorare sia performance che potenza. Appunto per questo OpenCL è un linguaggio di
programmazione sviluppato specificamente a supporto degli ambienti di programmazione
eterogenea.
Oggigiorno gli ambienti di programmazione eterogenea sono sempre più sfaccettati e
sfruttano le capacità di una vasta gamma di microprocessori multicore, CPU, DSP, FPGA
25
e GPU. Presentato con tutta questa eterogeneità, il processo che prevede il mapping dei
task software su un così grande gruppo di architetture pone al mondo dei programmatori
un certo numero di sfide.
Le applicazioni eterogenee di solito includono un mix di funzionamenti in base al carico di
lavoro, che vanno da quelli control intensive (e.g. ricerca, confronto, ordinamento) a quelli
data intensive (e.g. processing di immagini, simulazioni di modelli, data mining). Alcuni
task inoltre possono essere classificati come compute intensive (e.g. metodi iterativi,
metodi numerici, modellazione finanziaria) in cui il throughput complessivo del task è
fortemente
dipendente
dall’efficienza
computazionale
del
dispositivo
hardware
sottostante. Ognuna di queste classi solitamente viene eseguita in maniera più efficiente su
uno specifico tipo di architettura hardware, non esiste infatti un dispositivo ottimo che
esegua tutte le classi prima elencate. Ad esempio, le applicazioni control intensive tendono
ad eseguire più velocemente sulle CPU superscalari, in cui una parte significante del die è
stata destinata ai meccanismi di predizione dei branch. Invece le applicazioni data
intensive tendono ad eseguire più velocemente sulle architetture vettoriali, in cui la stessa
operazione è applicata a diversi dati e più operazioni vengono eseguite in parallelo. [8]
3.2 Breve panoramica della specifica di OpenCL
Illustriamo brevemente in questo paragrafo la specifica di OpenCL e un suo tipico modello
di esecuzione. La specifica di OpenCL è definita in quattro parti fondamentali, ognuna
delle quali viene indicata come modello. I modelli sono riassunti qui di seguito senza
entrare nel dettaglio ma indicando solo il ruolo che ricopre ognuno di esso.

Modello di Piattaforma: specifica che c’è un processore host, che coordina
l’esecuzione, e uno o più processori device il cui lavoro è quello di eseguire
il kernel OpenCL C. Esso inoltre definisce un modello di hardware astratto

per i dispositivi.
Modello di Esecuzione: definisce come l’ambiente OpenCL è configurato
dall’host e come quest’ultimo può indirizzare i dispositivi ad eseguire i
compiti. Esso comprende la definizione di un’ambiente per l’esecuzione
26
sull’host, i meccanismi per l’interazione e il modello di concorrenza usato al
momento della configurazione dei kernel. Il modello di concorrenza
definisce come un algoritmo viene decomposto in work-items e work-group


OpenCL.
Modello di programmazione kernel: definisce come il modello di
concorrenza viene mappato sull’hardware fisico.
Modello di memoria: definisce i tipi di oggetti della memoria e la gerarchia
della memoria astratta che i kernel usano senza curarsi della reale
architettura della memoria sottostante. Esso inoltre contiene i requisiti per
l’ordinazione della memoria e per l’eventuale memoria virtuale condivisa tra
l’host e i dispositivi.
In uno scenario tipico possiamo osservare un’implementazione OpenCL che esegue su una
piattaforma composta da un x86 CPU come host la quale usa una GPU come un
acceleratore. L’host imposta un kernel da far eseguire alla GPU e manda il comando alla
GPU di eseguire il kernel con un certo grado di parallelismo. Questo rappresenta il
modello di esecuzione. La memoria per i dati utilizzati dal kernel è allocata dal
programmatore in una parte specifica della gerarchia della memoria astratta specificata dal
modello di memoria. Infine la GPU crea i thread hardware per eseguire il kernel e li
mappa nelle sue unità hardware. Ciò viene fatto usando il modello di programmazione. [8]
3.3 OpenCL su FPGA
Andiamo ora a illustrare qual è il ruolo di OpenCL quando si vuole programmare un
dispositivo FPGA. Come visto nel primo capitolo, il design e il programming di questi
dispositivi è organizzato in più fasi; in questo caso OpenCL entra in gioco proprio nella
prima fase, ovvero quella di design entry. In questo paragrafo prenderemo come esempio
di design un FPGA Altera.
Come visto in precedenza lo standard OpenCL, oltre a fornire un modello portabile, offre
la possibilità di descrivere degli algoritmi paralleli da implementare sugli FPGA ad un
livello di astrazione più alto rispetto ai linguaggi di descrizione hardware (HDL) come
27
VHDL o Verilog. Sebbene esistano diversi strumenti di sintesi ad alto livello che
raggiungono questo livello di astrazione, essi soffrono dello stesso problema
fondamentale: questi tool hanno tentato di prendere un programma C sequenziale e
produrre un’implementazione HDL parallela. La difficoltà non stava tanto nella creazione
dell’implementazione HDL ma piuttosto nell’estrazione di un parallelismo di livello
thread che avrebbe permesso l’implementazione dell’FPGA per raggiungere alte
prestazioni.
Figura 3.1 Recente trend delle tecnologie programmabili e parallele
Essendo gli FPGA sull’estremo più lontano dello spettro di parallelismo, ogni fallimento
nell’estrazione del parallelismo massimo è più paralizzante che su altri device. Lo standard
OpenCL risolve alcuni di questi problemi permettendo al programmatore di specificare
esplicitamente e controllare il parallelismo. Appunto per questo tale standard si abbina più
naturalmente alla natura altamente parallela degli FPGA rispetto ai programmi sequenziali
descritti in C puro. Andiamo ora a vedere come avviene questa implementazione in
OpenCL.
Le applicazioni OpenCL sono strutturate in due parti. Il programma OpenCL dell’host è
una semplice routine software, scritta in C/C++, che esegue su qualsiasi tipo di
microprocessore. Esso infatti può essere, ad esempio, un processore soft embedded su un
FPGA, un processore hard ARM o uno x86 esterno come mostrato in figura 3.2.
28
Figura 3.2 Schema di un'applicazione OpenCL
Ad un certo punto durante l’esecuzione di questa routine software host ci potrebbe essere
una funzione che è computazionalmente costosa e potrebbe beneficiare dell’accelerazione
altamente parallela su un device tipo CPU, GPU, FPGA etc. Questa funzione per essere
accelerata viene indirizzata come un kernel OpenCL. Essa viene scritta con lo standard C
però viene annotata con i costrutti per specificare il grado di parallelismo e la gerarchia
della memoria. L’esempio mostrato in figura 3.3 esegue la somma vettoriale di due array,
A e B, mentre scrive il risultato in un array di output. I thread paralleli operano su ogni
elemento del vettore permettendo al risultato di essere calcolato molto più velocemente
quando viene accelerato da un device che offre un enorme quantità di parallelismo
finemente granulare come accade in un FPGA. Il programma host ha accesso alle API
(Application programming interface) dello standard OpenCL che permettono ai dati di
essere trasferiti ad un FPGA invocando il kernel su quest’ultimo e ritornando i dati
risultanti.
29
Figura 3.3 Esempio di implementazione OpenCL su un FPGA
Diversamente dalle CPU e dalle GPU, in cui i thread paralleli possono essere eseguiti su
core differenti, gli FPGA offrono una strategia diversa. Le funzioni kernel possono essere
trasformate in circuiti hardware in pipeline dedicati che sono appropriatamente utilizzati in
maniera multithread utilizzando il concetto di parallelismo della pipeline. Ognuna di
queste pipeline può essere replicata diverse volte per fornire ancora più parallelismo di
quanto sia possibile con una sola pipeline. Il compilatore OpenCL di Altera traduce un
kernel in un hardware creando un circuito che implementa tutte le operazioni. Questi
circuiti sono connessi tra loro per simulare il flusso dei dati nel kernel. Nella somma
vettoriale del nostro esempio la traduzione in hardware risulterà una semplice feedforward pipeline. I caricamenti dagli array A e B sono convertiti in unità di carico, le quali
sono piccoli circuiti responsabili del rilascio di indirizzi di una memoria esterna e
l’elaborazione dei dati restituiti. I due valori di ritorno vanno ad alimentare direttamente
un addizionatore responsabile dell’addizione floating-point. Infine, il risultato del
sommatore è collegato direttamente ad un'unità di deposito che scrive la somma nella
memoria esterna.
Il concetto più importante dietro il compilatore OpenCL-to-FPGA è la nozione di
parallelismo pipeline. Per semplicità, assumiamo che il compilatore abbia creato tre stadi
di pipeline per il kernel, come mostrato in figura 3.4. Al primo ciclo di clock il thread 0
viene clockato nelle due unità di caricamento, questo indica che devono iniziare la fase di
fetch dei primi elementi dai vettori A e B. Al secondo ciclo di clock il thread 1 viene
clockato nello stesso momento in cui il thread 0 ha completato la sua lettura dalla memoria
30
e memorizza il risultato nei registri successivi alle unità di caricamento. Al terzo ciclo
tocca al thread 2, il thread 1 acquisisce i dati che ha restituito e il thread 0 memorizza la
somma dei due valori che aveva caricato. È evidente che a regime tutte le parti della
pipeline sono attive e ogni fase di processing con un thread diverso.
Figura 3.4 Pipelined Information
La figura 3.4 mostra una rappresentazione di alto livello di un sistema OpenCL completo
che contiene diverse pipeline kernel e il circuito che connette queste pipeline alle
interfacce dati off-chip. Oltre alle kernel pipeline il compilatore OpenCL di Altera crea
delle interfacce per la memoria interna ed esterna. Le unità di load and store per ogni
pipeline sono connesse alla memoria esterna tramite una struttura di interconnessione
globale che gestisce le richieste multiple ad un gruppo di DDR DIMMs. Similarmente, gli
accessi alla memoria locale OpenCL sono collegati attraverso una struttura di
interconnessione
specializzata
per
RAM
on-chip
M9K.
Queste
strutture
di
interconnessione vengono progettate per assicurare operatività ad alte frequenze e
un’efficiente organizzazione delle richieste di memoria.
31
Figura 3.5 Implementazione di un sistema OpenCL
Vediamo ora quali sono i benefici portati dall’implementazione dello standard OpenCL su
un FPGA.
La creazione dei progetti per gli FPGA usando una descrizione OpenCL offre diversi
vantaggi in confronto ad una metodologia tradizionale basata sul design HDL. Lo sviluppo
dei software per i dispositivi programmabili segue tipicamente il flusso in cui prima si
concepisce un’idea, si codifica l’algoritmo in un linguaggio di alto livello come il C e poi
si usa un compilatore automatico per creare uno stream di istruzioni. Questo approccio
può essere contrastato con le tradizionali metodologie di design basate su FPGA. Infatti
gran parte del carico è posto sul progettista per creare delle descrizioni dell’hardware
“ciclo per ciclo” che vengono utilizzate per implementare gli algoritmi. Il flusso
tradizionale prevede la creazione di datapath e state machine per controllare tali datapath,
la connessione ai core IP di basso livello attraverso strumenti di livello di sistema (e.g.
SOPC Builder, Platform Studio) e la gestione di problemi di sincronizzazione dato che le
interfacce esterne impongono vincoli fissi che devono essere soddisfatti. L’obiettivo di un
compilatore OpenCL è quello di eseguire tutti questi step automaticamente per i progettisti
consentendogli di concentrarsi sulla definizione degli algoritmi piuttosto che sui dettagli
noiosi di progettazione hardware. Progettare in questo modo permette al designer di
migrare facilmente verso le nuove FPGA che offrono performance migliori e capacità più
elevate poiché il compilatore OpenCL trasformerà la stessa descrizione di alto livello in
pipeline che sfruttano le nuove FPGA. [9]
32
Conclusioni
In conclusione, quindi, abbiamo mostrato che la nascita di nuovi dispositivi programmabili
come gli FPGA hanno rivoluzionato il mondo dell’informatica e dell’elettronica digitale,
permettendo un riutilizzo dell’hardware e abbattendo anche i costi e i tempi di produzione
di tali dispositivi. In particolare l’evoluzione delle architetture ha portato ad una struttura
moderna degli FPGA che permette l’implementazione di qualsivoglia funzione logica ad
un costo minimo. Inoltre un forte vantaggio che si può trarre è che il debugging delle
applicazioni viene fatto più tardi possibile e nella maniera più semplice ed economica. Per
quanto riguarda invece le GPU, abbiamo visto che il nascere di queste nuove unità di
elaborazione grafiche ha giovato anche al mondo del calcolo parallelo proprio grazie alla
potenza computazionale intrinseca in questi dispositivi. Quindi, abbandonando l’idea della
semplice elaborazione di immagini e accogliendo questi device come acceleratori di
calcolo, nasce il modello GPGPU, ovvero una GPU general purpose che permette di
raggiungere un elevato grado di parallelismo nelle applicazioni. Tutti questi vantaggi sono
traibili ovviamente tramite una buona programmazione e la stesura di algoritmi che
supportino un determinato livello di parallelismo; a ciò viene in aiuto quindi il linguaggio
OpenCL che introduce una sorta di traduzione degli algoritmi seriali in routine parallele
che dipende dall’architettura hardware sottostante.
33
Bibliografia
[1]
[2]
[3]
[4]
[5]
[6]
[7]
[8]
[9]
Mariano Severi, “CorsoFPGA”, Elettronica In.
Wikipedia italia, FPGA,
https://it.wikipedia.org/wiki/Field_Programmable_Gate_Array , 04/11/2015.
Wikipedia, Field-Programmable Gate Array,
https://en.wikipedia.org/wiki/Field-programmable_gate_array, 04/11/2015.
National Instruments,
http://www.ni.com/white-paper/6983/en/, 04/11/2015.
Xilinx,
http://www.xilinx.com/fpga/, 04/11/2015
Wikipedia, Graphics processing unit,
https://en.wikipedia.org/wiki/Graphics_processing_unit, 06/11/2015.
David B. Kirk and Wen-mei W. Hwu. Programming Massively Parallel Processors:
A Hands-on Approach. Morgan Kaufmann Publishers Inc., 2010.
David Kaeli, Perhaas Mistry, Dana Schaa, Dong Ping Zhang. Heterogeneous
Computing with OpenCL 2.0 Third Edition. Morgan Kaufmann Publishers Inc.,
2015, pagg 1-6, 33-36, 41-43
Altera.com,
https://www.altera.com/en_US/pdfs/literature/wp/wp-01173-opencl.pdf, 15/11/2015
[10] Altera.com, Implementing FPGA Design with the OpenCL Standard
https://www.altera.com/products/design-software/embedded-softwaredevelopers/opencl/overview.html, pag 3-7, 15/11/2015
34
[11] Wikipedia, GPGPU,
https://it.wikipedia.org/wiki/GPGPU, 6/11/2015
35
Scarica

Elaborato Esposito Gabriella N46001421