arkanoid on altera de-1
TRANSCRIPT
INTRODUZIONE
Arkanoid è un videogioco prodotto dalla software house giapponese Taito nel 1986. Esso è da considerarsi come il rifacimento
dell'arcade Breakout, di casa Atari, risalente agli anni settanta. Sebbene non sia il padre del suo genere, è considerato una pietra
miliare dei videogiochi e negli anni ha ispirato innumerevoli cloni e giochi simili.
Scopo del gioco originale è abbattere un certo numero di mattoncini colorati colpendoli con una sfera. Sul fondo dello schermo
si trova l'astronave "Vaus" che agisce da "base" con cui far rimbalzare la sfera contro i mattoncini; naturalmente bisogna evitare
che la sfera cada nella porzione di schermo sottostante al "Vaus", pena la perdita di una vita.
Per questa tesina si è realizzata una versione semplificata di Arkanoid che ne rispecchia tutte le funzionalità base:
La sfera rimbalza e distrugge i mattoncini;
Esiste il concetto di vita(ci sono 3 vite disponibili per poter tentare di distruggere il muro di mattoncini)
e alcune di quelle avanzate:
Alcuni mattoncini sono indistruttibili;
È possibile variare l’angolo di rimbalzo della sfera imprimendole un effetto tramite il movimento della navicella;
È possibile colpire la sfera con i lati a 45 gradi della navicella facendole invertire il moto mantenendo l’angolo.
Per lo sviluppo si è utilizzata la scheda terasic DE1 che monta FPGA Cyclone II di ALTERA
La scheda DE1 mette a disposizione numerose features per sviluppare variegati progetti:
Altera Cyclone II 2C20 FPGA with 20000 LEs
Altera Serial Configuration deivices (EPCS4) for Cyclone II 2C20
USB Blaster built in on board for programming and user API controlling
JTAG Mode and AS Mode are supported
8Mbyte (1M x 4 x 16) SDRAM
4Mbyte Flash Memory
512Kbyte(256Kx16) SRAM
SD Card Socket
4 Push-button switches
10 DPDT switches
8 Green User LEDs
10 Red User LEDs
4 Seven-segment LED displays
50MHz oscillator ,24MHz oscillator ,27MHz oscillator and external clock sources
24-bit CD-Quality Audio CODEC with line-in, line-out, and microphone-in jacks
VGA DAC (4-bit R-2R per channel) with VGA out connector
RS-232 Transceiver and 9-pin connector
PS/2 mouse/keyboard connector
Two 40-pin Expansion Headers
Per la realizzazione di Arkanoid si è scelto di utilizzare:
2 switch ”Push-Button” per comandare il movimento a destra e sinistra della barretta;
2 switch DPDT per controllare reset del gioco e Start/Pause;
I display a 7 segmenti per visualizzare le “vite” rimanenti;
L’uscita VGA per la visualizzazione a schermo.
Inoltre, finita la fase di testing in cui la programmazione del FPGA Cyclone II è stata effettuata con metodologia JTAG, si è anche
sfruttata la eeprom EPCS4 per salvare in maniera permanente il codice.
Il software di sviluppo utilizzato è Quartus II fornito da Altera. Per la finalità didattica di
questo progetto è sufficiente la versione web scaricabile direttamente dal sito Altera.
Tale software rende disponibili tool per realizzare tutte le fasi necessarie per il progetto:
Editor Grafico per gli schematici
Editor testuale per la creazione dei file in linguaggio VHDL
Interfaccia per programmare in modalità JTAG o AS
ARCHITETTURA
DIVISORE DI FREQUENZA:
Ha il compito di dividere di un fattore due la frequenza di clock di 50 MHz fornita da uno degli oscillatori montati sulla Board
TERASIC DE1. Tale divisione è necessaria per ottenere la frequenza di 25 MHz richiesta per la modalità di funzionamento della
VGA: 640x480 pixel a 25MHz.
INTERFACCIA VGA:
Come visto nell’introduzione la scheda TERASIC DE1 rende disponibile un’uscita VGA con connettore a 16pin. I segnali di
sincronizzazione vengono gestiti direttamente dallaFPGA Cyclone II ed è presente un convertitore digitale analogico (4bit DAC)
per produrre i segnali analogici R G e B.
Figura 1 - Circuito VGA scheda terasic DE1
La risoluzione supportata è 640x480 a cui corrisponde un dot clock di 25MHz e un refresh di 60Hz, ossia vengono disegnati 60
schermi al secondo. La visualizzazione dell’immagine su monitor avviene tramite righe. Una volta disegnata una riga ed atteso il
tempo di latenza necessario(Front Porch) per passare da una riga all’altra, si passa a disegnare la successiva fino a che si è
disegnato tutto il monitor. A questo punto si ricomincia da capo.
Figura 2 - Diagramma temporale del segnale di sincronismo orizzontale
Figura 3 - Specifiche temporali
Un impulso attivo basso di durata a viene applicato al segnale di sincronismo orizzontale della porta VGA; tale periodo
determina una riga di dati. L’ingresso RGB del monitor dev’essere pilotato a 0 Volt per un periodo di tempo chiamato back porch
dopo l’arrivo dell’impulso hsync. Successivamente nel periodo c viene disegnato sull’uscita ogni singolo pixel relativo a quella
linea. Infine si attende ancora il tempo d, Front Porch, dove il segnale RGB è ancora una volta pilotato a 0 Volt in attesa del
successivo hsync. Il grafico della sincronia verticale è il medesimo solamente che l’arrivo del vsync significa la fine di un frame e
l’inizio del successivo.
IMPLEMENTAZIONE: LOGICA DI CONTROLLO
La maggior parte del progetto, sia per quanto riguarda la logica di funzionamento che per la visualizzazione su schermo, è stata
inserita in un unico file VHDL chiamato myArkanoid. Tale modulo si occupa della sincronizzazione VGA, della gestione degli input
switch e dei display a 7 segmenti, del movimento e della resa grafica delle componenti del gioco.
SINCRONIZZAZIONE VGA:
Il segnale viene generato grazie ad un contatore h_cnt che viene incrementato ogni colpo di clock ( 25Mhz ). Il segnale h_sync
viene pilotato in base al valore che assume h_cnt, per poter rispettare i tempi dello standard VGA 640x480.
In particolare h_cnt varia da 0 a 799, un intervallo di valori corrispondente al periodo totale della linea, cioè 31,7 µs (a+b+c+d nel
grafico in Figura 3); il segnale h_sync si mantiene alto per h_cnt che varia tra 0 e 659, viene posto a 0 quando h_cnt assume
valori compresi tra 660 e 755, poi viene nuovamente settato a 1 fino a 799. A differenza del grafico precedentemente illustrato,
questa routine inizializza il contatore a 0 nel momento dell’inizio del Display Interval, spostando Back Porch dopo il Front Porch.
In questo modo il funzionamento del sincronismo non cambia, ma permette di leggere direttamente in h_cnt il valore della
colonna attuale ( column).
Figura 4 - Diagramma temporale del segnale di sincronismo orizzontale (nell'implementazione usata)
In maniera analoga avviene per quanto riguarda la sincronia verticale, in particolare v_cnt varia da 0 a 524, un intervallo di valori
corrispondente al periodo totale della schermata, cioè 16.6 ms; il segnale v_sync si mantiene alto per v_cnt che varia tra 0 e 492,
viene posto a 0 quando v_cnt assume i valori 493 e 494, poi viene nuovamente settato a 1 fino a 524.
Esiste un ulteriore segnale interno, video_en ossia video enabled, il quale è attivo solo se h_cnt è minore di 640 e v_cnt è minore
di 480. Pertanto l’informazione sul colore del pixel viene ricopiata dall’ingresso all’uscita solo quando video_en è settato a uno,
cioè solo se ci si trova all’interno dello schermo attivo.
In appendice A il frammento di codice che realizza quanto visto.
INPUT SWITCH:
Per comandare il movimento della navicella sono stati utilizzati due dei quattro switch Push Button presenti sulla scheda. Tali
switch mantengono lo stato logico fino a che non vengono rilasciati e quindi sono ideali per realizzare quanto necessario.
Altri due Toggle Switch sono stati invece utilizzati per il Reset del gioco e la Pausa.
DECODER DISPLAY A 7 SEGMENTI:
Per pilotare il display a 7 segmenti è stato realizzato un semplice decoder atto a creare una corrispondenza biunivoca tra
simbolo visualizzato sul display e il numero intero corrispondente alle vite rimanenti. Il codice è molto semplice e si riassume con
un banale statement case. In appendice A il frammento di codice che lo realizza.
DIVISORE DI FREQUENZA:
Il modo più semplice per realizzare un divisore di frequenza è quello di realizzare un contatore che fornisce un segnale in uscita
solamente per alcuni valori. In questo caso particolare è sufficiente un contatore a 1 bit: se il contatore conta ad ogni fronte di
salita (o discesa) infatti, otteniamo già il clock a frequenza dimezzata. In appendice A il frammento di codice che lo realizza.
IMPLEMENTAZIONE: ELEMENTI GRAFICI E ANIMAZIONI
Ogni elemento grafico è stato realizzato andando ad accendere o meno i pixel che lo compongono in una determinata posizione
dello schermo. Ad esempio per realizzare un mattoncino, è necessario fornirne gli estremi v_cnt e h_cnt:
IF ((v_cnt >=195) AND (v_cnt <= 204) AND (h_cnt >= 61) AND (h_cnt <= 110)) THEN red3_signal <= '0'; red2_signal <= '1'; red1_signal <= '1'; red0_signal <= '0'; green3_signal <= '0'; green2_signal <= '1'; green1_signal <= '1'; green0_signal <= '0'; blue3_signal <= '0'; blue2_signal <= '1'; blue1_signal <= '1'; blue0_signal <= '0';
END IF;
Per capire meglio il significato di v_cnt e h_cnt li si consideri come righe e colonne: se il pixel si trova tra le righe 195 e 204 e tra
le colonne 61 e 110, allora viene acceso secondo la colorazione fornita specificata(grigio scuro).
Appare subito evidente come sia complicata la realizzazione di linee oblique o curve. Nel dettaglio le configurazioni per la resa
grafica degli altri oggetti in scena.
LA SFERA:
Quanto visto in precedenza, come introduzione riguardo alla resa grafica del mattoncino, rappresenta la modalità di disegno di
un oggetto statico. Nel gioco, la sfera deve muoversi nello schermo, rimbalzare sulle pareti, sugli ostacoli e sui mattoncini. Per
rendere possibile il movimento, anziché disegnare la sfera tramite
punti fissi, si utilizzano delle variabili che vengono aggiornate
continuamente in base ad un contatore che divide il clock
affinché il movimento avvenga ad una velocità umanamente
ragionevole. In figura lo schema utilizzato per la resa della sfera:
si notano le variabili ballPositionV e ballPositionH che
rappresentano la posizione della sfera in ogni istante. La sfera ha
due componenti di movimento: orizzontale ballMovementH e
verticale ballMovementV. Tali variabili assumono 3 valori: 0
movimento negativo, verso l’alto nel caso del movimento
verticale e verso sinistra nel caso del movimento orizzontale; 1
movimento positivo, verso il basso e verso destra; 2 situazione di
STOP, prima di partire o alla fine della partita. Tali variabili
servono per discriminare l’aumento o la diminuzione dei valori
caratterizzanti la posizione della sfera negli istanti di cambio
direzione; infatti se ad esempio la sfera si muove dall’alto verso il basso il valore di ballPositionV aumenterà fino al momento in
cui la sfera stessa si scontra con un ostacolo: a tal punto il valore di ballPositionV diminuisce in modo analogo. Similmente
accade per il movimento orizzontale. Per creare il movimento in diagonale, ad ogni refresh della posizione della pallina,
dovranno variare contemporaneamente sia la componente di movimento orizzontale che quella verticale. In particolare per
quanto riguarda il caso in questione si sono adottati diversi angoli di impatto in base all’effetto impresso alla sfera dal
movimento della navicella. È evidente che per variare l’angolo descritto dalla traiettoria della sfera, bisogna variare l’incremento
di una delle due componenti. Si è scelto di utilizzare la variabile angle che varia l’incremento per quanto riguarda la componente
verticale.
Figura 6 - Componenti del movimento della sfera
La figura 7 mostra la variazione delle componenti del movimento della sfera nei vari casi presi in esame. Il valore angle
sopraccitato varia quindi tra 1 e 3 a creare gli angoli di 45°, 60° e 72° circa in modulo. Una variazione di questo tipo comporta
inevitabilmente una variazione anche della velocità di movimento della sfera. Questo perché aumentando il numero di pixel che
vengono saltati da una posizione all’altra, in un istante temporale la sfera si è muove di più. Tale caratteristica(divenuta poi tale
ma nata come un errore) peraltro era presente anche nel videogioco originale. Un metodo semplice per sopperire a questo
Figura 5 - Realizzazione grafica della sfera
errore è basato sulla variazione della velocità della sfera a seconda dell’angolo. Come accennato infatti la sfera varia la sua
posizione a intervalli regolari cadenzati da un contatore che suddivide la frequenza di clock. Se si variasse dinamicamente i l
valore a cui si azzera il contatore, variabile ballSpeed nel sorgente, in base all’angolo di impatto della pallina, ne risulterebbe una
velocità adattata al movimento della sfera. Una prova in tal senso è stata fatta e rimane commentata all’interno del codice.
Tuttavia si è notato che rallentando la frequenza di aggiornamento della sfera, il movimento della stessa risulta meno fluido e di
conseguenza la resa finale è meno piacevole alla vista.
Il codice che realizza quanto descritto è abbastanza complesso e si rimanda al sorgente completo per un’analisi più specifica.
Volendo semplificarlo al massimo al caso di una sfera che rimbalza, sempre mantenendo il medesimo angolo e a velocità
costante, a contatto con i bordi dello schermo, eccone il sorgente:
IF(ballMovementV = 1) THEN IF (ballPositionV = 450) --Pallina Colpisce fondo schermo
THEN ballMovementV:=0; ballPositionV:= ballPositionV-1; ELSE ballPositionV:= ballPositionV+1; END IF; END IF;
IF(ballMovementV = 0) THEN IF (ballPositionV =30) --Pallina Colpisce bordo alto dello schermo
THEN ballMovementV:=1; ballPositionV:= ballPositionV+1; ELSE ballPositionV:= ballPositionV-1; END IF; END IF;
Analogamente per quanto riguarda il movimento orizzontale.
LA NAVICELLA:
Considerazioni simili a quelle del movimento della sfera, con la differenza che la navicella ha solamente la componente
orizzontale del movimento. Ecco come è stata realizzata la navicella:
Figura 7 - Realizzazione grafica della navicella
Come accennato, in base al movimento della navicella all’istante dell’impatto con la sfera, a quest’ultima viene impresso un
effetto che ne varia la traiettoria. Inoltre i lati della navicella sono posti a 45 gradi e nel caso in cui la sfera impattasse questa
parte di navicella, il movimento verrebbe completamente invertito facendole ripercorrere la stessa traiettoria al contrario. La
figura 9 mostra alcuni degli angoli d’impatto.
Figura 8 - Angoli d'impatto
BORDI E SCRITTE:
Per rendere più piacevole la grafica del gioco, si sono creati dei bordi per delimitare la zona di movimento della sfera. Inoltre al
di sotto della navicella si è disegnata una grafica che ricorda uno specchio d’acqua ondulato.
Sempre con la stessa finalità si è inserita l’intestazione che contiene l’autore, l’università, il corso per il quale è stata realizzata
questa tesina, il titolo del gioco e la scheda su cui si è lavorato. Per disegnare le lettere si è proceduto come visto in precedenza,
ecco a titolo di esempio come è stata realizzata la “S” e il codice corrispondente:
IF (((v_cnt = 300 OR v_cnt = 305 OR v_cnt = 310)) AND ((h_cnt >=Pause_s_Position+1) AND (h_cnt <= Pause_s_Position+3)))
OR (((v_cnt >= 301 AND v_cnt<=304) OR v_cnt = 309) AND (h_cnt = Pause_s_Position))
OR (((v_cnt >= 306 AND v_cnt<=309) OR v_cnt = 301)AND(h_cnt = Pause_s_Position+4))
THEN red3_signal <= '0';red2_signal <= '0';red1_signal <= '0'; red0_signal <= '0';green3_signal <= '1';green2_signal <= '0'; green1_signal <= '0'; green0_signal <= '0'; blue3_signal <= '1'; blue2_signal <= '0'; blue1_signal <= '0'; blue0_signal <= '0'; END IF;
SFUMATURE:
Poiché sono disponibili 4bit per ogni colore, sono a disposizione 212
colori ossia 4096 colori. La gradazione di colore è data
dall’insieme dei segnali red0, red1, red2, red3 ,green0, green1, green2, green3,blue0, blue1,blue2 e blue3. Ad esempio con la
combinazione:
red3_signal <= '0'; red2_signal <= '1'; red1_signal <= '1'; red0_signal <= '0';
green3_signal <= '0'; green2_signal <= '1'; green1_signal <= '1'; green0_signal <= '0';
blue3_signal <= '1'; blue2_signal <= '1'; blue1_signal <= '1'; blue0_signal <= '0';
si ottiene una gradazione di azzurro.
Sfruttando alcune delle variazioni di colore si sono creati diversi gradienti per simulare i riflessi nei bordi, nel mare sottostante,
nella navicella e nella sfera.
SCHEMATICO FINALE E PIN ASSIGNMENT
Figura 9 - schematico finale
Ecco come appare il file di design dello schematico finale. Come già detto, per l’implementazione si è scelto di concentrare la
maggior parte della logica in un unico blocco, myArkanoidVHDL. Nella figura sono evidenziati anche i pin di destinazione nel
FPGA.
PROGRAMMAZIONE DELLA BOARD: MODALITÀ AS
Una volta terminato il progetto ed eseguite le fasi di compilazione, analisi e sintesi del circuito e pin assignment, si procede con
la programmazione della scheda. Le modalità utilizzate sono state 2:
JTAG in fase di debug, comodo in quanto il bitstream (.sof) viene scaricato al volo nel FPGA e vi rimane fino allo
spegnimento della board;
AS, Active Serial Programming: in questo caso il file del programma (.pof) viene scritto nella eeprom EPCS4 montata
sulla board e risiede in memoria fino a quando questa non viene riscritta. In questo modo all’avvio della scheda verrà
avviato automaticamente il codice presente sulla EPCS4. Lo schema a blocchi seguente mostra la fase di
Programmazione, si noti lo switch RUN/PROG in posizione PROG, e la successiva fase di caricamento dalla EPCS4
all’FPGA:
Figura 10 - Schema di configurazione Attiva Seriale
Si tralascia la descrizione della modalità JTAG che è trattata in altri tutorial e ci si sofferma sulla programmazione attiva seriale
che richiede anche alcune configurazioni preliminari: infatti il file .pof va preparato in base al tipo di eeprom montata sulla
scheda. In particolare la scheda DE1 monta una eeprom da 512Kbyte, EPCS4.
Procediamo dunque con le impostazioni necessarie. Cliccando su AssignmentsDevice appare la seguente finestra:
Cliccando poi su Device and Pin Options e selezionando la tab Configurations appare:
Come indicato in figura, selezionare Active Serial per quanto riguarda il Configuration Scheme e la eeprom EPCS4 come device.
A questo punto ogni volta che il software compilerà e assemblerà il codice, verrà creato un file .pof di 512Kbyte compatibile con
la EPCS4 e quindi non rimane che programmarla. Cliccando su ToolsProgrammer apparirà la seguente:
1. Selezionare la modalità Active Serial Programming.
2. Selezionare il device di destinazione, l’EPCS4 cliccando su Add Device.
3. Selezionare il file .pof sorgente cliccando su Add File.
A questo punto si procede con la programmazione vera e propria cliccando su Start sempre nella finestra del programmatore. È
importante assicurarsi che sia selezionato USB-Blaster come Hardware di Programmazione, in caso contrario va selezionato
tramite l’apposito bottone Hardware setup. Inoltre, dato che stiamo programmando la EEPROM lo switch RUN/PROG va posto
nella posizione PROG. Al termine della programmazione, va spenta la scheda, rimesso lo switch RUN/PROG in posizione RUN e
riaccesa la scheda che avvierà la configurazione appena scritta sulla EPCS4.
COMANDI DI GIOCO
Data la semplicità del gioco, i comandi necessari sono pochi:
PUSH BUTTON SWITCH KEY0 : Muove la navicella verso destra;
PUSH BUTTON SWITCH KEY1 : Muove la navicella verso sinistra;
TOGGLE SWITCH SW1 : Mette in pausa il gioco
TOGGLE SWITCH SW2 : Resetta il gioco.
Figura 11 - Comandi di Gioco
Per completare la distruzione del muro di mattoncini il giocatore ha a disposizione 3 vite che corrispondono a 3 navicelle.
Quando una navicella viene distrutta, la sfera viene riposizionata nella posizione iniziale e dopo circa 2 secondi riparte con
direzione casuale; in questo lasso di tempo la navicella non potrà muoversi.
Figura 12 - Screenshot del gioco
APPENDICE A: PORZIONI SIGNIFICATIVE DI CODICE SORGENTE
SINCRONIZZAZIONE VGA:
--Horizontal Sync --Reset Horizontal Counter IF (h_cnt = 799) THEN h_cnt <= "0000000000"; ELSE h_cnt <= h_cnt + 1; END IF; --Generate Horizontal Sync IF (h_cnt <= 755) AND (h_cnt >= 659) THEN h_sync <= '0'; ELSE h_sync <= '1'; END IF; --Vertical Sync --Reset Vertical Counter
IF (v_cnt >= 524) AND (h_cnt >= 699) THEN v_cnt <= "0000000000"; ELSIF (h_cnt = 699) THEN v_cnt <= v_cnt + 1; END IF; --Generate Vertical Sync IF (v_cnt <= 494) AND (v_cnt >= 493) THEN v_sync <= '0'; ELSE v_sync <= '1'; END IF; --Generate Horizontal Data IF (h_cnt <= 639) THEN horizontal_en <= '1'; --column <= h_cnt; ELSE horizontal_en <= '0'; END IF; --Generate Vertical Data IF (v_cnt <= 479) THEN
vertical_en <= '1'; --row <= v_cnt; ELSE vertical_en <= '0'; END IF;
DECODER DISPLAY A 7 SEGMENTI:
case vite is when 0 => leds1 <= "0111111"; when 1 => leds1 <="0000110”; when 2 => leds1 <= "1011011”; when 3 => leds1 <="1001111”; when others => NULL;
end case;
DIVISORE DI FREQUENZA:
ENTITY dimezzaClock IS PORT (clock_f : IN STD_LOGIC; clock_f_mezzi : OUT STD_LOGIC); END dimezzaClock; ARCHITECTURE comportamento OF dimezzaClock IS SIGNAL conta : STD_LOGIC_VECTOR(0 DOWNTO 0); BEGIN PROCESS (clock_f) BEGIN IF clock_f = '0' AND clock_f'event THEN conta <= conta + 1; END IF; END PROCESS; clock_f_mezzi <= conta(0); END comportamento;
BIBLIOGRAFIA
Sincronizzazione VGA: http://www.pyroelectro.com/tutorials/vhdl_vga/
Arkanoid Game: http://en.wikipedia.org/wiki/Arkanoid
Terasic DE1 Development Board Manuals: http://www.terasic.com.tw/cgi-bin/page/archive.pl?Language=English&No=83
Altera Web Site: http://www.altera.com/