Pubblichiamo un contributo di Antonio Barba, sviluppatore software specializzato nel ramo videogiochi.
Tutti noi conosciamo, almeno di fama, il Nintendo DS. Lanciato verso la fine del 2004 in Giappone e Nord America (marzo 2005 in Europa), il Nintendo DS è, ad oggi, la console portatile più venduta al mondo – 144 milioni di unità a Dicembre 2010.
Per la sua progettazione, Nintendo riprende la piattaforma hardware del precendente Nintendo GameBoy Advance, il form factor dei Game&Watch anni ’80 ed aggiunge alcune importanti innovazioni, come il touchscreen, il microfono, la connessione WiFi e l’accelerazione hardware 3D.
Nitro Processor
Tutte le funzioni principali del Nintendo DS sono gestite da un System on Chip custom, chiamato Nitro Processor.
Al suo interno sono presenti ben 2 CPU con architettura di tipo ARM: un core ARM946E-S, che funge da CPU principale (Main processor) e opera alla frequenza di 67,028 MHz ed un secondo core (subprocessor) ARM7TDMI, che lavora a 33,514 MHz.
Le funzioni assegnate alle due CPU sono ben distinte e pertanto possiamo parlare di sistema AMP (Asymmetric Multi Processor). Nel SoC trova inoltre posto il settore grafico, costituito da 2 coprocessori quasi gemelli, chiamati rispettivamente 2D Graphics Engine A e B, ed il coprocessore denominato 3D Graphics Engine che opera in maniera particolare, appoggiandosi in parte alle funzioni del 2D Graphics Engine A.
Troviamo poi due coprocessori matematici, connessi all’ARM9 Bus, quindi utilizzabili solamente dal Main processor. Questi coprocessori matematici implementano le funzioni di divisione e di radice quadrata per numeri interi a 16 e 32 bit. Non vi è alcun supporto hardware per i calcoli in floating point, cosa abbastanza comune sulle console di vecchio tipo.
Completano il quadro un coprocessore sonoro dotato di mixer, alcuni Timer, vari banchi di memoria Ram ed un paio di controller DMA, uno per ciascun processore, dotati di 4 canali programmabili ciascuno.
Struttura della memoria
Nel DS la struttura della memoria è sorprendentemente complessa. Il Nitro Processor incorpora al suo interno 12KB di cache L1 per il Main processor, suddivisi in 8KB per le istruzioni e 4KB per i dati. A questa cache L1 si affianca un secondo livello di cache, che va gestito “manualmente” dal programmatore, quindi non è trasparente come la L1.
Questa particolare memoria si chiama TCM (Tightly Coupled Memory). Come la cache di primo livello, anche la TCM può essere letta e scritta senza interferire con il bus di sistema. Questo consente, ad esempio, di pianificare dei trasferimenti DMA e nel frattempo lavorare con codice e dati precedentemente caricati su TCM.
In realtà le TCM sono due: 32KB per le istruzioni e 16KB per i dati. Questo ricalca ovviamente il modello Harvard della CPU ARM9. La separazione tra dati e istruzioni consente di effettuare il fetch di una istruzione contemporaneamente ad una operazione di Load/Store dei dati, massimizzando l’efficienza della pipeline a 5 stadi dell’ARM9.
Questo vantaggio si perde ovviamente quando le istruzioni e i dati si trovano sulla Main Ram, che è situata esternamente al Nitro Processor. La Main Ram ammonta a 4 MB, ed è molto più lenta della TCM per vari motivi: è dotata di bus a 16bit anziché 32 bit, opera ad una frequenza di clock di 33,5 Mhz anziché 67, condivide in un unico spazio di indirizzi sia i dati che le istruzioni (conformandosi quindi all’architettura Von Neumann), negando quindi l’accesso parallelo a dati e istruzioni, e inoltre presenta dei tempi di accesso che variano da 2 a 5 cicli di clock, in base a vari fattori che vedremo in seguito.
A causa di questi problemi di accesso alla memoria, quando il Nintendo DS lavora sui dati presenti nei 4MB di Main Ram risulta mediamente più lento del GameBoy Advance. Il trucco per sfruttarne pienamente la potenza risiede in alcune tecniche importantissime, che mostrerò in seguito.
Il subprocessor non possiede né la cache di primo livello, né la TCM. Inoltre non è connesso fisicamente con la Main Ram. L’unica memoria RAM accessibile dall’ARM7 è costituita da un totale di 96 KB di memoria molto veloce, chiamata Working RAM.
La Working RAM è suddivisa a sua volta in 3 banchi, uno da 64 KB ad uso esclusivo dell’ARM7 e 2 banchi da 16KB che possono essere letti/scritti alternativamente dall’ARM7 e dall’ARM9, costituendo di fatto un meccanismo molto veloce di scambio dati tra i due processori. L’assegnazione dei due banchi da 16KB deve essere fatta manualmente dal programmatore, agendo su particolari registri del Nitro Processor.
La dotazione di memoria inoltre comprende 656 KB di memoria video, suddivisa in 4 banchi da 128 KB, 1 banco da 64KB, 1 banco da 32KB e 3 banchi da 16 KB. La gestione della memoria video è uno dei compiti più complessi che deve affrontare il programmatore durante la creazione di un videogame. I singoli banchi di VRAM possono, e devono, essere opportunamente configurati per ospitare immagini bitmap, texture, sprite, palette, ecc…, agendo su speciali registri di configurazione.
A complicare le cose contribuisce il fatto che ogni singolo banco può coprire una o più funzioni, ma non contemporaneamente, e inoltre bisogna decidere quali banchi assegnare al 2D Engine A, quali al 2D Engine B e quali al 3D Engine. Il 3D Engine dispone poi di 4 banchi speciali, non mappabili direttamente dalla CPU, che contengono rispettivamente i vertici (72KB x 2) ed i poligoni (52KB x 2) della scena 3D da renderizzare. Ulteriori 4 banchi speciali da 1KB ciascuno sono assegnati ai 2D Engine A e B, e contengono le descrizioni degli sprite OAM (coordinate, attributi, ecc..) e la cosiddetta Standard Palette (per compatibilità con il GameBoy Advance).
Per sfruttare correttamente e proficuamente una console del genere si presuppone, come si evince, una profonda conoscenza dell’hardware e soprattutto dei 3 Graphics Engine e dei modi con cui questi interagiscono con i banchi di memoria.
Sul fronte software è d’obbligo poi sapere che il DS non ospita un vero e proprio Sistema Operativo, ma la libreria Nintendo utilizzata per sviluppare i giochi contiene una serie di utilità, tra cui anche un minimo supporto a thread, mutex e semafori. La gestione del multitasking avviene comunque in manuale, tramite un meccanismo di multitasking cooperativo molto elementare.
Prossimamente parlerò dei coprocessori grafici, mostrando come questi contengano sostanzialmente tutte le funzioni “classiche” dei chip grafici prodotti a partire dai primi sistemi Atari fino al sopravvento dell’hardware 3D-only, basato cioè esclusivamente sull’uso di poligoni.
In particolare parlerò di come sia possibile realizzare complesse scene 2D con 4 background sovrapposti, dotati di parallax scrolling, 128 sprite a schermo, antialiasing e alpha blending, tutti dotati di effetti di rotazione e scala, senza intaccare minimamente la capacità di calcolo della CPU che resta libera al 99% di effettuare altri calcoli.
Parlerò inoltre dei dettagli delle due CPU e della sofisticata struttura a coprocessori del Nitro Processor, che in qualche modo rende il Nintendo DS molto simile sia all’architettura delle gloriose console casalinghe (ad esempio Super Nintendo e Nintendo 64), sia a famosi computer anni ’80-’90 come il Commodore Amiga.