6502 – !etěvs ,johA

Všechno jde udělat i jinak, hlavně v osmibitovém assembleru. Takže ani pro Hello, world není jen jediný předepsaný postup.

V první ukázce jsem použil postup, kterému se říká ASCIIZ – tedy ASCII řetězec, ukončený bajtem  s hodnotou 0 (ASCII + Zero). Tento způsob zápisu řetězců používají třeba překladače jazyka C – kdo z vás zná Céčko, tak ví.

Jazyk Pascal používal jiný přístup – první bajt udával délku řetězce ve znacích, a pak následovaly kýžené znaky. Někdy může být tento přístup výhodnější – hlavně tehdy, když nám délku spočítá překladač a natvrdo uloží do kódu. Jak by se to přepsalo do assembleru 6502?

Tak, základ zůstane stejný – definice konstant, inicializace i rutina SEROUT, jen ten vnitřek se změní. Registr X použijeme jako počítadlo a index znaku v řetězci. Když dosáhne hodnoty rovné délce řetězce, přestaneme. Ta zásadní pasáž kódu bude vypadat nějak takhle:

A co Woz?

Logout mě upozornil, že Steve Wozniak v monitoru pro Apple I použil trik, kterým ušetřil několik bajtů (a tedy taktů procesoru). Ačkoli mi jeho trik připadá v tomto případě jako klasický příklad „overengineeringu“ (tedy optimalizace až přehnaná), tak si ji ukážeme, protože ilustruje schopnost podívat se na problém z naprosto neobvyklého úhlu. Což se zase při programování v assembleru často hodí.

Úvodní úvaha je jednoduchá: na začátku se nuluje registr X, v průběhu se pak zvyšuje o 1 a kontroluje se, jestli už má hodnotu N. Kdybychom na začátku do registru X uložili hodnotu N a šli v opačném pořadí, tedy směrem k nule, tak by odpadla instrukce porovnání a konec by nastal ve chvíli, kdy instrukce DEX (X=X-1) dojde k nule. Pak nastaví příznak Z (viz popis instrukce).

Nese to s sebou jeden drobný problém: čtení znaků by fungovalo od konce. Hm, a co? Tak je tam zapíšeme pozpátku!

Ušetřili jsme dva bajty a nějaký ten takt, přišli jsme o snadnou čitelnost. Ano, při programování osmibitů jsou situace, kdy dva bajty či pár taktů znamená hodně. (Spousta programátorů, co na assemblerech vyrostla, pak logicky považuje cokoli jiného za plýtvání ad absurdum.)

Ještě nějaký trik, prosím!

„Ale no jistě,“ odpovědělo sluchátko. Pojďte se podívat na následující kód:

Přeložte si ho, spusťte emulátor a pojďme krokovat:

První instrukce (adresa 0000, 2 bajty) nastaví X na hodnotu FFh, druhá (adresa 0002, 1 bajt) tuto hodnotu zkopíruje do ukazatele zásobníku S. Třetí instrukce (adresa 0003, 3 bajty) je instrukce volání podprogramu. Volá se podprogram na adrese 0009…

V tuto chvíli se zastavíme a podíváme se na vrchol zásobníku, co se tam uložilo. Víme, že instrukce JSR ukládá dva bajty návratové adresy. Protože byl ukazatel nastaven na FFh, budou tyto dva bajty na adresách 01FEh a 01FFh. Co tam najdeme?

Na zásobníku je uložena hodnota 0005. Vidíme, že to není adresa instrukce za voláním JSR (ta je 0006), ale o 1 nižší. Instrukce RTS vezme hodnotu ze zásobníku, k ní přičte 1 a na tu adresu skočí.

K čemu nám bylo tohle mentální cvičení? Ukážeme si totiž jeden princip, který se u osmibitových assemblerů používá docela často, a nejčastěji právě u výpisu různých textů. Pokud se text vyskytuje v programu jen jednou a je konstantní (tj. typicky nějaké hlášení), tak je pro programátora pohodlné napsat ho přímo do kódu, tam, kde potřebuje. Ušetří tím sekvenci „zadej někam adresu hlášení, co chceš vypsat – zavolej rutinu, která vypíše řetězec ze zadané adresy“. Místo toho jen zavolá podprogram, jehož funkce se dá popsat slovy „vypiš znaky, co se nacházejí za instrukcí JSR, a až narazíš na ukončovací znak 00, tak se vrať za tu nulu, tam pokračuje program.“

Nějak takhle (stále upravujeme kód z předchozího příkladu):

Vidíte, že je tam instrukce volání podprogramu PRIMM (PRint IMMediately), za ní jsou přímo znaky požadované hlášky, ukončené nulou, a za tím zase pokračuje program.

Co musí udělat podprogram PRIMM? Představte si, že je vyvolán. V tu chvíli je na zásobníku „adresa instrukce za JSR – 1“. Ukazatel SP je jeden bajt POD touto hodnotou, návratová adresa je tedy na adresách SP+1 a SP+2.

Nejdřív si uložíme pracovní registry A, X a Y – tím se SP sníží o 3 a situace na zásobníku bude vypadat takto:

SP+5 Vyšší bajt návratové adresy
SP+4 Nižší bajt návratové adresy
SP+3 Obsah registru A
SP+2 Obsah registru X
SP+1 Obsah registru Y
SP První volná pozice na zásobníku

Takže na adrese SP + 0100h + 4 je nižší bajt návratové adresy, na adrese SP + 0100h + 5 je vyšší. Tuto hodnotu si můžeme někam zkopírovat – ideálně do zero page do dvou buněk vedle sebe. Výhodně pak využijeme adresní mód IZY. Připomeňme si: tento mód vezme adresu ze dvou vedle sebe ležících paměťových míst, k té adrese přičte obsah registru Y a výsledek udává adresu, kam se má sahat pro data.

Jakmile narazíme na konec řetězce (anebo nám přeteče registr Y), tak končíme s vypisováním. Teď je potřeba vzít tu původní adresu, k ní přičíst počet vypsaných znaků (tedy registr Y), tu pak zase zapsat na zásobník – a pak už jen standardně vrátit obsah registrů a provést RTS.

A protože vlastní studium zdrojového kódu řekne víc, než sáhodlouhé popisy, tak bez dalšího vysvětlování – podprogram PRIMM:

Můžete si zkusit složit celý zdrojový kód a vyzkoušet, jak hezky funguje. (Nebo si můžete kliknout sem a on se vám automaticky přidá do pracovního prostoru v ASM80 IDE).

(Rutina PRIMM pochází z operačního systému Commodore C64 a je mírně upravena, viz zdroj.)

Pro pozorné: v kódu je jedna chyba, která jsou dvě chyby, které se projeví při určité konstelaci – zkuste na ně přijít.

Mimochodem – existuje ještě jeden používaný způsob označování konce řetězců, hlavně u anglických textů. Kromě zadaného počtu znaků + řetězce nebo řetězce ukončeného bajtem 00h (u CP/M služba pro vypisování řetězců používá ukončování znakem $) můžeme použít i trik, který počítá s tím, že anglická abeceda si v ASCII vystačí se znaky z rozsahu 00h-7Fh. Pak stačí poslednímu znaku nastavit nejvyšší bit na 1 (tj. posunout jej do rozsahu 80h-FFh). Ze znaku „!“ (kód 21h) se tak stane znak s kódem A1h. No a postup je prostý – před vypsáním znaku použijeme AND s hodnotou 7Fh (abychom nastavili nejvyšší bit na 0), znak vypíšeme a pak zkontrolujeme, jestli není nejvyšší bit roven 1 (u 6502 třeba tak, že ho načteme do registru A – tím se nejvyšší bit zkopíruje do příznaku N). Pokud ano, byl to poslední znak a my se můžeme vrátit. Napsání rutiny, která bude takto pracovat, nechám už na vás, máte to za domácí úkol…

Líbil se vám článek? Podpořte autora na Patreonu
Příspěvek byl publikován v rubrice 6502. Můžete si uložit jeho odkaz mezi své oblíbené záložky.

5 komentáře u 6502 – !etěvs ,johA

  1. Roman Bórik napsal:

    Ak sa nemýlim, tak problém nastane v prípade, ak je pred volaním rutiny PRIMM register SP nastavený na hodnotu $00.
    Keďže je zásobník iba v 1. stránke, tak sa po inštrukcii JSR uloží vyšší byte návratovej adresy na adresu $0100 a nižší byte na adresu $01FF. Inštrukcia LDA $0105,X pre získanie vyššieho bytu však vezme byte z adresy $0200 (SP má v tomto okamihu hodnotu $FB).
    Takže zobrazované znaky silne závisia od hodnoty na adrese $0200, čo by ešte nebola taká „katastrofa“. Otázny je práve návrat z rutiny, pretože po dopočítaní návratovej adresy podľa počtu zobrazených znakov, sa na zásobníku modifikuje iba nižší byte a tak skutočný „návrat“ môže byť kto vie kde…

    • Martin Malý napsal:

      Tak to je ta druhá. 🙂 První, kterou jsem měl na mysli já, souvisí se stavem, kdy rutina skončí s tím, že přeteče registr Y, tj. vypíše se 255 znaků. Ale nebudu psát víc, nechám to na čtenářích, a příště si ukážeme patch.

      • Roman Bórik napsal:

        Nad tým testom pretečenia indexu X/Y pri tlači znakov som sa pozastavoval sám pre seba už pri predošlých príkladoch, ale povedal som si, že obvykle nie sú jednotlivé tlačené texty také dlhé…

        V aktuálnom prípade sa dá tento problém vyriešiť tak, že sa za inštrukciu
        BNE PRIM2 ; a pokud jsme ještě nepřetočili počítadlo, tak pokračujeme v tisknutí znaků.

        pridáme

        INC $01 ; Když už je Y nulové, tak posuneme ukazatel do další stránky.
        JMP PRIM2 ; a vrátíme se do smyčky

  2. odpad napsal:

    Tak se mi to tady začíná líbit čím dál víc 🙂
    Opět – díky!

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *