Obsah
1. Kouzlo minimalismu: vývoj her a grafických i zvukových dem pro herní konzoli NES
2. Slavná osmibitová herní konzole NES
3. Architektura herní konzole NES
4. Základní technické parametry herní konzole NES
5. Osmibitový mikroprocesor Ricoh 2A03
6. Další podpůrné moduly integrované do čipu 2A03/2A07
7. Zvukový subsystém herní konzole NES
8. Generátory obdélníkových signálů a trojúhelníkového signálu
10. Adresy všech řídicích registrů zvukového subsystému
12. Assemblery a překladače pro NES
13. Komplexní vývojová prostředí
14. Základní programové vybavení pro vývoj her pro NE
17. Kostra programu pro NES vytvořená v céčku
18. Kostra programu pro NES vytvořená v assembleru
19. Repositář s demonstračními příklady
1. Kouzlo minimalismu: vývoj her a grafických i zvukových dem pro herní konzoli NES
Na sérii článků o vývoji her (či spíše hříček) pro osmibitovou herní konzoli Atari 2600 s využitím Batari Basicu [1] [2] [3] dnes nepřímo navážeme. Řekneme si totiž, jakým způsobem je možné vyvíjet hry a grafická či hudební dema pro slavnou a přelomovou herní konzoli NES (Nintendo Entertainment System). Tato konzole již nemá tak omezené HW prostředky jako Atari 2600, což programátorům umožňuje tvorbu složitějších her (Legend of Zelda, Castlevania či Metroid) a dostupné systémové prostředky (kapacita ROM i RAM) jsou vlastně tak velké, že se dá s úspěchem použít i programovací jazyk C. V tomto miniseriálu se zaměříme jak na použití C, tak i assembleru.
Existuje několik rozumných i nerozumných důvodů, proč se vlastně zabývat dnes již historickou herní konzolí:
- Jedná se o stroj, na kterém má programátor přímý přístup k hardware, a to bez jakýchkoli pomocných prostředků – žádná ROM s BIOSem či dokonce s operačním systémem. Vše je zapotřebí řešit, dokonce i správně nastavit přerušovací vektory atd.
- Lze získat reálné výsledky, což mi přijde jako více produktivní činnost, než se zúčastnit různých code golfů (ale to je pochopitelně čistě subjektivní názor), nebo používat různé jednodeskové minipočítače, kde lze většinou jen blikat LED.
- Komunita okolo NESu je stále aktivní a velmi přátelská. Vzniká i velké množství retroher.
- V případě NESu, a obecně osmibitových herních konzolí i osmibitových domácích mikropočítačů, se jedná o jednu z mála platforem, kde má význam ve větší míře používat assembler.
- Mikroprocesor MOS 6502 je navržený minimalisticky, má jen několik desítek instrukcí a práce s ním může být zábavná (samozřejmě opět čistě subjektivní popis).
- Pro vývoj jsou zapotřebí jen minimální prostředky – assembler, popř. překladač céčka, editor grafiky, textový editor a emulátor herní konzole. To je velmi osvěžující, zejména v době obrovských IDE i „nekonečně“ tranzitivně závislých knihoven v běžných moderních aplikacích.
2. Slavná osmibitová herní konzole NES
V úvodní části článku se budeme zabývat popisem osmibitové herní konzole nazvané Nintendo Entertainment System, zkráceně NES, která byla navržena a následně vyráběna společností Nintendo Corporation. Tato firma se již před uvedením NESu na trh zabývala konstrukcí video automatů a taktéž jednoúčelových herních konzolí s jedním či dvěma LCD (v našich zemích jsou známější podobné konzole vyráběné v SSSR a mnohdy upravované pro místní podmínky – odlišné postavičky atd.). Stejně jako herní konzole firmy Sega, i konzole NES pochází z Japonska, kde se prodávala pod názvem Family Computer neboli Famicom (pod stejným názvem se ostatně tato konzole prodávala i v některých dalších asijských zemích).
Obrázek 1: Osmibitová herní konzole Nintendo Entertainment System (NES).
Jedná se o nejúspěšnější osmibitovou herní konzoli vůbec, a to jak z pohledu celkového počtu prodaných kusů, tak i při pohledu na časové období, kdy byla tato konzole vyráběna a prodávána, protože se odhaduje, že bylo prodáno téměř 62 milionů kusů tohoto zařízení a navíc vzniklo přibližně padesát více či méně kompatibilních klonů NESu. A navíc byly pro tuto herní konzoli vyvinuty herní série, které vlastně pokračují dodnes (například …). Mimochodem je 62 milionů v dobovém srovnání velmi vysoké číslo, a to například i v porovnání s další (již popsanou) úspěšnou herní konzolí Atari 2600, jíž bylo prodáno celkem 30 milionů (do obou čísel nejsou započítány klony konzole, které většinou nebyly korektně licencovány).
Obrázek 2: K herní konzoli NES postupně vzniklo velké množství různých přídavných zařízení a doplňků, například rozhraní pro MIDI či disketová jednotka používající speciální diskety o průměru dva palce. Zajímavý byl taktéž modem prodávaný zejména v Japonsku, který měl být používán především pro hraní loterií.
Zajímavá je i „morální životnost“ NESu, protože tato konzole byla představena již v roce 1983 v Japonsku a i když byl oficiálně prodej NESu ukončen „již“ v roce 1995 (tj. po dvanácti letech), ve skutečnosti výroba pokračovala až do roku 2003 a ještě do roku 2007 nabízela firma Nintendo možnost oprav NESu (posléze byla tato nabídka stažena, protože již nebyly k dispozici příslušné náhradní díly). Právě na příkladu NESu a taktéž herní konzole Atari 2600 je možné ukázat, že se vývoj herních konzolí poměrně podstatným způsobem odlišuje od vývoje osobních počítačů a je v ostrém kontrastu například s překotným „morálním zastaráváním“ mobilních telefonů (kde je rozdíl asi nejvíce patrný).
Obrázek 3: Rozhraní pro MIDI určené pro NES.
Není tedy divu, že i přes některé problematické rysy NESu (ochranné prvky a zpočátku i striktní politika prodeje her třetích stran) se dosti úspěšně rozvinula tvorba „moderních retroher“ určených právě pro tuto herní konzoli.
3. Architektura herní konzole NES
Ještě předtím, než vykreslíme první snímek hry nebo zahrajeme první tón, je nutné alespoň do určité míry porozumět interní architektuře herní konzole NES. Interně se sice jedná o poměrně jednoduché zařízení s minimálním počtem čipů (což je s ohledem na tržní segment logické), ovšem zejména grafický subsystém může zpočátku vypadat poměrně komplikovaně, protože se v NESu nepoužívá přímočará bitmapová grafika, na jakou jsme zvyklí dnes například při použití knihoven SDL či SDL2. Samozřejmě je výsledkem činnosti grafického subsystému rastrový obrázek vykreslený na televizoru (či dnes spíše na monitoru připojeného k počítači s emulátorem NESu), ovšem tento rastrový obrázek vzniká interpretací (či zpracováním) několika tabulek, či možná lépe řečeno polí. Nicméně grafický subsystém NESu byl ve skutečnosti navržen velmi pečlivě a s ohledem na to, že se jedná o herní konzoli a nikoli o obecný domácí mikropočítač, který je sice univerzální, ovšem tato univerzalita znamená, že se prakticky v každém ohledu musely udělat ústupky (což je ovšem téma na samostatný článek).
Obrázek 4: Zjednodušené schéma architektury herní konzole NES.
4. Základní technické parametry herní konzole NES
Konstruktéři herní konzole NES stáli před podobným problémem, jako konstruktéři všech dalších osmibitových konzolí: jakým způsobem dosáhnout uspokojivého výpočetního výkonu, dobrých grafických i zvukových schopností, a to za co nejnižší výrobní cenu, která je samozřejmě ve velmi konkurenčním prostředí herních konzolí jedním z nejdůležitějších parametrů. Na jednu stranu bylo snahou ušetřit co nejvíce systémových prostředků, na stranu druhou měla být NES minimálně o třídu lepší (z pohledu grafiky i zvuků) než konkurenční herní konzole, tedy především dosluhující Atari 2600, ale především konzole z třetí generace Sega Master System, Atari 7800 a SG-1000.
Výsledkem výše zmíněné snahy o dobré vybalancování výrobní ceny a schopností konzole byl systém se čtyřmi (pouhými čtyřmi!) hlavními čipy: osmibitovým mikroprocesorem Ricoh 2A03 (de facto upravený MOS 6502) obsahujícím i zvukový generátor, dále čipem RP2C02, resp. RP2C07 (PPU) zajišťujícím generování grafického obrazu (nazývá se PPU) a dvojicí paměťových čipů. Jeden z těchto paměťových čipů přitom tvořil programovou a datovou RAM, druhý čip sloužil grafickému procesoru pro uložení vzorků, z nichž se skládalo herní pole (playfield, ovšem nenechte se zmást i termínem background) a sprity, popř. i další důležité informace nutné pro vygenerování grafického obrazu.
Obrázek 5: Herní série The Legend of Zelda
Systém Nintendo Entertainment System samozřejmě podporoval (přesněji řečeno přímo vyžadoval) použití zásuvných modulů s hrami či jinými aplikacemi. Tyto moduly mohly obsahovat jak paměť ROM/EPROM s binárním kódem hry i se všemi statickými daty (maximální adresovatelná kapacita dosahovala 32 kB, zvýšit ji bylo možné klasicky přes paměťové banky), tak i paměť RAM, jejíž typická kapacita byla 8 kB. Pomocí zásuvného modulu bylo možné rozšířit i kapacitu video paměti. Schéma zapojení jedné z variant herní konzole Nintendo Entertainment System je dostupné například na této adrese: http://nesdev.parodius.com/Ntd_8bit.jpg. Povšimněte si především způsobu vzájemného propojení mikroprocesoru (CPU) s grafickým procesorem (PPU) pomocí datové sběrnice a šesti bitů adresové sběrnice i toho, jak jsou od sebe odděleny oba čipy RAM (ve druhém případě je adresa zachycena do latche LS 373 s využitím signálu ALE).
5. Osmibitový mikroprocesor Ricoh 2A03
Ústředním čipem, na němž je postavena herní konzole NES, je mikroprocesor Ricoh 2A03, popř. Ricoh 2A07 (tyto čipy se od sebe liší především odlišným časováním a jsou určeny pro různé televizní normy). Konstruktéři v případě mikroprocesoru 2A03/2A07 vsadili na osvědčenou jistotu, protože tento čip je založen na jádru oblíbeného mikroprocesoru MOS 6502, jenž byl v různých variantách použit v několika dalších osmibitových herních konzolích (Atari 2600, Atari 5200, Atari 7800) a taktéž v mnoha domácích osmibitových mikropočítačích (Atari 400/800/800XL/130XE a varianty, Apple I, Apple II, BBC Micro, Commodore C64…) – je to jeden ze tří nejúspěšnějších dobových mikroprocesorů. V případě čipů 2A03/2A07 však došlo k určitému zjednodušení jádra původního MOS 6502; chyběl například nepříliš často používaný režim pro práci s BCD čísly, kdy se v jednom bajtu ukládaly dvě číslice 0–9, tj. rozsah ukládaných hodnot byl 00 až 99 a nikoli 0 a 255. Původní mikroprocesor MOS 6502 mohl být do tohoto režimu přepnut pomocí instrukce SED, bližší informace o tomto režimu lze najít na adrese http://www.6502.org/tutorials/decimal_mode.html.
Obrázek 6: Mikroprocesor Ricoh 2A03.
6. Další podpůrné moduly integrované do čipu 2A03/2A07
Zatímco jádro mikroprocesoru bylo nepatrně zjednodušeno, byly na čip Ricoh 2A03/2A07 přidány další velmi důležité podpůrné moduly, díky jejichž existenci mohlo být redukováno celkové množství integrovaných obvodů, z nichž se herní konzole NES skládala (což významně snížilo cenu i zmenšilo množství reklamací). Jednalo se o následující moduly, z nichž některé budou popsány v navazujících kapitolách:
- Programovatelný „pomalý“ časovač se základní frekvencí 240 Hz, z níž mohly být odvozeny další frekvence (48, 60, 96, 120 a 192 Hz).
- Modul pro přímý přístup do paměti a přenosy dat (DMA).
- Dva programovatelné generátory obdélníkového signálu používané pro tvorbu zvuků.
- Jeden generátor trojúhelníkového signálu, taktéž používaný pro tvorbu zvuků.
- Konfigurovatelný generátor šumu tvořený posuvným registrem se zpětnou vazbou.
- Modul pro přehrávání samplů (D/A převodník).
Jen pro zajímavost se podívejme na zapojení vývodů:
___ ___ |o \/ | ROUT <--- 01] [40 <--- VCC COUT <--- 02] [39 ---> $4016W.0 /RES ---> 03] [38 ---> $4016W.1 A0 <--- 04] [37 ---> $4016W.2 A1 <--- 05] [36 ---> /$4016R A2 <--- 06] [35 ---> /$4017R A3 <--- 07] [34 ---> R/W A4 <--- 08] [33 <--- /NMI A5 <--- 09] [32 <--- /IRQ A6 <--- 10] 2A03 [31 ---> PHI2 A7 <--- 11] [30 <--- --- A8 <--- 12] [29 <--- CLK A9 <--- 13] [28 <---> D0 A10 <--- 14] [27 <---> D1 A11 <--- 15] [26 <---> D2 A12 <--- 16] [25 <---> D3 A13 <--- 17] [24 <---> D4 A14 <--- 18] [23 <---> D5 A15 <--- 19] [22 <---> D6 VEE ---> 20] [21 <---> D7 |________|
Obrázek 7: Schéma pinů mikroprocesoru Ricoh 2A03.
7. Zvukový subsystém herní konzole NES
V této kapitole si řekneme základní informace o zvukovém subsystému herní konzole NES. Z předchozího textu již víme, že je tento subsystém přímo součástí integrovaného obvodu s mikroprocesorem 2A03/2A07. Zvuk či melodie mohly být tvořeny v pěti na sobě nezávislých kanálech (což je s ohledem na dobu vzniku hodně). Jedná se o dva generátory obdélníkového signálu s volitelnou amplitudou, frekvencí a střídou, dále o generátor trojúhelníkového signálu s volitelnou frekvencí a automaticky měněnou amplitudou, generátor šumu, u nějž bylo možné zvolit režim činnosti, amplitudu a frekvenci posunu a konečně o D/A převodník, který mohl být buď ovládán přímo programově, nebo bylo umožněno načítat zvukové vzorky (samply) uložené v paměti ROM či RAM. Zajímavé je, že zvukový subsystém neobsahoval žádné filtry, na rozdíl od (jednoduchých) filtrů nabízených čipem POKEY (Atari) či komplexnějších filtrů použitých v čipu SID (Commodore C64).
Obrázek 8: Super Mario Bros ve verzi pro NES.
Taktéž podpora pro tvarování obálky generovaných signálů byla pouze dosti jednoduchá (tuto funkcionalitu však neměl ani POKEY, kde se musela řešit programovými cestami). Čip 2A03/2A07 obsahoval dva piny, na které byl přiváděn výstup ze všech pěti zvukových kanálů. Na pin číslo 1 (ROUT) byl přiváděn výstup z obou generátorů obdélníkových signálů, zatímco výstup z ostatních třech kanálů byl přiváděn na pin číslo 2 (COUT). V herní konzoli NES byl výstup z obou kanálů sloučen a analogově sečten s mikrofonním vstupem (ten se příliš často nepoužíval) a výsledný analogový signál byl zesílen a dále zpracován.
Obrázek 9: Další screenshot hry Super Mario Bros.
8. Generátory obdélníkových signálů a trojúhelníkového signálu
Na ovládání nejjednodušší byly oba generátory obdélníkových signálů, u nichž bylo možné měnit zejména tři základní parametry: amplitudu (4 bity = 16 úrovní), frekvenci (přibližně 54,6 Hz až 12,4 kHz podle použité televizní normy, frekvenci bylo možné měnit plynule) a střídu (1:8, 1:4, 1:2, 3:4). U generátoru trojúhelníkového signálu se měnila jeho úroveň (4 bity = 16 úrovní) automaticky, ale programově bylo možné nastavit frekvenci změn signálu v rozsahu 27,3 Hz až 55,9 kHz (opět v závislosti na použité televizní normě). Generátor šumu si popíšeme v následující kapitole, takže nám zbývá už jen pátý kanál, jímž je D/A převodník. Ten bylo možné ovládat programově změnou jednoho řídicího registru (sedmibitová hodnota), popř. byla podporována funkce automatického přehrávání vzorků (samplů) z ROM, přičemž při načítání samplu došlo k pozastavení mikroprocesoru. Při přehrávání vzorků byla frekvence nastavitelná v rozsahu 4,2 kHz až 33,5 kHz (ovšem zvolit bylo možné jen šestnáct přednastavených frekvencí).
42Obrázek 10: Poslední screenshot ze hry The Legend of Zelda ve verzi pro NES.
9. Generátor šumu
Podobně jako zvukové čipy POKEY či AY-3–8910, obsahoval i zvukový subsystém herní konzole NES generátor šumu. Ten byl vytvořen pomocí patnáctibitového posuvného registru se zpětnou vazbou: vybrané dva bity posuvného registru byly přes hradlo XOR přesunuty na jeho začátek, přičemž se obsah registru mezitím posunul o jeden bit doleva. Zvukový výstup byl generován na základě aktuálního obsahu bitu s indexem 14. Posuvný registr mohl pracovat ve dvou režimech činnosti: „krátkém“ a „dlouhém“. Ve dlouhém režimu se generovala sekvence pseudonáhodných bitů s periodou 32767 vzorků (zbývající 32768 stav nemohl být použit, protože se jednalo o nulovou hodnotu spadající mimo generovanou sekvenci). V krátkém režimu byla perioda pseudonáhodného signálu kratší: 93 bitů/vzorků. Samozřejmě bylo taktéž možné měnit frekvenci posunu, a to přibližně v rozsahu 29,3 Hz až 447 kHz (opět v závislosti na tom, jaká televizní norma byla použita).
<======================= +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ v v _____ ^ v +-------| xor |--------------------------------------------------+ +------------|_____|
Obrázek 11: Konfigurace generátoru šumu v „dlouhém“ režimu.
<======================= +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+ v v _____ ^ v +-------| xor |-------------------------+ +-------------------------------------|_____|
Obrázek 12: Konfigurace generátoru šumu v „krátkém“ režimu.
10. Adresy všech řídicích registrů zvukového subsystému
Na závěr si ještě uvedeme mapu všech 22 řídicích registrů ovlivňujících funkci všech zvukových generátorů popsaných výše:
Adresa | Název řídicího registru | Význam |
---|---|---|
$4000 | SQ1_VOL | Duty and volume for square wave 1 |
$4001 | SQ1_SWEEP | Sweep control register for square wave 1 |
$4002 | SQ1_LO | Low byte of period for square wave 1 |
$4003 | SQ1_HI | High byte of period and length counter value for square wave 1 |
$4004 | SQ2_VOL | Duty and volume for square wave 2 |
$4005 | SQ2_SWEEP | Sweep control register for square wave 2 |
$4006 | SQ2_LO | Low byte of period for square wave 2 |
$4007 | SQ2_HI | High byte of period and length counter value for square wave 2 |
$4008 | TRI_LINEAR | Triangle wave linear counter |
$4009 | Not used | |
$400A | TRI_LO | Low byte of period for triangle wave |
$400B | TRI_HI | High byte of period and length counter value for triangle wave |
$400C | NOISE_VOL | Volume for noise generator |
$400D | Not used | |
$400E | NOISE_LO | Period and waveform shape for noise generator |
$400F | NOISE_HI | Length counter value for noise generator |
$4010 | DMC_FREQ | Play mode and frequency for DMC samples |
$4011 | DMC_RAW | 7-bit DAC |
$4012 | DMC_START | Start of DMC waveform is at address $C000 + $40*$xx |
$4013 | DMC_LEN | Length of DMC waveform is $10*$xx + 1 bytes (128*$xx + 8 samples) |
$4014 | OAM_DMA | Writing $xx copies 256 bytes by reading from $xx00-$xxFF and writing to $2004 (OAM data) |
$4015 | SND_CHN | Sound channels enable and status |
Obrázek 13: Jeden z trackerů pro herní konzoli NES: Nerd Tracker.
11. Grafický subsystém NESu
Grafický subsystém použitý v herní konzoli Nintendo Entertainment System byl v mnoha ohledech ještě zajímavější než její zvukový subsystém. Víme již, že herní konzole NES existovala ve variantě pro televizní normu NTSC i pro normu PAL. Konzole určené pro televizní normu PAL obsahovaly mikroprocesor 2A07 s hodinovou frekvencí 1,66 MHz, zatímco pro normu NTSC byly určeny konzole s mikroprocesorem 2A03 používající hodinovou frekvenci 1,79 MHz. I grafické čipy se lišily podle toho, pro jakou televizní normu byly určeny. Pro normu NTSC se používal čip RP2C02 se vstupní hodinovou frekvencí 5,37 MHz, zatímco pro normu PAL byl použit čip RP2C07 s frekvencí 5,32 MHz. Tyto čipy, označované taktéž zkratkou PPU, obsahovaly 256 interní paměti využívané systémem pro zobrazení spritů. Kromě toho přistupoval PPU k samostatnému čipu RAM o kapacitě dva kilobajty. V této paměti bylo uloženo větší množství datových struktur nesoucích informace o pozadí scény, o barvové paletě i o tvarech spritů. Ve skutečnosti však mohl PPU přistupovat i k paměti ROM umístěné na paměťovém modulu se hrou.
Obrázek 14: Barvová paleta používaná herní konzolí NES.
12. Assemblery a překladače pro NES
Vzhledem k tomu, že se mikroprocesor herní konzole NES lišil od původního čipu MOS 6502 jen v několika málo instrukcích, je možné pro vývoj her pro NES používat assembler nazvaný DASM, o němž jsme se zmínili v článcích o Batari BASICu. Tento assembler (přesněji řečeno cross assembler) je v současnosti pravděpodobně nejpoužívanější, a to i v oblasti dalších zařízení založených na procesorech řady MOS 6502. Existují či v nedávné minulosti existovaly však i další assemblery, například starší FASM, v minulosti poměrně populární X816 či P65, který je zajímavý především tím, že je naprogramován v Perlu a nikoli v jazyku C, podobně jako mnoho dalších assemblerů. Musím též doplnit informaci o disassemblerech: 6502 Simulator, TRaCER či NESrev – ten je psaný v Javě (v souvislosti s osmibitovými konzolemi jde o poměrně bizarní technologii :-) a jeho autorem je Kent Hansen.
Obrázek 15: Arkanoid pro herní konzoli NES – třetí obtížnost.
Ovšem díky poměrně velké kapacitě paměti ROM umístěné na cartridgích s hrami a taktéž díky tomu, že při programování grafického subsystému NESu není nutné provádět optimalizace na úrovni jednotlivých strojových cyklů (viz Atari 2600) se dnes mnoho vývojářů uchyluje k použití programovacího jazyka C. V této oblasti se velmi často používá cross compiler CC65 doplněný o hlavičkové soubory obsahující jména a adresy řídicích registrů NESu atd.
Obrázek 16: První místnost ve hře Flappy (verze pro NES).
13. Komplexní vývojová prostředí
Pro vývoj lze alternativně použít i nbasic (doplněný o nesasm) což je určitý hybrid mezi jazykem BASIC a assemblerem. Dialekt BASICu, který je zde použitý, například nepodporuje dynamickou alokaci paměti, proměnné jsou pouze lokální, pole se umisťují na zadanou absolutní adresu a je podporován inline assembler. Díky těmto omezením je převod programu z BASICu do assembleru MOS 6502 poměrně přímočarý, takže programátor může mít velmi dobrou představu, jakým způsobem se jednotlivé jazykové konstrukce přeloží a využijí. Mimochodem: nbasic a nesasm byl v roce 2004 dokonce využit i v univerzitním kurzu pro výuku programování počítačových her s využitím emulátoru NESu. Bližší informace a především informacemi (i vtipem) nabité slajdy najdete na adrese http://bobrost.com/nes/.
Obrázek 17: Scrollující prostředí hry Final Fantasy.
14. Základní programové vybavení pro vývoj her pro NES
Pro herní konzoli NES dokonce vzniklo i moderně pojaté integrované vývojové prostředí nazvané NESICIDE. Autory tohoto vývojového prostředí jsou Christopher Pow, Tim Sarbin a Benjamin Eriksson. V tomto IDE nalezneme některé nástroje známé i z jiných současných integrovaných vývojových prostředí, například správce projektů, programátorský editor apod. Ovšem navíc jsou přidány nástroje, které úzce souvisejí s možnostmi herní konzole NES. Příkladem může být integrovaný debugger, disassembler (+code browser) a emulátorNESu, jenž je možné spustit přímo v okně IDE. Vzhledem k poměrně komplikované struktuře grafické paměti popsané v navazujícím článku je integrované vývojové prostředí NESICIDE vybaveno i editorem a prohlížečem barvové palety NESu a taktéž možností „vizualizace“ obrazové paměti – je možné pracovat jak s uživatelsky definovanými znaky (součást pozadí), tak i se sprity. Díky tomuto nástroji je možné NESICIDE relativně snadno použít i pro modifikaci existujících her.
Obrázek 18: Screenshot vývojového prostředí NESICIDE.
15. Instalace ca65 a cc65
Instalace assembleru ca65 i céčkového překladače cc65 je na většině distribucí Linuxu snadná, neboť se jedná o balíčky umístěné přímo v repositářích dané distribuce. Příkladem může být Linux Mint:
$ sudo apt-get install cc65 Reading package lists... Done Building dependency tree Reading state information... Done Suggested packages: cc65-doc The following NEW packages will be installed: cc65 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 2 162 kB of archives. After this operation, 31,8 MB of additional disk space will be used. Get:1 http://archive.ubuntu.com/ubuntu focal/universe amd64 cc65 amd64 2.18-1 [2 162 kB] Fetched 2 162 kB in 5s (423 kB/s) Selecting previously unselected package cc65. (Reading database ... 291820 files and directories currently installed.) Preparing to unpack .../archives/cc65_2.18-1_amd64.deb ... Unpacking cc65 (2.18-1) ... Setting up cc65 (2.18-1) ...
Po dokončení instalace budou k dispozici tři nové nástroje (a několik podpůrných nástrojů).
V první řadě se jedná o assembler:
$ cc65 --version cc65 V2.18 - Ubuntu 2.18-1
Dále o překladač céčka:
$ ca65 --version ca65 V2.18 - Ubuntu 2.18-1
A využijeme i samostatný linker:
$ ld65 --version ld65 V2.18 - Ubuntu 2.18-1
16. Instalace emulátoru NESu
V současnosti existuje několik (řádově desítka) podporovaných a stále vyvíjených emulátorů konzole NES. Prozatím se spokojíme s jedním z nejjednodušších emulátorů, kterým je Nestopia. Opět se jedná o nástroj typicky dostupný ve standardních repositářích Linuxových distribucí:
$ sudo apt-get install nestopia Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: nestopia 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 885 kB of archives. After this operation, 3 933 kB of additional disk space will be used. 0% [Working]y Get:1 http://archive.ubuntu.com/ubuntu focal/universe amd64 nestopia amd64 1.50-1build1 [885 kB] Fetched 885 kB in 7s (119 kB/s) Selecting previously unselected package nestopia. (Reading database ... 292411 files and directories currently installed.) Preparing to unpack .../nestopia_1.50-1build1_amd64.deb ... Unpacking nestopia (1.50-1build1) ... Setting up nestopia (1.50-1build1) ... update-alternatives: using /usr/games/nestopia to provide /usr/bin/nes (nes) in auto mode Processing triggers for desktop-file-utils (0.24+linuxmint1) ... Processing triggers for mime-support (3.64ubuntu1) ... Processing triggers for hicolor-icon-theme (0.17-2) ... Processing triggers for doc-base (0.10.9) ... Processing 1 added doc-base file... Processing triggers for gnome-menus (3.36.0-1ubuntu1) ... Processing triggers for man-db (2.9.1-1) ...
17. Kostra programu pro NES vytvořená v céčku
Nyní, když již máme nainstalován jak assembler, tak i překladač programovacího jazyka C pro mikroprocesor MOS 6502 (a tím pádem i pro 2A03/2A07), si můžeme ukázat, jak by mohla vypadat kostra (základ) hry naprogramované pro NES. Prozatím si bez dalšího podrobnějšího popisu ukažme příklad, který byl vytvořen Dougem Frakerem a je dostupný na adrese https://github.com/nesdoug/01_Hello/blob/master/hello.c. Jedná se o „čisté céčko“, ovšem celá funkcionalita je postavena na již předpřipravených knihovnách, z×nichž některé části jsou naprogramovány v assembleru (jedinou výjimkou je klauzule #pragma):
/* simple Hello World, for cc65, for NES * writing to the screen with rendering disabled * using neslib * Doug Fraker 2018 */ #include "LIB/neslib.h" #include "LIB/nesdoug.h" #define BLACK 0x0f #define DK_GY 0x00 #define LT_GY 0x10 #define WHITE 0x30 // there's some oddities in the palette code, black must be 0x0f, white must be 0x30 #pragma bss-name(push, "ZEROPAGE") // GLOBAL VARIABLES // all variables should be global for speed // zeropage global is even faster unsigned char i; const unsigned char text[]="Hello World!"; // zero terminated c string const unsigned char palette[]={ BLACK, DK_GY, LT_GY, WHITE, 0,0,0,0, 0,0,0,0, 0,0,0,0 }; void main (void) { ppu_off(); // screen off pal_bg(palette); //load the BG palette // set a starting point on the screen // vram_adr(NTADR_A(x,y)); vram_adr(NTADR_A(10,14)); // screen is 32 x 30 tiles ppu_on_all(); // turn on screen while (1){ // infinite loop // game code can go here later. } }
18. Kostra programu pro NES vytvořená v assembleru
V kontextu tohoto miniseriálu bude zajímavější kostra programu vytvořená přímo v assembleru mikroprocesoru MOS 6502. Následující program byl získán úpravou kódu popsaného na stránce https://www.pagetable.com/?p=410. Prozatím si uvedeme jeho prakticky nejjednodušší možnou podobu, ovšem v dalším článku budou uvedeny všechny možné úpravy a pochopitelně i rozšíření. K samotnému zdrojovému kódu je dodáván i soubor link.cfg (jméno může být libovolné a použijeme ho při volání linkeru ld65), který obsahuje informaci o struktuře paměti NESu i názvy jednotlivých segmentů. Právě tyto názvy následně použijeme ve zdrojovém kódu assembleru, abychom nemuseli všude psát hexadecimální adresy:
MEMORY { ZP: start = $0000, size = $0100, type = rw; RAM: start = $0300, size = $0400, type = rw; HEADER: start = $0000, size = $0010, type = rw, file = %O, fill = yes; PRG0: start = $8000, size = $4000, type = ro, file = %O, fill = yes; CHR0a: start = $0000, size = $1000, type = ro, file = %O, fill = yes; CHR0b: start = $1000, size = $1000, type = ro, file = %O, fill = yes; } SEGMENTS { ZEROPAGE: load = ZP, type = zp; BSS: load = RAM, type = bss; INES: load = HEADER, type = ro, align = $10; CODE: load = PRG0, type = ro; VECTOR: load = PRG0, type = ro, start = $BFFA; CHR0a: load = CHR0a, type = ro; CHR0b: load = CHR0b, type = ro; }
; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 1 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "INES" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .code ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) ; nastavení řídicích registrů ldx #$00 stx $2000 ; nastavení PPUCTRL = 0 stx $2001 ; nastavení PPUMASK = 0 stx $4015 ; nastavení APUSTATUS = 0 ; čekání na vnitřní inicializaci PPU (dva snímky) wait1: bit $2002 ; test obsahu registru PPUSTATUS bpl wait1 ; skok, pokud je příznak N nulový wait2: bit $2002 ; test obsahu registru PPUSTATUS bpl wait2 ; skok, pokud je příznak N nulový ; vymazání obsahu RAM lda #$00 ; vynulování registru A loop: sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne loop ; po přetečení 0xff -> 0x00 konec smyčky ; čekání na dokončení dalšího snímku, potom může začít herní smyčka wait3: bit $2002 ; test obsahu registru PPUSTATUS bpl wait3 ; skok, pokud je příznak N nulový ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTOR" .addr nmi .addr reset .addr irq ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
Překlad z assembleru do objektového kódu se provede následovně:
$ ca65 example01.asm -o example01.o -l example01.list
Objektový kód se přeloží do „obrazu“ pro NES příkazem, v němž definujeme cestu k výše zmíněnému konfiguračnímu souboru linkeru:
$ ld65 -C link.cfg example01.o -o example01.nes
Výsledný soubor je již možné otevřít v emulátoru, který by ho měl načíst bez nahlášení chyby (ovšem obrazovka zůstane černá).
Ještě se pro zajímavost podívejme na soubor vygenerovaný assemblerem nazvaný example01.list. Jedná se o kontrolní výpis/opis zpracovávaného assemblerovského zdrojového kódu:
ca65 V2.18 - Ubuntu 2.18-1 Main file : example01.asm Current file: example01.asm 000000r 1 ; --------------------------------------------------------------------- 000000r 1 ; Definice hlavičky obrazu ROM 000000r 1 ; --------------------------------------------------------------------- 000000r 1 000000r 1 ; Size of PRG in units of 16 KiB. 000000r 1 prg_npage = 1 000000r 1 000000r 1 ; Size of CHR in units of 8 KiB. 000000r 1 chr_npage = 1 000000r 1 000000r 1 ; INES mapper number. 000000r 1 mapper = 0 000000r 1 000000r 1 ; Mirroring (0 = horizontal, 1 = vertical) 000000r 1 mirroring = 1 000000r 1 000000r 1 .segment "INES" 000000r 1 4E 45 53 1A .byte $4e, $45, $53, $1a 000004r 1 01 .byte prg_npage 000005r 1 01 .byte chr_npage 000006r 1 01 .byte ((mapper & $0f) << 4) | (mirroring & 1) 000007r 1 00 .byte mapper & $f0 000008r 1 .code 000000r 1 000000r 1 000000r 1 000000r 1 ; --------------------------------------------------------------------- 000000r 1 ; Blok paměti s definicí dlaždic 8x8 pixelů 000000r 1 ; --------------------------------------------------------------------- 000000r 1 000000r 1 .segment "CHR0a" 000000r 1 .segment "CHR0b" 000000r 1 000000r 1 000000r 1 000000r 1 ; --------------------------------------------------------------------- 000000r 1 ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU 000000r 1 ; 000000r 1 ; viz též https://www.pagetable.com/?p=410 000000r 1 ; --------------------------------------------------------------------- 000000r 1 000000r 1 ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) 000000r 1 000000r 1 .proc nmi 000000r 1 40 rti ; návrat z přerušení 000001r 1 .endproc 000001r 1 000001r 1 000001r 1 000001r 1 ; Obslužná rutina pro IRQ (maskovatelné přerušení) 000001r 1 000001r 1 .proc irq 000001r 1 40 rti ; návrat z přerušení 000002r 1 .endproc 000002r 1 000002r 1 000002r 1 000002r 1 ; Obslužná rutina pro RESET 000002r 1 000002r 1 .proc reset 000002r 1 ; nastavení stavu CPU 000002r 1 78 sei ; zákaz přerušení 000003r 1 D8 cld ; vypnutí dekadického režimu (není podporován) 000004r 1 000004r 1 A2 FF ldx #$ff 000006r 1 9A txs ; vrchol zásobníku nastaven na 0xff (první stránka) 000007r 1 000007r 1 ; nastavení řídicích registrů 000007r 1 A2 00 ldx #$00 000009r 1 8E 00 20 stx $2000 ; nastavení PPUCTRL = 0 00000Cr 1 8E 01 20 stx $2001 ; nastavení PPUMASK = 0 00000Fr 1 8E 15 40 stx $4015 ; nastavení APUSTATUS = 0 000012r 1 000012r 1 ; čekání na vnitřní inicializaci PPU (dva snímky) 000012r 1 2C 02 20 wait1: bit $2002 ; test obsahu registru PPUSTATUS 000015r 1 10 FB bpl wait1 ; skok, pokud je příznak N nulový 000017r 1 2C 02 20 wait2: bit $2002 ; test obsahu registru PPUSTATUS 00001Ar 1 10 FB bpl wait2 ; skok, pokud je příznak N nulový 00001Cr 1 00001Cr 1 ; vymazání obsahu RAM 00001Cr 1 A9 00 lda #$00 ; vynulování registru A 00001Er 1 95 00 loop: sta $000, x ; vynulování X-tého bajtu v nulté stránce 000020r 1 9D 00 01 sta $100, x 000023r 1 9D 00 02 sta $200, x 000026r 1 9D 00 03 sta $300, x 000029r 1 9D 00 04 sta $400, x 00002Cr 1 9D 00 05 sta $500, x 00002Fr 1 9D 00 06 sta $600, x 000032r 1 9D 00 07 sta $700, x ; vynulování X-tého bajtu v sedmé stránce 000035r 1 E8 inx ; přechod na další bajt 000036r 1 D0 E6 bne loop ; po přetečení 0xff -> 0x00 konec smyčky 000038r 1 000038r 1 ; čekání na dokončení dalšího snímku, potom může začít herní smyčka 000038r 1 2C 02 20 wait3: bit $2002 ; test obsahu registru PPUSTATUS 00003Br 1 10 FB bpl wait3 ; skok, pokud je příznak N nulový 00003Dr 1 00003Dr 1 ; vlastní herní smyčka je prozatím prázdná 00003Dr 1 game_loop: 00003Dr 1 4C rr rr jmp game_loop ; nekonečná smyčka (později rozšíříme) 000040r 1 .endproc 000040r 1 000040r 1 000040r 1 000040r 1 ; --------------------------------------------------------------------- 000040r 1 ; Tabulka vektorů CPU 000040r 1 ; --------------------------------------------------------------------- 000040r 1 000040r 1 .segment "VECTOR" 000000r 1 rr rr .addr nmi 000002r 1 rr rr .addr reset 000004r 1 rr rr .addr irq 000006r 1 000006r 1 000006r 1 000006r 1 ; --------------------------------------------------------------------- 000006r 1 ; Finito 000006r 1 ; --------------------------------------------------------------------- 000006r 1
19. Repositář s demonstračními příklady
Demonstrační příklad napsaný v assembleru, jenž je určený pro překlad pomocí ca65, byl uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Příklad si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | example01.asm | zdrojový kód příkladu | https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example01.asm |
2 | link.cfg | konfigurace segmentů pro linker ld65 | https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg |
3 | Makefile | Makefile pro překlad prvního příkladu | https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/Makefile |
20. Odkazy na Internetu
- NesDev.org
https://www.nesdev.org/ - How to Program an NES game in C
https://nesdoug.com/ - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - „Game Development in Eight Bits“ by Kevin Zurawel
https://www.youtube.com/watch?v=TPbroUDHG0s&list=PLcGKfGEEONaBjSfQaSiU9yQsjPxxDQyV8&index=4 - Game Development for the 8-bit NES: A class by Bob Rost
http://bobrost.com/nes/ - Game Development for the 8-bit NES: Lecture Notes
http://bobrost.com/nes/lectures.php - NES Graphics Explained
https://www.youtube.com/watch?v=7Co_8dC2zb8 - NES GAME PROGRAMMING PART 1
https://rpgmaker.net/tutorials/227/?post=240020 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - bee – The Multi-Console Emulator
http://www.thebeehive.ws/ - Nerdy Nights Mirror
https://nerdy-nights.nes.science/ - NES Development Day 1: Creating a ROM
https://www.moria.us/blog/2018/03/nes-development - How to Start Making NES Games
https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/ - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Nocash NES Specs
http://nocash.emubase.de/everynes.htm - Nintendo Entertainment System
http://cs.wikipedia.org/wiki/NES - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - NesDev
http://nesdev.parodius.com/ - 2A03 technical reference
http://nesdev.parodius.com/2A03%20technical%20reference.txt - NES Dev wiki: 2A03
http://wiki.nesdev.com/w/index.php/2A03 - Ricoh 2A03
http://en.wikipedia.org/wiki/Ricoh_2A03 - 2A03 pinouts
http://nesdev.parodius.com/2A03_pinout.txt - 27c3: Reverse Engineering the MOS 6502 CPU (en)
https://www.youtube.com/watch?v=fWqBmmPQP40 - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Nintendo Entertainment System (NES)
https://8bitworkshop.com/docs/platforms/nes/ - Question about NES vectors and PPU
https://archive.nes.science/nesdev-forums/f10/t4154.xhtml - How do mapper chips actually work?
https://archive.nes.science/nesdev-forums/f9/t13125.xhtml - INES
https://www.nesdev.org/wiki/INES - NES Basics and Our First Game
http://thevirtualmountain.com/nes/2017/03/08/nes-basics-and-our-first-game.html - Where is the reset vector in a .nes file?
https://archive.nes.science/nesdev-forums/f10/t17413.xhtml - CPU memory map
https://www.nesdev.org/wiki/CPU_memory_map - How to make NES music
http://blog.snugsound.com/2008/08/how-to-make-nes-music.html - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - MIDINES
http://www.wayfar.net/0×f00000_overview.php - FamiTracker
http://famitracker.com/ - nerdTracker II
http://nesdev.parodius.com/nt2/ - How NES Graphics work
http://nesdev.parodius.com/nesgfx.txt - NES Technical/Emulation/Development FAQ
http://nesdev.parodius.com/NESTechFAQ.htm