Obsah
1. Kouzlo minimalismu potřetí: vývoj her a grafických i zvukových dem pro osmibitová Atari
2. Vývojové nástroje pro osmibitová Atari
3. Assemblery pro osmibitová Atari
4. Překladače jazyka C pro osmibitová Atari
5. Základní programové vybavení pro vývoj her pro Atari v současnosti
7. Praktická část: čím se liší vývoj pro osmibitová Atari od dnes běžného vývoje?
8. První demonstrační příklad: prostá nekonečná smyčka
9. Překlad do strojového kódu s vytvořením listingu
10. Konfigurace linkeru, spuštění linkeru s vytvořením výsledného souboru ve formátu xex
12. Druhý demonstrační příklad: změna barvy pozadí v textovém režimu
13. Symboly definované v souboru atari.inc
14. Třetí demonstrační příklad: využití symbolů definovaných v souboru atari.inc
15. Adresovací režimy mikroprocesoru MOS 6502, využití registrů X a Y
16. Čtvrtý demonstrační příklad: tisk znaku „A“ do horního levého rohu obrazovky
17. Příloha A: Konfigurační soubor pro linker ld65
18. Příloha B: Makefile pro překlad všech demonstračních příkladů
19. Repositář s demonstračními příklady
1. Kouzlo minimalismu potřetí: vývoj her a grafických i zvukových dem pro osmibitová Atari
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] a taktéž na jedenáctidílný seriál o vývoji pro slavnou osmibitovou herní konzoli NES i na seriál o vývoji pro ZX Spectrum dnes nepřímo navážeme. Řekneme si totiž, jakým způsobem je možné vyvíjet hry a grafická či hudební dema pro neméně slavné osmibitové stroje – Atari (modely 400, 800, řadu XL i XE). Vývoj pro Atari se od vývoje pro (například) již zmíněné ZX Spectrum odlišuje v prakticky všech aspektech, od zcela odlišně pojatého instrukčního souboru, až po rozdíly v grafickém subsystému. To je ostatně jen dobře, protože se naučíme (pro někoho) nové techniky a nebudeme se příliš opakovat.
Obrázek 1: Jednou z nejoblíbenějších her pro ZX Spectrum zůstává JetPac od firmy Ultimate Play the Game. Tato hra si vystačila s pouhými 16kB RAM (kam musíme počítat i obrazovou paměť!).
2. Vývojové nástroje pro osmibitová Atari
Na osmibitových mikropočítačích (a nyní se zaměříme především na domácí mikropočítače) měli ti uživatelé, kteří se chtěli zabývat programováním, na výběr z relativně velkého množství různých vývojových nástrojů a pomůcek (bylo jich dokonce až překvapivě velké množství – desítky programovacích jazyků atd.). Některé mikropočítače byly vybaveny pouze velmi jednoduchým operačním systémem zajišťujícím základní vstupně/výstupní operace (čtení klávesnice, zápis znaku na obrazovku, rutiny pro čtení a zápis dat na magnetofon).
Poměrně často byl tento minimalistický operační systém doplněn o takzvaný monitor což byl (a někde doposud je) program umožňující manipulaci s daty v operační paměti, tj. zápis dat do paměti, čtení (zobrazení) vybraného bloku, přesun bloku, vyplnění určité oblasti konstantou, čtení a zápis dat na externí paměťové médium (typicky na magnetofon) a některé pokročilejší monitory byly vybaveny i takzvaným disassemblerem umožňujícím transformaci sekvence bajtů na symbolické instrukce. Mimochodem – při výpisu obsahu operační paměti se v monitorech většinou používal stejný formát, jaký najdeme u mnoha moderních programátorských hexa editorů.
Monitory, zejména ty vybavené disassemblerem (zpětným assemblerem) bylo možné použít i pro tvorbu programů na úrovni strojového kódu. Takový program se většinou nejprve zapsal na papír ve formě assembleru, dále se provedl ruční překlad jednotlivých instrukcí do sekvence decimálních nebo hexadecimálních číslic a následně se tato sekvence zapsala do monitoru do určité předem vybrané oblasti operační paměti. V případě, že monitor podporoval zápis bloku paměti na externí paměťové médium (kazetový magnetofon, disketu), jednalo se o dosti primitivní, ovšem stále ještě použitelné (a v některých případech i používané) vývojové prostředí.
Na druhou stranu je programování na úrovni strojového kódu samozřejmě velmi pracné, zejména ve chvíli, kdy je nutné existující program modifikovat a tím pádem měnit cílové adresy skoků, adresy globálních proměnných atd. Mnohem civilizovanější způsob představovalo použití takzvaných assemblerů, což byly nástroje schopné překládat programy zapsané v jazyku symbolických adres/instrukcí do strojového kódu – ostatně přesně pro podobné činnosti jsou počítače zkonstruovány.
Obrázek 2: Jedním z poměrně velkého množství assemblerů vyvinutých pro osmibitové mikropočítače Atari je SynAssembler vytvořený a prodávaný společností Synapse Software. Tato společnost, s níž jsme se již na stránkách Roota seznámili, se nezaměřovala pouze na vývoj počítačových her. Kromě nich totiž vydávala i programy určené jak pro běžné koncové uživatele (FileManager 800, DiskManager, SynFile+, SynCalc, SynChron, SynComm, SynStock, SynTrend, …) tak právě vývojové nástroje; mezi nimi i SynAssembler.
Z vyšších programovacích jazyků byl populární především Pascal, a to díky tomu, že tento jazyk byl navržen takovým způsobem, aby byl překlad programů proveden jednoprůchodově a tudíž velmi rychle v porovnání s víceprůchodovými překladači. Ovšem existovaly i další programovací jazyky, například i populární céčko. Zde je nutné zmínit především známý překladač Aztec C portovaný na velké množství různých typů mikropočítačů, zapomenout nesmíme ani na Deep Blue C pro osmibitové počítače Atari (zde se autoři museli vyrovnat s faktem, že znaková sada neobsahovala složené závorky, tento jazyk měl ovšem i mnoho dalších omezení). A taktéž se jednalo o jazyky, které vznikly pouze pro jedinou platformu a jinam se nerozšířily. Ve světě osmibitových Atari je takovým zvláštním (a nepřenositelným) jazykem Action!.
Obrázek 3: Disassembling neboli zobrazení symbolických instrukcí získaných z obsahu strojového kódu.
Zapomenout nesmíme ani na Interlisp. Jedná se o dialekt jazyka Lisp, v němž se objevilo několik nových technologií, které byly navrženy tak, aby usnadnily dialog člověka s počítačem. Příkladem až překvapivě dobré portace je Interlisp/65 určený právě pro osmibitové domácí mikropočítače Atari. Zajímavé je, že distribuci (ne ovšem samotnou portaci) zajišťovala společnost Datasoft, s níž jsme se seznámili ve zcela jiném kontextu – tato firma totiž vytvářela i počítačové hry; viz například Hry vytvořené firmou Datasoft pro osmibitové domácí mikropočítače (už jsem psal, že v IT vše souvisí se vším, že?).
3. Assemblery pro osmibitová Atari
Nás však v dnešním článku budou zajímat především assemblery.
Na osmibitových domácích mikropočítačích se používaly dva typy assemblerů. Prvním typem byly assemblery interaktivní, které uživateli nabízely poměrně komfortní vývojové prostředí, v němž bylo možné zapisovat jednotlivé instrukce, spouštět programy, krokovat je, vypisovat obsahy registrů mikroprocesoru atd. Výhodou takto pojatého řešení byla nezávislost těchto assemblerů na rychlém externím paměťovém médiu. S těmito assemblery určenými pro počítače Atari se ve stručnosti seznámíme i v dnešním článku, protože jak Atari Assembler Editor, tak i MAC/65 náleží do této kategorie. Druhý typ assemblerů je používán dodnes – jedná se vlastně o běžné překladače, kterým se na vstupu předloží zdrojový kód (uložený na kazetě či disketě) a po překladu se výsledný nativní kód taktéž uloží na paměťové médium (odkud ho lze spustit). Tyto assemblery byly mnohdy vybaveny více či méně dokonalým systémem maker (odtud název macroassembler). Příkladem takového assembleru pro Atari jsou všechny moderní crossassemblery.
Jedním z nejstarších vývojových nástrojů pro osmibitové mikropočítače Atari je Atari Assembler Editor, jehož vznik se datuje do let 1979 až 1980. Tento nástroj byl používán i pro vývoj samotného operačního systému počítačů Atari. Jak však tento nástroj vůbec vznikl, když ještě vlastně počítače Atari nebyly dokončené? První (zjednodušené) verze byly ručně děrovány na děrnou pásku, která byla použita pro naprogramování čipu EPROM. Ten byl vložen do prototypu Atari a následně otestován. Jednalo se tak o jednu z forem bootstrapingu, kterým jsme ze zabývali v článku Můžeme věřit překladačům? Projekty řešící schéma „důvěřivé důvěry“. Výsledná podoba Atari Assembler Editoru byla dodávána na standardní cartridge o kapacitě osmi kilobajtů (ovšem ve skutečnosti se do značné míry využívaly subrutiny z operačního systému a používané i Atari Basicem).
Obrázek 5: Úvodní obrazovka Atari Assembler Editoru je pojata přísně minimalisticky. Pouze se na ní oznamuje aktuálně vybraný režim, zde konkrétně režim editace.
Atari Assembler Editor se skládal ze tří částí: editoru, assembleru a debuggeru (což však byl ve skutečnosti pouze monitor – viz předchozí text). Všechny tři části byly dostupné z cartridge a bylo možné se mezi nimi přepínat. Zdrojový kód zapisovaný v editoru byl překládán dvouprůchodovým překladačem a mohl být v operační paměti uložen souběžně se zdrojovým kódem, což ovšem znamenalo omezení maximálního objemu zdrojového kódu. Zajímavé a typické pro interaktivní assemblery je, že řádky zdrojového kódu byly číslovány, podobně jako v BASICu. K dispozici však byly i poměrně pokročilé operace pro vyhledání návěští atd.
Obrázek 6: Přepnutí do režimu debuggeru a výpis obsahu prvních několika bajtů s interpretrem jazyka Basic.
Debugger (spíše monitor) umožňoval zobrazení obsahu operační paměti, zobrazení obsahu registrů mikroprocesoru, modifikaci paměti, zobrazení disassemblované části paměti, krokování programu atd. – ovšem ne tak kvalitně, jako tomu bylo u dále popsaného assembleru MAC/65.
Obrázek 7: Disassembler (zpětný assembler) určený pro mikropočítače Atari (úvodní nastavení).
Další programátorský nástroj, s nímž se dnes alespoň ve stručnosti seznámíme, se jmenuje MAC/65. Jedná se o assembler a současně i o debugger, jehož ovládání je odvozeno od výše zmíněného Atari Assembler Editoru; ve skutečnosti je však MAC/65 prakticky po všech stránkách lepší (až na poněkud vyšší cenu). Za vývojem MAC/65 stála společnost Optimized Systems Software (OSS). MAC/65 byl dodáván na specializované cartridgi s kapacitou 16kB, což je dvojnásobná kapacita, než jakou počítače Atari podporují (konektor měl pouze třináct adresních pinů). Z tohoto důvodu bylo oněch 16kB rozděleno do dvojice paměťových bank, z nichž každá měla kapacitu osmi kilobajtů a mezi kterými se provádělo automatické přepínání (taková cartridge se někdy nazývala „supercartridge“ a existovalo jich více typů, které se lišily způsobem přepínání paměťových bank).
Obrázek 8: Úvodní obrazovka MAC/65 verze 1.02.
Předností assembleru MAC/65 byl mnohem rychlejší překlad v porovnání s Atari Assembler Editorem. Taktéž byla k dispozici lepší forma debuggeru dostupná pod příkazem DDT.
Obrázek 9: V režimu EDIT se zapisují jednotlivé deklarace i instrukce procesoru MOS 6502. Řádky s deklaracemi a instrukcemi jsou číslovány, takže přidání resp. smazání řádku je snadné. Taktéž je možné si vyžádat automatické číslování (což například Atari BASIC neumí). K dispozici jsou i složitější příkazy – obecně je možné říci, že programátorský editor v MAC/65 je propracovanější, než v případě BASICů.
4. Překladače jazyka C pro osmibitová Atari
Pro osmibitová Atari byly dostupné i překladače jazyka C. Některé překladače C pro MOS 6502 byly navrženy přímo pro běh na strojích osazených tímto čipem. To je dnes zcela normální situace (aplikace pro PC se překládají na PC), ovšem v případě MOS 6502 se museli tvůrci překladačů vypořádat s pomalým čipem, velmi malou kapacitou paměti a navíc i relativně pomalým externím paměťovým médiem (typicky disketa, protože kazetové verze C by byly ještě problematičtější). V důsledku těchto omezení se jednalo spíše o projekty určené pro amatérské použití, zatímco profesionální software stále vznikal v assembleru. Jednou z prvních implementací překladače C pro MOS 6502 je C/65 od slavné firmy Optimized Systems Software (OSS).
Obrázek 10: C/65 od společnosti Optimized Systems Software (OSS).
Taktéž se na tomto místě musíme zmínit o známém překladači Aztec C, jenž byl portovaný na velké množství různých typů mikropočítačů, zapomenout nesmíme ani na Deep Blue C (viz též https://en.wikipedia.org/wiki/Deep_Blue_C) pro osmibitové počítače Atari. Zde se autoři museli vyrovnat s faktem, že znaková sada neobsahovala složené závorky, takže zápis vypadal například takto:
main()
$(
printf("Hello World!");
$)
Obrázek 11: Dobová reklama na nástroje společnosti OSS.
Zajímavější jsou z dnešního pohledu cross compilery a cross assemblery (viz poznámka o českém překladu tohoto názvu). Tyto typy nástrojů jsou velmi často používané i dnes, zejména v oblasti mikrořadičů, digitálních signálových procesorů nebo mobilních telefonů (viz například Scratchbox). Ovšem tato technologie se používala již na začátku osmibitové éry. Například vývoj her pro herní konzoli Atari 2600 (Atari Video Computer System neboli Atari VCS) byl prováděn na minipočítači. Ovšem i později některé firmy vyvíjely profesionální software pro Atari, C64 i další osmibitové mikropočítače na výkonnějších strojích, kde se prováděl i překlad.
Obrázek 12: Jeden z konkurenčních překladačů k Aztec C byl Lattice C (ovšem až v pozdější době).
Dobrým a možná i typickým příkladem jsou právě cross překladače programovacího jazyka C. Tvorbou těchto cross překladačů se zabývala například společnost Manx Software Systems, jejíž překladače céčka (Aztec C) určené pro IBM PC s DOSem i pro osobní mikropočítače Macintosh dokázaly provádět cross překlad na osmibitové mikropočítače Commodore C64 a Apple II. Na chvíli se u Aztec C zastavme, i když přímo nesouvisí s osmibitovými Atari.
Aztec C totiž byl ve své době velmi úspěšný překladač, jenž existoval jak ve verzi pro osmibitové mikroprocesory (MOS 6502, Zilog Z-80), tak i pro mikroprocesory 16bitové a 32bitové. Tento překladač byl velmi úspěšný právě na Amize, kde byl používán, společně s Lattice C, prakticky až do faktického zániku této platformy. Ovšem na IBM PC jeho sláva netrvala dlouho, především z toho důvodu, že firma Microsoft považovala segment překladačů za poměrně důležitý a snažila se vytlačit jakoukoli konkurenci z trhu (i když ve skutečnosti v té době ještě neměla vlastní céčkový překladač). Společnosti Manx Software Systems se postupně zmenšoval počet platforem, na něž bylo možné překladač prodávat a přechod na podporu vestavěných systémů již přišel dosti pozdě. A právě pro cross překlad se Aztec C může používat dodnes (běží v DOSu, takže dnes vlastně taktéž v emulovaném prostředí).
Podobným stylem byl řešen i Microsoft C původně vytvořený společností, která stála za slavným Lattice C. Ostatně Lattice C byl s velkou pravděpodobností vůbec prvním překladačem céčka pro IBM PC (pochází z roku 1982). Ten byl později převeden i na Amigu, dále se rozšířil i na minipočítače a mainframy společnosti IBM. Firma Microsoft překladač Lattice C nabízela pod svým názvem MSC (Microsoft C) a teprve verze MSC 4.0 byla skutečně vytvořena přímo programátory z Microsoftu. Lattice C byl používán i při portaci aplikací z operačního systému CP/M na DOS (dnes je však možné pouze odhadnout, kolik kódu bylo skutečně napsáno v céčku a kolik kódu vzniklo transformací assembleru).
5. Základní programové vybavení pro vývoj her pro Atari v současnosti
Pro vývoj aplikací pro osmibitová Atari postačuje poměrně malé množství nástrojů. Základem pro nás bude assembler ca65 zkombinovaný s linkerem ld65. Jedná se vlastně o cross assembler, takže vývoj bude probíhat přímo na PC a do Atari se bude nahrávat až výsledný binární soubor. Dále je pochopitelně nutné mít k dispozici (libovolný) programátorský textový editor, ideálně s podporou zvýraznění syntaxe. A zapomenout nesmíme ani na emulátor osmibitových počítačů Atari. Výsledkem činnosti assembleru a linkeru budou binární soubory s koncovkou .xex, které je možné přímo nahrát do emulátoru a spustit je tam. Později si ukážeme, jakým způsobem se vytvoří obrazy disket, kazet (pro vážné zájemce) nebo aplikací uložených na cartridge.
6. Instalace ca65 a ld65
Jak jsme si již řekli v předchozí kapitole, budeme pro vývoj potřebovat assembler ca65 a linker ld65. Tyto dva nástroje jsou součástí instalace sady nástrojů vytvořených okolo překladače jazyka C, který se nazývá cc65. To znamená, že „instalací céčka“ získáme i nainstalovaný assembler a linker. Samotná instalace 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 (založený na apt/aptitude):
$ 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) ...
Instalace na Fedoře založené na RPM/DNF je stejně snadná:
$ sudo dnf install cc65 Updating and loading repositories: Repositories loaded. Package Arch Version Repository Size Installing: cc65 x86_64 2.19-12.fc42 fedora 1.2 MiB Installing dependencies: cc65-devel noarch 2.19-12.fc42 fedora 29.8 MiB Transaction Summary: Installing: 2 packages Total size of inbound packages is 2 MiB. Need to download 2 MiB. After this operation, 31 MiB extra will be used (install 31 MiB, remove 0 B). Is this ok [y/N]: y [1/2] cc65-0:2.19-12.fc42.x86_64 100% | 1.0 MiB/s | 422.1 KiB | 00m00s [2/2] cc65-devel-0:2.19-12.fc42.noarch 100% | 1.5 MiB/s | 1.8 MiB | 00m01s --------------------------------------------------------------------------------------------------- [2/2] Total 100% | 1.3 MiB/s | 2.3 MiB | 00m02s Running transaction [1/4] Verify package files 100% | 105.0 B/s | 2.0 B | 00m00s [2/4] Prepare transaction 100% | 3.0 B/s | 2.0 B | 00m01s [3/4] Installing cc65-devel-0:2.19-12.fc42.noarch 100% | 213.2 MiB/s | 29.8 MiB | 00m00s [4/4] Installing cc65-0:2.19-12.fc42.x86_64 100% | 1.7 MiB/s | 1.2 MiB | 00m01s Complete!
Po dokončení instalace budou k dispozici všechny tři výše zmíněné 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
7. Praktická část: čím se liší vývoj pro osmibitová Atari od dnes běžného vývoje?
V současnosti (pokud tedy vynecháme vibe coding a specs coding) typický vývoj probíhá takovým způsobem, že se vývojář seznámí s programovacím jazykem a taktéž se sadou knihoven, které bude používat. Vývoj pro osmibitová Atari, ale i pro další podobné stroje, je poněkud odlišný. Vývojář se musí seznámit s assemblerem i s podrobným popisem činnosti mikroprocesoru a navíc musí znát hardwarové vlastnosti Atari. Na druhou stranu odpadá seznamování se s knihovnami – každý pravý vývojář pro osmibitové stroje si totiž potřebné knihovny (nebo i celé enginy) naprogramuje sám. A podobně budeme v tomto seriálu postupovat i my: v jednotlivých krocích se seznámíme s možnostmi CPU MOS 6502 a taktéž hardwarem Atari, tj. s čipy ANTIC, GTIA a POKEY. Vývoj pro Atari totiž do značné míry znamená přímé ovládání těchto čipů popř.v případě ANTICu jeho programování (přes takzvaný display list, což je série specializovaných instrukcí).
8. První demonstrační příklad: prostá nekonečná smyčka
Dnešní první demonstrační příklad bude po logické stránce velmi jednoduchý. Je v něm totiž pouze implementována nekonečná programová smyčka. Ta je definována v segmentu .CODE (ten v našem případě začíná na adrese 0×2000, ovšem může se posunout prakticky kamkoli kromě nulté a první stránky a stránek s HW registry či mapovanou ROM). Samotná smyčka je pro lepší čitelnost zařazena do procedury nazvané main. Segment .CODE tedy bude vypadat následovně:
.CODE .proc main loop: jmp loop end: .endproc
To však nestačí, protože ve výsledném binárním souboru s koncovkou .xex musí být uloženy další dva segmenty. První z těchto segmentů obsahuje hlavičku 0×ffff a dvě adresy se začátkem a koncem kódového segmentu:
.segment "EXEHDR" .word $ffff ; uvodni sekvence bajtu v souboru XEX .word main ; zacatek kodoveho segmentu .word main::end - 1 ; konec kodoveho segmentu
A druhý segment obsahuje adresu první instrukce, která se má spustit. Tato adresa se uloží do slova umístěného na adrese RUNAD (0×02e0):
.segment "AUTOSTRT" ; segment s pocatecni adresou .word $02E0 ; naplni se pouze adresy RUNAD a RUNAD+1 .word $02E1 .word main ; adresa vstupniho bodu do programu
Celý zdrojový kód tohoto demonstračního příkladu je sice poněkud dlouhý, ale v praxi vlastně nebudeme segmenty EXEHDR a AUTOSTRT (prakticky nikdy) měnit, takže v dalších příkladem pouze zmodifikujeme instrukce v kódovém segmentu:
.CODE .proc main loop: jmp loop end: .endproc .segment "EXEHDR" .word $ffff ; uvodni sekvence bajtu v souboru XEX .word main ; zacatek kodoveho segmentu .word main::end - 1 ; konec kodoveho segmentu .segment "AUTOSTRT" ; segment s pocatecni adresou .word $02E0 ; naplni se pouze adresy RUNAD a RUNAD+1 .word $02E1 .word main ; adresa vstupniho bodu do programu ; finito
9. Překlad do strojového kódu s vytvořením listingu
Překlad demonstračního příkladu z předchozí kapitoly je proveden ve dvou krocích. V kroku prvním se spustí assembler ca65, jenž vytvoří soubor s objektovým kódem a taktéž „listing“ pro kontrolu, jakým způsobem byl vlastně překlad proveden:
$ ca65 background_color_1.asm -t atari -o dummy.o -l dummy.asm --list-bytes 100
Soubor s listingem by měl vypadat následovně. Povšimněte si, že obsahuje i kódy instrukcí a jediné chybějící údaje se týkají adres, které (prozatím) nejsou doplněny (namísto nich jsou použity znaky rr):
ca65 V2.18 - Fedora 2.19-12.fc42 Main file : dummy.asm Current file: dummy.asm 000000r 1 .CODE 000000r 1 000000r 1 .proc main 000000r 1 4C rr rr loop: jmp loop 000003r 1 end: 000003r 1 .endproc 000003r 1 000003r 1 000003r 1 .segment "EXEHDR" 000000r 1 FF FF .word $ffff ; uvodni sekvence bajtu v souboru XEX 000002r 1 rr rr .word main ; zacatek kodoveho segmentu 000004r 1 rr rr .word main::end - 1 ; konec kodoveho segmentu 000006r 1 000006r 1 000006r 1 .segment "AUTOSTRT" ; segment s pocatecni adresou 000000r 1 E0 02 .word $02E0 ; naplni se pouze adresy RUNAD a RUNAD+1 000002r 1 E1 02 .word $02E1 000004r 1 rr rr .word main ; adresa vstupniho bodu do programu 000006r 1 000006r 1 ; finito 000006r 1
10. Konfigurace linkeru, spuštění linkeru s vytvořením výsledného souboru ve formátu xex
V kroku druhém se spouští linker ld65. Výsledkem jeho činnosti bude soubor s mapou paměti a především pak binární soubor ve formátu xex, který bude možné přímo spustit v emulátoru Atari. Linker spustíme následujícím příkazem:
$ ld65 -C linker.cfg dummy_1.o -o dummy_1.xex -m dummy_1.map
Soubor dummy1.map obsahuje mapu paměti. Povšimněte si tabulky se všemi třemi segmenty. Kódový segment skutečně začíná na adrese 0×2000 a končí na adrese 0×2002 (tedy celkem tři bajty, do kterých je zakódována instrukce jmp):
Modules list:
-------------
dummy.o:
CODE Offs=000000 Size=000003 Align=00001 Fill=0000
EXEHDR Offs=000000 Size=000006 Align=00001 Fill=0000
AUTOSTRT Offs=000000 Size=000006 Align=00001 Fill=0000
Segment list:
-------------
Name Start End Size Align
----------------------------------------------------
AUTOSTRT 000000 000005 000006 00001
EXEHDR 000000 000005 000006 00001
CODE 002000 002002 000003 00001
Exports list by name:
---------------------
Exports list by value:
----------------------
Imports list:
-------------
Ovšem důležitější je soubor dummy.xex, jenž má velikost patnácti bajtů a je přímo spustitelný v emulátoru Atari:
$ ls -l dummy.xex -rw-r--r--. 1 ptisnovs ptisnovs 15 Mar 3 17:01 dummy.xex
Obrázek 16: Takto sofistikovaně vypadá obrazovka po spuštění prvního demonstračního příkladu v emulátoru.
11. Formát .xex
Zastavme se na chvíli u souboru dummy.xex, který vznikl jako výsledek činnosti linteru a je ho možné přímo spustit v emulátoru osmibitových mikropočítačů Atari. Tento soubor má interně velmi jednoduchou strukturu. Skládá se z jednotlivých segmentů, přičemž na začátku je speciální segment nazvaný EXEHDR (ten jsme již viděli ve zdrojovém kódu). Každý segment je uložen následovně:
| Offset | Stručný popis |
|---|---|
| 00–01 | obsahuje 0×ffff u prvního segmentu EXEHDR, u dalších segmentů uveden nemusí být |
| 02–03 | startovní adresa (dva bajty); obsah segmentu se do paměti nahraje právě od této adresy |
| 04–05 | koncová adresa určená pro výpočet délky segmentu |
| 06-?? | obsah segmentu |
Až se do paměti nahrají všechny segmenty, je program spuštěn od adresy uložené na 0×02e0.
Ostatně se můžeme podívat na náš soubor dummy1.xex. Jeho obsah je následující:
$ od -Ad -tx1 dummy_1.xex 0000000 ff ff 00 20 02 20 4c 00 20 e0 02 e1 02 00 20 0000015
Význam všech patnácti bajtů je následující:
| Bajty | Stručný popis |
|---|---|
| ff ff | začátek segmentu EXEHDR |
| 00 20 | startovní adresa = 0×2000 |
| 02 20 | koncová adresa = 0×2002 (tři bajty celkem) |
| 4c 00 20 | instrukce JMP 0×2000 |
| e0 02 | druhý segment, startovní adresa = 0×02e0 |
| e1 02 | druhý segment, koncová adresa = 0×02e1 (dva bajty celkem) |
| 00 20 | obsah segmentu = 0×2000 (což je startovní adresa programu) |
12. Druhý demonstrační příklad: změna barvy pozadí v textovém režimu
Úvodní demonstrační příklad byl až příliš jednoduchý – vlastně ani nebylo poznat, že byl korektně spuštěn. Z tohoto důvodu si ukážeme jen nepatrně složitější příklad, který po svém spuštění změní hodnotu HW registru mapovaného na adresu 710 (decimálně). Tento registr obsahuje kód barvy pozadí textového režimu (v grafických režimech tomu je jinak, tedy kromě režimu číslo 8). Kód barvy je uložen v jednom bajtu a obsahuje intenzitu barvy i její odstín. Zápisem nuly nastavíme černou barvu, což již bude viditelná změna, protože ve výchozím nastavení má textový režim na pozadí barvu světle modrou.
A jak se provede zápis nuly na adresu 710? Musíme použít dvě instrukce. První instrukce vynuluje akumulátor A a druhá instrukce uloží obsah akumulátoru na zvolenou adresu. Povšimněte si, že konstanta se zapisuje s křížkem na začátku, zatímco adresa je reprezentována pouze číslem (POZOR: na i86 je tomu zcela jinak):
lda #0 ; kod barvy
sta 710 ; ulozit do registru COLOR2
Celý zdrojový kód tohoto demonstračního příkladu bude vypadat následovně:
.CODE
.proc main
lda #0 ; kod barvy
sta 710 ; ulozit do registru COLOR2
loop: jmp loop
end:
.endproc
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word main::end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word $02E0 ; naplni se pouze adresy RUNAD a RUNAD+1
.word $02E1
.word main ; adresa vstupniho bodu do programu
; finito
Výsledek po spuštění v emulátoru osmibitových Atari:
13. Symboly definované v souboru atari.inc
Ve zdrojovém kódu předchozího demonstračního příkladu bylo použito mnoho „magických konstant“, konkrétně konstanty 710, 0×02e0 a 0×02e1. Mj. i kvůli používání takových konstant má assembler reputaci špatně čitelného jazyka, ovšem nemusí tomu tak být. Společně s cc65 a dalšími nástroji je totiž dodáván soubor nazvaný atari.inc, který obsahuje přibližně tisíc (!) pojmenovaných konstant – jmen HW registrů, tabulek v ROM, kódů kláves atd. atd. A mj. v tomto souboru nalezneme i následující pětici pojmenovaných konstant s adresami barvových registrů:
COLOR0 = $02C4 ;1-byte playfield 0 color/luminance COLOR1 = $02C5 ;1-byte playfield 1 color/luminance COLOR2 = $02C6 ;1-byte playfield 2 color/luminance COLOR3 = $02C7 ;1-byte playfield 3 color/luminance COLOR4 = $02C8 ;1-byte background color/luminance
A navíc zde nalezneme konstantu:
RUNAD = $02E0 ;##map## 2-byte binary file run address
Tyto konstanty můžeme snadno využít ve zdrojových kódech; pouze je nutné na začátku hlavičkový soubor načíst:
.include "atari.inc"
14. Třetí demonstrační příklad: využití symbolů definovaných v souboru atari.inc
Podívejme se nyní, jak se zápis programu zpřehlední, pokud v něm použijeme konstanty COLOR2 a RUNAD. Nyní již kód neobsahuje žádná „magická čísla“:
.include "atari.inc"
.CODE
.proc main
lda #0 ; kod barvy
sta COLOR2 ; ulozit do registru COLOR2
loop: jmp loop
end:
.endproc
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word main::end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
15. Adresovací režimy mikroprocesoru MOS 6502, využití registrů X a Y
Adresovací režimy odlišují mikroprocesor MOS 6502 od naprosté většiny ostatních mikroprocesorů a umožňují použít odlišný styl programování založený na efektivním použití nulté stránky paměti a obou index registrů. Existuje celkem třináct (!) adresovacích režimů, ovšem žádná instrukce nevyužívá všechny dostupné režimy. Některé adresovací režimy jsou určeny pouze pro skoky, další pro implicitní operandy atd.:
| # | Zápis | Název | Assembler | Stručný popis |
|---|---|---|---|---|
| 1 | A | accumulator | INS A | operandem je přímo akumulátor |
| 2 | abs | absolute | INS $LLHH | za instrukcí následuje šestnáctibitová adresa, na níž je operand uložen |
| 3 | abs,X | absolute, X-indexed | INS $LLHH,X | za instrukcí následuje šestnáctibitová adresa, která je přičtena k X |
| 4 | abs,Y | absolute, Y-indexed | INS $LLHH,Y | za instrukcí následuje šestnáctibitová adresa, která je přičtena k Y |
| 5 | # | immediate | INS #$BB | za instrukcí následuje bajt s konstantou |
| 6 | impl | implied | INS | operand je odvozen přímo z instrukce, například INX |
| 7 | ind | indirect | INS ($LLHH) | nepřímá adresace přes adresu uloženou za instrukcí (ta je ukazatelem), nepřímý skok |
| 8 | X,ind | X-indexed, indirect | INS ($LL,X) | efektivní adresa je spočtena z hodnoty uložené na (LL+X) |
| 9 | ind,Y | indirect, Y-indexed | INS ($LL),Y | efektivní adresa je spočtena z hodnoty uložené na LL, k výsledku se přičte Y |
| 10 | rel | relative | INS $BB | použito u relativních skoků; za instrukcí je jeden bajt reprezentující offset se znaménkem |
| 11 | zpg | zeropage | INS $LL | operand je uložen na nulté stránce na adrese LL |
| 12 | zpg,X | zeropage, X-indexed | INS $LL,X | operand je uložen na nulté stránce na adrese LL+X |
| 13 | zpg,Y | zeropage, Y-indexed | INS $LL,Y | operand je uložen na nulté stránce na adrese LL+Y |
16. Čtvrtý demonstrační příklad: tisk znaku „A“ do horního levého rohu obrazovky
Popis adresovacích režimů byl do tohoto článku zařazen schválně. Ukážeme si totiž, jakým způsobem lze provádět zápis do obrazové paměti. Standardní textový režim počítačů Atari má 40 znaků na řádku a 24 řádků, celkem tedy zabírá 960 bajtů. Začátek obrazové paměti je ovšem proměnný, takže ho nejprve musíme nějak zjistit. Konkrétní adresa začátku obrazové paměti je uložena ve dvou bajtech na adrese 88 (decimálně). Pokud tedy budeme chtít na začátek obrazovky vypsat písmeno „A“, musíme přečíst adresu uloženou na adresách 88+89 a na tuto načtenou adresu (může být například rovna 40000) uložíme ATASCII hodnotu znaku „A“.
Ovšem díky adresovacímu režimu (ADR),Y se celá situace zjednodušuje. Tento adresovací režim načte z adresy ADR (v našem případě z adresy 88) ukazatel a přičte k němu hodnotu registru Y. To tedy znamená, že zápis na nepřímo zadanou adresu je proveden jedinou instrukcí! A právě tento postup je použit v dnešním posledním demonstračním příkladu:
.include "atari.inc"
.CODE
.proc main
lda #33 ; ATASCII hodnota znaku "A"
ldy #0 ; vynulovat registr Y
sta (88),y ; tisk znaku "A" na první místo na obrazovce
; (adresa Video RAM je na adresách 88 a 89)
loop: jmp loop
end:
.endproc
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word main::end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Můžete pochopitelně použít i následující pojmenovanou konstantu:
SAVMSC = $58 ;2-byte saved memory scan counter
Obrázek 18: Znak „A“ byl zapsán na první volné místo na obrazovce, dokonce ještě před textový kurzor.
17. Příloha A: Konfigurační soubor pro linker ld65
Připomeňme si, že v průběhu vytváření výsledného souboru ve formátu xex musí linker mj. znát i paměťové rozsahy, adresy některých symbolů (minimálně EXEHDR, STARTUP, CODE) atd. Pro naše prozatím velmi jednoduché demonstrační příklady bude postačovat následující konfigurace linkeru (později ji rozšíříme):
FEATURES
{
STARTADDRESS: default = $2000;
}
SYMBOLS
{
}
MEMORY
{
ZP: start = $0082, size = $007E, type = rw, define = yes;
HEADER: start = $0000, size = $0006, file = %O;
RAM: start = %S, size = $8000, file = %O;
TRAILER: start = $0000, size = $0006, file = %O;
}
SEGMENTS
{
EXEHDR: load = HEADER, type = ro;
STARTUP: load = RAM, type = ro, define = yes, optional = yes;
ZEROPAGE: load = ZP, type = zp;
CODE: load = RAM, type = ro, define = yes;
AUTOSTRT: load = TRAILER, type = ro;
}
FEATURES
{
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: type = interruptor,
segment = RODATA,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}
18. Příloha B: Makefile pro překlad všech demonstračních příkladů
Všechny tři dnes popsané demonstrační příklady, pro jejichž překlad je zapotřebí použít assembler ca65 a linker ld65, je možné přeložit s využitím souboru Makefile, jehož obsah je vypsán pod tímto odstavcem:
execs := dummy.xex print_a.xex background_color_1.xex background_color_2.xex
all: $(execs)
clean:
rm -f *.o
rm -f *.xex
.PHONY: all clean
%.o: %.asm
ca65 $< -t atari -o $@ -l $(basename $<)_list.asm --list-bytes 100
%.xex: %.o
ld65 -C linker.cfg $< -o $@ -m $(basename $<).map
Výsledkem překladu jsou soubory s koncovkou .xex, které je možné přímo spustit v emulátoru osmibitových počítačů Atari.
19. Repositář s demonstračními příklady
Všechny demonstrační příklady, s nimiž jsme se v dnešním článku seznámili a které jsou určeny pro překlad s využitím assembleru ca65, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy příkladů psané v assembleru i „listingy“ vygenerované samotným assemblerem, ze kterých je patrné, jakým způsobem se jednotlivé příklady přeložily do výsledného XEX souboru:
20. Odkazy na Internetu
- MOS 6502 instruction set
http://www.6502.org/users/obelisk/6502/instructions.html - EXE File Format Description
https://gury.atari8.info/refs/file_formats_exe.php - XEX Filter – A toolkit to analyze and manipulate Atari binary files
https://www.vitoco.cl/atari/xex-filter/index.html - chkxex.py
https://raw.githubusercontent.com/seban-slt/tcx_tools/refs/heads/master/chkxex.py - 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 - Překladače jazyka C pro historické osmibitové mikroprocesory
https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/ - Překladače programovacího jazyka C pro historické osmibitové mikroprocesory (2)
https://www.root.cz/clanky/prekladace-programovaciho-jazyka-c-pro-historicke-osmibitove-mikroprocesory-2/ - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - 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/ - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - “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/ - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - How To Start Learning Atari 8 Bit Assembly For Free
https://forums.atariage.com/topic/300732-how-to-start-learning-atari-8-bit-assembly-for-free/ - WUDSN (Demo Group)
https://www.wudsn.com/ - Machine Language For Beginners
https://www.atariarchives.org/mlb/ - Assembly language: all about I/O
https://www.atarimagazines.com/v3n8/AllAbout_IO.html - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/




