Jak se programovalo ve strojovém kódu na počítačích ZX Spectrum

David Cimbůrek, 2002

1. Úvod

Sir Clive Sinclair

Mikropočítač ZX Spectrum spatřil světlo světa právě před dvaceti lety. Sir Clive Sinclair ho vyráběl s úmyslem prodávat ho lidem v penzi, kteří, dle jeho slov, měli dostatek času na to, aby se počítači zabývali, učili se je používat a programovat. Mikropočítač měl úžasný úspěch - týdně se ho prodávalo na 15 tisíc kusů. Nicméně, mezi zákazníky bylo seniorů minimum.

Ve své době bylo ZX Spectrum zdaleka nejlevnější počítač, který byl schopen zobrazovat barevnou grafiku. Tudíž našel široké uplatnění zejména v oblasti počítačových her. A ty, jak známo, hrají povětšinou děti.

2. Proč assembler

Mikropočítač Sinclair ZX Spectrum

"Základním" programovým vybavením ZX Spectra byl BASIC. Byl uložen v ROM paměti počítače a po jeho spuštění se uživatel ocitl v editačním okně tohoto jazyka, ve kterém mohl psát BASICovské příkazy, nahrávat a spouštět programy a provádět všechny vstupně-výstupní operace.

BASIC však nebyl pro programování grafických operací příliš vhodný. Jeho hlavní nevýhoda spočívala v tom, že byl interpretovaný. A interpreter nebyl příliš rychlý. A grafické operace BASICu (kreslení čar a kružnic a práce s barvami) nebyly napsány zrovna nejlépe.

Proto, pokud chtěl programátor napsat nějakou rychlou aplikaci, která by používala grafiku, nebo třeba multitasking (viz dále), byl nucen sáhnout po strojovém kódu.

3. Technické parametry ZX Spectra

3.1 Procesor

Sinclair ZX Spectrum obsahoval osmibitový procesor Z80A, který pracoval na frekvenci 3,5 MHz. Měl tyto 8-bitové registry:

  • f - flag registr, stavový registr; obsahoval informace o výsledku předchozích operací. Každý bit tohoto registru představoval jeden flag - jednu informaci. Při práci si programátor v naprosté většině případů vystačil se dvěmi flagy:
  • ZERO flag - obsahoval informaci, zda byl výsledek předchozí operace 0;
  • CARRY flag - pokud došlo při poslední operaci k přetečení, byla zde jednička, jinak nula.
  • a - akumulátor - nejdůležitější 8-bitový registr, neboť operace s ním byly rychlejší než operace s ostatními registry a některé operace prováděné s registrem a nebylo možné s jinými registry vykonávat;
  • b, c, d, e, h, l - ostatní osmibitové registry;
  • hx, lx, hy, ly - indexové registry; nebyly moc využívány, protože práce s nimi byla velmi pomalá;
  • r - tento registr měl zvláštní postavení. Sloužil procesoru k občerstvování pamětí a po provedení každé instrukce se jeho obsah zvětšil o jedničku. Bylo možné ho využívat jako generátor pseudonáhodných čísel;
  • i - tento registr se používal pro nastavení módu přerušení.
  • Některé 8-bitové registry se sdružovaly do dvojic a mohlo se s nimi pracovat jako s 16-bitovými:
  • hl - nejdůležitější 16-bitový registr, měl podobnou důležitost jako registr a mezi 8-bitovými registry;
  • af, bc, de - ostatní 16-bitové registry;
  • ix, iy - 16-bitové indexové registry;
  • pc - program counter, čítač instrukcí; ukazoval vždy na právě prováděnou instrukci;
  • sp - stack pointer, ukazatel na zásobník návratových hodnot.
  • Registry af, bc, de, hl byly v procesoru obsaženy dvakrát a programátor mohl mezi těmito sadami registrů přepínat. Tato redundance se využívala v případě, že registry již byly obsazeny nějakými daty a přesto byly potřeba pro další výpočet. Místo ukládání obsahu registru do paměti (relativně pomalá operace) se prostě prohodily sady registrů, provedl se potřebný výpočet a registry se znovu přehodily.

    Přepínat se mohl zvlášť registr af (instrukcí ex af,af') a registry bc, de, hl (instrukce exx).

    Rychlost instrukcí procesoru se udávala v takzvaných T-cyklech, "téčkách". Jeden T-cyklus byl dlouhý 1/3 500 000 vteřiny. Udávaná hodnota u každé instrukce znamenala dobu potřebnou pro vykonání dané instrukce při zakázaném přerušení.

    Například instrukce exx trvala 4 T-cykly, instrukce ld a,b (naplnění obsahu registru a obsahem registru b) trvala také 4 T-cykly, instrukce ld lx,a (naplnění obsahu indexregistru lx obsahem registru a) trvala již 8 T-cyklů a například instrukce ex (sp),ix (výměna obsahu 16-bitového indexregistru ix s obsahem paměti na adrese obsažené v registru sp) trvala 23 T-cyklů.

    3.2 Paměť

    Jelikož byl procesor Z80A osmibitový, byl schopný adresovat maximálně 64KB paměti. Ta byla rozdělena do několika částí.

  • Od adresy 0 až po 16383 byla paměť ROM s interpretem BASICu.

  • Paměť obrazovky - videoRAM - zabírala 6912 bytů (adresy 16384-23295). Byla organizována zvláštním způsobem. Byla rozdělena na dvě části: pixelovou a atributovou. Pixelová část obsahovala informace o tom, které body na obrazovce jsou rozsvícené a které ne. Nacházela se na adresách 16384 až 22527. Velikost obrazovky tedy byla 255x192 bodů. Nastavováním bitů v bytech na dané adrese se vykreslovaly jednotlivé body. Například následující čtyři instrukce

    ld   hl,16384     ;vlož do hl adresu 1. bytu obrazovkové paměti
    ld   a,128        ;vlož do a masku pro nejlevější bit
    or   (hl)         ;logický součet a a obsahu paměti na adrese hl, výsledek jde opět do a
    ld   (hl),a       ;vlož výsledek do obrazové paměti

    vykreslí jeden bod do levého horního kraje obrazovky.

    Attributová část nesla informace o barvě (celkem bylo 8 barev), jasu a blikání bodů, přičemž tato hodnota byla stejná pro skupinu 64 bodů (čtverec 8x8).

  • Oblast paměti od 23296 až po 23755 využíval BASIC a ukládal sem své systémové proměnné. Pokud se v programu ve strojovém kódu nepoužívaly BASICovské rutiny, mohla se tato část paměti přepsat jinými daty.

  • Od adresy 65535 začínal zásobník návratových hodnot. Jeho velikost byla dynamická a zásobník rostl směrem dolů k nižším adresám. Na konec zásobníku ukazoval registr sp.

  • Paměť mezi adresou 23755 a koncem zásobníku byla určena pro uživatelská data, tedy buď text programu v BASICu nebo text programu ve strojovém kódu a jeho data.

  • 3.3 Další části

    Dále byl v počítači zabudován jednoduchý reproduktor, konektor pro výstup obrazu na televizní přijímač, 2 vývody pro joysticky, port pro připojení další periférie (například tiskárny) a kombinovaný konektor pro přívod elektrického proudu z transformátoru a pro vstup a výstup na kazetový magnetofon.

    4. Překladače strojového kódu

    Editační okno systému PROMETHEUS

    Programy ve strojovém kódu bylo možné psát dvojím způsobem. Pro velmi krátké programy bylo možné napsat si je na papír, ručně přepsat assembler do číselné podoby strojového kódu, a tato čísla v BASICu vložit do paměti. Tento způsob je velmi pracný a zdlouhavý, je možné takto programovat skutečně pouze krátké programy.

    Pro většinu programů bylo nutné použít některý překladač strojového kódu.

    Nejstarší překladač strojového kódu se jmenoval GENS. Měl velmi nepohodlný a neintuitivní editor, byl ovšem relokovatelný (to znamená, že bylo možné jej umístit do kteréhokoliv místa do paměti) a zabíral poměrně málo místa - 8,5 KB.

    Monitor systému PROMETHEUS

    Existoval k němu i monitor jménem MONS - program určený pro ladění programů. Umožňoval krokovat a trasovat programy a vypisoval obsahy registrů, ale práce s ním byla opět velmi složitá a nepřehledná.

    Postupem času byly vytvořeny další překladače. Nejlepší z nich byl asi systém PROMETHEUS. Uchovával zdrojový text assembleru v paměti komprimovaný, tudíž se ho vešlo do paměti dvakrát tolik než v GENSu, obsahoval pohodlný a intuitivní editor, rychlost překladu byla také mnohem vyšší než u GENSu. Monitor obsahoval všechny potřebné funkce, několik trasovacích režimů a jako jediný uměl zobrazovat T-cykly, takže bylo možné jednoduše počítat, jak dlouho bude trvat provádění daných částí kódu.

    5. Jak se...

    5.1 Jak se tiskly znaky

    Znaky bylo možné tisknout pomocí BASICovské rutiny uložené v ROM. Například pro vytisknutí vykřičníku stačilo napsat následující instrukce:

    ld   a,2         ;incializace tiskové rutiny se provede
    call #1601       ;zavoláním podprogramu na adrese 1601 hexadecimálně
    ld   a,"!"
    rst  16          ;zavolání tiskového podprogramu na adrese 16

    Pokud chtěl programátor používat jiný font, než byl v ROM, nezbylo nic jiného, než si napsat vlastní tiskovou rutinu a vykreslovat znaky na obrazovku byte po bytu. Na Spectru se psalo v naprosté většině neproporcionálními fonty, které měly písmena široká 8 bitů (kvůli jednoduchosti tisku na obrazovku). Vyskytovaly se i programy, které používaly znaky široké 6 nebo dokonce pouze 4 bity, ale to již bylo na úkor čitelnosti. Asi jediný program, který pracoval s proporcionálními písmeny, byl DTP editor DESKTOP.

    5.2 Jak se ze Spectra vyluzoval zvuk

    ZX Spectrum mělo jednoduchý reproduktor, který šel nastavit do dvou poloh. Pokud se tyto polohy střídaly dostatečně rychle, vznikal zvuk. Například tento kus kódu

    SOUND    ld   b,128       ;cyklus proběhne 128x
             ld   hl,1000     ;adresa do ROM
    SOUND1   ld   a,(hl)      ;načti hodnotu z ROM
             and  24          ;ponech z ní pouze jeden bit
             or   7           ;nastav bílý okraj obrazovky
             out  (254),a     ;nastav reproduktor a okraj obrazovky
             dec  l           ;posuň se v ROM o jednu adresu níže
             djnz SOUND1      ;opakuj
             ret              ;konec podprogramu

    přehrál na reproduktor 128 bitů z části ROM paměti a vytvořil tím jakési krátké pípnutí. Malé vysvětlení - port 254 sloužil jak pro nastavování reproduktoru (5. bit), tak i pro nastavování barvy okraje obrazovky (spodní 3 bity).

    Bylo mozné vytvářet i dvou- či tříkanálové hudební rutiny, ale muselo při nich být zakázáno přerušení (kvůli synchronizaci kanálů) a jejich vytváření bylo poměrně obtížné.

    5.3 Jak se prováděly kazetové operace

    Kazetové operace - tedy ukládání dat na magnetofonovou pásku, jejich opětovné načítání z kazety do paměti počítače a ověřování kompaktnosti již uložených dat - se prováděly obdobně jako výstup na reproduktor. Data se posílala na jeden bit výstupního portu. Ten byl napojen kabelem na mikrofonní vstup magnetofonu. Naopak při nahrávání putovala data opačným směrem. Tyto operace byly časově velmi kritické a proto bylo nutné při nich zakázat přerušení.

    5.4 Jak se kreslily sprity

    Bitmapa šipky

    Sprity, tedy pohyblivé animované obrázky, tvořily základ většiny Spectrovských her. Jednalo se o sadu bitmap, na kterých byly rozfázované pohyby dané postavy či objektu. Ty se pak postupně vykreslovaly na obrazovku, jak se daný objekt pohyboval. Tím se budil dojem plynulé chůze, letu či jiného pohybu. Jak se takové sprity vyráběly?

    Nejprve se samozřejmě musely nakreslit právě bitmapy. Vzhledem ke složitému způsobu práce s barvou byly většinou černobílé. Ke každé bitmapě musela být vytvořena takzvaná maska bitmapy. Maska obsahovala prakticky tu samou kresbu, jako bitmapa, plus zde bylo ještě "začerněno" nejbližší okolí bitmapy. Maska byla nutná, neboť se mohlo stát, že sprity splynou s pozadím (například bílá postava na bílém pozadí).

    Maska šipky

    Vykreslování pak probíhalo následujícím způsobem: Uložila se do bufferu ta část obrazovky, na kterou se měl sprite vykreslit. Na tomto místě se pak ponechaly ty bity, kterým v masce spritu odpovídaly nulové bity. Ty bity, kterým v masce odpovídala jednička, se vymazaly. Poté se na dané místo vykreslil sprite.

    Dále byl čas pro další akce, například k testování klávesnice či joysticku. Na základě těchto operací se vybrala další pohybová fáze spritu (například zvednutí ruky či nohy u postavy a atd.). Pak se na místo spritu opět vykreslilo uložené pozadí. A mohlo se začít s vykreslováním další pohybové fáze spritu.

    Na obrázcích kolem jsou vidět bitmapa a maska jednoduchého spritu - kurzorové šipky. Tato šipka má pouze jednu pohybovou fázi.

    5.5 Jak se dělal multitasking

    ZX Spectrum umělo zpravovávat dva módy přerušení - mód 1 a 2. Při módu jedna se vykonávala rutina v ROM paměti na adrese 56. Zde byl uložen program pro testování stisknutých kláves na klávesnici a inkrementování systémových hodin.

    Mód dva mohl být používán programátory pro napsání vlastní oblužné rutiny přerušení. Procesor si vyzvedl číslo z registru i a udělal z něj horní byte adresy obslužné rutiny. Spodní byte byl bohužel neošetřen, a tak se místo něj mohlo v adrese objevit jakékoliv číslo. Proto se od adresy i*256 následujících 256 bytů naplnilo hodnotou FE hexadecimálně. Procesor se poté podíval na adresu i*256+"nějaké náhodné číslo v rozsahu nula až 255". Z této adresy si vzal 16-bitové číslo, po předchozích operacích to tedy bylo vždy číslo FEFE hexadecimálně. A na této adrese předpokládal řídící rutinu přerušení. Výše popsané operace musel bohužel napsat ve svém programu uživatel.

    Obrázek ze hry Knight Lore

    Při vyvolání přerušení bylo přerušení zakázáno (aby nemohl být obslužný program opět přerušen). Na jeho konci tedy muselo být přerušení opět povoleno. Nyní k multitaskingu. Multitasking Spectrum nativně nepodporovalo. Veškerá režije s ním spojená tedy opět zůstávala na programátorovi. Při každém přepnutí procesů tedy musel ukládat jejich stav - obsahy registrů procesoru a další pomocná data. Běh systému tedy vypadal tak, že byla spuštěna jedna úloha. Po jisté době (přerušení se na Spectru vyvolávalo každou padesátinu vteřiny) se vyvolalo přerušení. Obslužná rutina přerušení uložila někam do své pracovní paměti informace nezbytné pro obnovení běhu procesu, nastavila jako aktivní další proces v pořadí a předala mu procesorový čas.

    Tato programátorská technika byla využívána většinou ve hrách. Například pro současné testování klávesnice, joysticku, vykreslování spritů, hodin, výpočty umělé inteligence soupeře, generování zvuků, ...

    6. Závěrem

    Dnes již počítače ZX Spectrum skoro nikdo nepoužívá. Je však zajímavé podívat se v dnešní době, kdy počítáme paměť na stovky megabytů, na programy, které se vešly do 40 kilobytů a někdy dokázaly více, než jejich mnohamegabytové obdoby na PC.

    7. Literatura a zdroje

  • Tomáš Vilím - Assembler a ZX Spectrum I, Proxima, 1992;
  • Tomáš Vilím - Assembler a ZX Spectrum II, Proxima, 1993;
  • Prometheus (uživatelský manuál), Proxima, 1990;
  • Golden Archive - The Very Best of Sinclair ZX Spectrum
  • World of Spectrum - Emulators