Obsah
1. Programátorský model mikroprocesoru MOS 6502
2. Registry a příznakové bity mikroprocesoru MOS 6502
3. Aritmetické a logické instrukce
4. Praktický příklad: výpočet barvy s využitím instrukce pro součet
6. Praktický příklad: výpočet barvy založený na bitovém posunu
8. Praktický příklad: skok do podprogramu bez předávání parametrů
9. Skok do podprogramu s předáním parametrů
10. Rozsáhlejší projekt: tisk hexadecimálních číslic na obrazovku
12. Interní kódy znaků při zápisu do video paměti
13. Realizace tisku jedné decimální číslice
14. Převod hodnoty 0..15 na kód znaku odpovídajícího hexadecimální cifře
15. Realizace tisku jedné hexadecimální číslice
17. Strukturovaná varianta příkladu pro tisk jedné hexadecimální číslice
18. Příloha: Makefile pro překlad všech demonstračních příkladů
19. Repositář s demonstračními příklady
1. Programátorský model mikroprocesoru MOS 6502
Z pohledu programátora, který pracuje v assembleru, se mikroprocesor MOS 6502 dosti podstatným způsobem odlišuje jak od konkurenčního Intelu 8080 (i od později vydaného Zilogu Z80), tak i například od čipu RCA-1802. Zatímco mikroprocesor Intel 8080 obsahoval poměrně rozsáhlou sadu obecně použitelných osmibitových registrů (konkrétně se jednalo o registry A, B, C, D, E, H a L), které se u některých instrukcí kombinovaly do šestnáctibitových registrových párů, obsahoval MOS 6502 pouze jeden osmibitový akumulátor (registr A) a dva taktéž osmibitové index-registry pojmenované X a Y. Oba zmíněné typy mikroprocesorů samozřejmě obsahovaly další speciální registry, mezi které patří ukazatel na vrchol zásobníku (SP), programový čítač (PC) a příznakový registr (F nebo P).
Na první pohled by se mohlo zdát, že počet pracovních registrů mikroprocesoru MOS 6502 je zcela nedostatečný pro provádění většiny aritmetických či logických operací. Ve skutečnosti tomu tak ovšem není, protože tento mikroprocesor podporuje načtení druhého operandu z operační paměti (rychlost RAM nebyla tak limitujícím faktorem, jako je tomu dnes – ve skutečnosti byl přístup do RAM dvojnásobně rychlý v porovnání s mikroprocesorem, což například umožnilo konstrukci sofistikovaných grafických subsystémů). U mnoha instrukcí je navíc podporován větší počet adresovacích režimů; celkově je možné operandy strojových instrukcí adresovat třinácti navzájem odlišnými způsoby. Při adresování se často používají oba index-registry, které je možné inkrementovat a dekrementovat – tím je umožněno provádění blokových přenosů dat, mazání souvislé oblasti paměti atd.
Mikroprocesor MOS 6502 také zavádí pojem takzvané nulté stránky paměti, která byla důsledně využita v instrukční sadě. Jedná se o prvních 256 bytů operační paměti, kterou je možné adresovat zjednodušeným způsobem. Adresa libovolné buňky z nulté stránky paměti je totiž uložena v jednom bajtu v operačním kódu instrukce, takže celá instrukce může být kratší (typicky pouze dva bajty). Současně je i provádění instrukcí adresujících nultou stránku paměti rychlejší než při šestnáctibitovém adresování (například se provádí pouze osmibitové sčítání atd.).
Z tohoto důvodu se můžeme na nultou stránku paměti dívat jako na pole 256 registrů resp. alternativně na 128 plnohodnotně využitelných 16bitových ukazatelů (musíme si opět uvědomit, že operační paměti byly v té době stejně rychlé jako procesor, takže čtení či zápis dat do paměti byla záležitost jednoho či dvou cyklů). Myšlenka nulté stránky paměti byla dále rozšířena v procesoru Motorola 6809, kde se však tato stránka dala v adresovatelné paměti posouvat na libovolné místo, podobně jako v pokračovateli 6502 – 16bitovém čipu 65816 (použit například v herní konzoli SNES).
2. Registry a příznakové bity mikroprocesoru MOS 6502
V předchozí kapitole jsme si řekli, že osmibitový mikroprocesor MOS 6502 obsahoval pouze minimální, ovšem ještě stále prakticky použitelný počet registrů. Všechny tyto registry jsou vypsány v následující tabulce:
| # | Registr | Šířka | Význam |
|---|---|---|---|
| 1 | A | 8 bitů | akumulátor |
| 2 | X | 8 bitů | index registr (osmibitová adresa nebo offset adresy) |
| 3 | Y | 8 bitů | index registr (osmibitová adresa nebo offset adresy) |
| 4 | SP | 8 bitů | část ukazatele na vrchol zásobníku (+ $0100) |
| 5 | PC | 16 bitů | čítač instrukcí |
| 6 | P | 7/8 bitů | příznakový a stavový registr |
Většina aritmetických a logických operací používala jako jeden z operandů akumulátor A. Druhý operand (pokud se pochopitelně jednalo o instrukci se dvěma operandy) byl typicky načítán z operační paměti. Přitom se pro adresování často používaly index registry X a Y. Ukazatel na vrchol zásobníku SP (nebo jen S) dokázal adresovat zásobník v rozsahu $0100 až $01FF, tedy 256 bajtů (což většinou postačuje při práci v assembleru, ovšem vyšší programovací jazyky nedokážou zásobník využívat jako zásobníkový rámec, resp. mohou, ale s omezeními). A příznakový registr P měl obsazen jen sedm bitů:
| Bit | Označení | Význam |
|---|---|---|
| 7 | N | záporný výsledek |
| 6 | V | přetečení do sedmého bitu |
| 5 | – | neobsazeno |
| 4 | B | rozlišení přerušení od instrukce BRK či PHP |
| 3 | D | režim výpočtů: binární versus BCD |
| 2 | I | zákaz přerušení |
| 1 | Z | nulový výsledek |
| 0 | C | přenos |
3. Aritmetické a logické instrukce
Mikroprocesor MOS 6502 obsahuje, což může být překvapující, pouze 56 instrukcí, přičemž mnoho instrukcí podporuje větší množství adresovacích režimů a tudíž i více variant (i tak však zdaleka není obsazeno všech 256 možných kombinací – ty byly postupně obsazovány v dalších procesorech, popř. na původním MOS 6502 měly sice oficiálně nedokumentovanou, ovšem logickou/očekávanou funkci [odkaz je funkční jen „náhodně“]).
Nejprve si popíšeme aritmetické a logické instrukce mikroprocesoru MOS 6502. Většina dále popsaných instrukcí jako svůj první operand akceptuje akumulátor (viz předchozí kapitolu) a druhým operandem může být konstanta popř. hodnota načtená z operační paměti s využitím minule popsaných adresovacích režimů. Výjimkou jsou instrukce s jediným operandem, v nichž nemusí vystupovat akumulátor, popř. instrukce, v nichž je přímo operand vyjádřen názvem instrukce (INX znamená zvýšení obsahu index registru X o jedničku atd.):
| # | Instrukce | Plné jméno | Popis |
|---|---|---|---|
| 1 | ADC | add with carry | součet hodnoty s akumulátorem (včetně přetečení) |
| 2 | SBC | subtract with carry | odečtení hodnoty od akumulátoru (včetně výpůjčky) |
| 3 | AND | and with accumulator | logické AND s akumulátorem |
| 4 | ORA | or with accumulator | logické OR s akumulátorem |
| 5 | EOR | exclusive or with accumulator | logické XOR s akumulátorem |
| 6 | INC | increment | zvýšení hodnoty o 1 (kupodivu nelze s akumulátorem, ovšem s pamětí ano) |
| 7 | INX | increment X | zvýšení hodnoty index registru X o 1 |
| 8 | INY | increment Y | zvýšení hodnoty index registru Y o 1 |
| 9 | DEC | decrement | snížení hodnoty o 1 (opět nelze s akumulátorem) |
| 10 | DEX | decrement X | snížení hodnoty index registru X o 1 |
| 11 | DEY | decrement Y | snížení hodnoty index registru Y o 1 |
| 12 | CMP | compare with accumulator | odečtení hodnoty od akumulátoru bez zápisu výsledku |
| 13 | CPX | compare with X | odečtení hodnoty od index registru X bez zápisu výsledku |
| 14 | CPY | compare with Y | odečtení hodnoty od index registru Y bez zápisu výsledku |
| 15 | BIT | bit test | logické AND bez uložení výsledků (změní se jen příznakové bity) |
4. Praktický příklad: výpočet barvy s využitím instrukce pro součet
Ukažme si nyní použití instrukcí ADC a CLC na sice poněkud umělém, ale snadno pochopitelném příkladu. Budeme v něm počítat kód barvy, který je složený ze dvou hodnot. První hodnotou je číslo barevného odstínu (0–15) a druhou hodnotou intenzita barvy (sudé hodnoty 0–14). Barevný odstín je nutné posunout doprava o čtyři bity popř. vynásobit šestnácti. Můžeme si tedy připravit tabulku se jmény odstínů a jejich hodnotami, které jsou již posunuty o čtyři bity:
HUE_COLOR_GRAY = 16 * $0 HUE_COLOR_GOLD = 16 * $1 HUE_COLOR_ORANGE_1 = 16 * $2 HUE_COLOR_PINK = 16 * $3 HUE_COLOR_MAGENTA = 16 * $4 HUE_COLOR_BLUE = 16 * $5 HUE_COLOR_INDIGO = 16 * $6 HUE_COLOR_SKY_BLUE = 16 * $7 HUE_COLOR_ROYAL_BLUE = 16 * $8 HUE_COLOR_LIGHT_BLUE = 16 * $9 HUE_COLOR_TURQOISE = 16 * $A HUE_COLOR_AQUAMARIN = 16 * $B HUE_COLOR_SEA_GREEN = 16 * $C HUE_COLOR_LIGHT_GREEN = 16 * $D HUE_COLOR_OLIVE = 16 * $E HUE_COLOR_ORANGE_2 = 16 * $F
Výpočet kódu barvy na základě čísla jejího odstínu a intenzity může proběhnout takto – do akumulátoru načteme odstín (již posunutý o čtyři bity) a přičteme k němu intenzitu (bez započtení příznaku přenosu). Následně je již možné nastavit barvu pozadí s využitím vypočteného kódu barvy:
lda #HUE_COLOR_MAGENTA ; kod odstinu barvy
clc ; vymazat priznak prenosu
adc #6 ; svetlost v rozsahu 0..14 s krokem 2
sta COLOR2 ; ulozit do registru COLOR2
Celý zdrojový kód příkladu může vypadat následovně:
.include "atari.inc"
HUE_COLOR_GRAY = 16 * $0
HUE_COLOR_GOLD = 16 * $1
HUE_COLOR_ORANGE_1 = 16 * $2
HUE_COLOR_PINK = 16 * $3
HUE_COLOR_MAGENTA = 16 * $4
HUE_COLOR_BLUE = 16 * $5
HUE_COLOR_INDIGO = 16 * $6
HUE_COLOR_SKY_BLUE = 16 * $7
HUE_COLOR_ROYAL_BLUE = 16 * $8
HUE_COLOR_LIGHT_BLUE = 16 * $9
HUE_COLOR_TURQOISE = 16 * $A
HUE_COLOR_AQUAMARIN = 16 * $B
HUE_COLOR_SEA_GREEN = 16 * $C
HUE_COLOR_LIGHT_GREEN = 16 * $D
HUE_COLOR_OLIVE = 16 * $E
HUE_COLOR_ORANGE_2 = 16 * $F
.CODE
.proc main
lda #HUE_COLOR_MAGENTA ; kod odstinu barvy
clc ; vymazat priznak prenosu
adc #6 ; svetlost v rozsahu 0..14 s krokem 2
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
Po překladu a spuštění tohoto demonstračního příkladu by se měla nastavit fialová barva pozadí se zhruba střední intenzitou (6/15):
5. Bitové posuny a rotace
Samostatnou skupinu instrukcí tvoří instrukce, které dokážou provádět bitové posuny a rotace. Pro MOS 6502, ostatně podobně, jako i pro mnohé další osmibitové procesory, je typické, že se posun/rotace provádí o jediný bit; není tedy možné nastavit ani specifikovat větší počet bitů. V praxi se tak můžeme setkat s opakovaným použitím těch samých instrukcí za sebou:
| # | Instrukce | Plné jméno | Popis |
|---|---|---|---|
| 16 | ASL | arithmetic shift left | aritmetický posun doleva o jeden bit |
| 17 | LSR | logical shift right | logický posun doprava o jeden bit |
| 18 | ROL | rotate left | rotace doleva o jeden bit |
| 19 | ROR | rotate right | rotace doprava o jeden bit |
U instrukcí ASL a LSR je do výsledku nasouván nulový bit. Mikroprocesor MOS 6502 tedy nepodporuje aritmetický posun doprava (se znaménkem), to je však možné v případě potřeby relativně snadno simulovat. Operace rotací vždy pracují s devíti bity – osmi bity operandu a příznakem carry.
Všechny tyto operace nastavují resp. modifikují příznakové bity přenosu (carry), nulovosti (zero) a záporného výsledku (negative). I tím se MOS 6502 odlišuje od dalších mikroprocesorů, které typicky nastavují jen příznak přenosu.
6. Praktický příklad: výpočet barvy založený na bitovém posunu
Pokusme se nyní o využití instrukce pro provedení aritmetického posunu doleva, tj. instrukce, která vlastně provádí násobení dvěma. V úvodním demonstračním příkladu jsme byli nuceni posunout (či vynásobit) index barvového odstínu o čtyři bity již v definici odstínů. Nyní budeme postupovat odlišně. Všech šestnáct odstínů bude reprezentováno hodnotami 0 až 15:
HUE_COLOR_GRAY = $0 HUE_COLOR_GOLD = $1 HUE_COLOR_ORANGE_1 = $2 HUE_COLOR_PINK = $3 HUE_COLOR_MAGENTA = $4 HUE_COLOR_BLUE = $5 HUE_COLOR_INDIGO = $6 HUE_COLOR_SKY_BLUE = $7 HUE_COLOR_ROYAL_BLUE = $8 HUE_COLOR_LIGHT_BLUE = $9 HUE_COLOR_TURQOISE = $A HUE_COLOR_AQUAMARIN = $B HUE_COLOR_SEA_GREEN = $C HUE_COLOR_LIGHT_GREEN = $D HUE_COLOR_OLIVE = $E HUE_COLOR_ORANGE_2 = $F
Výpočet kódu barvy tedy bude vypadat takto: přímo v kódu vynásobíme index odstínu konstantou 16. Pochopitelně namísto násobení použijeme aritmetický posun doleva, který bude opakovaný čtyřikrát za sebou. Poté k mezivýsledku připočítání světlost:
lda #HUE_COLOR_MAGENTA ; kod odstinu barvy
clc ; vymazat priznak prenosu
asl A ; provest 4x aritmeticky posun doprava
asl A
asl A
asl A
adc #6 ; svetlost v rozsahu 0..14 s krokem 2
sta COLOR2 ; ulozit do registru COLOR2
Výsledek by měl vypadat následovně:
Pro úplnost si pochopitelně uvedeme úplný zdrojový kód tohoto demonstračního příkladu:
.include "atari.inc"
HUE_COLOR_GRAY = $0
HUE_COLOR_GOLD = $1
HUE_COLOR_ORANGE_1 = $2
HUE_COLOR_PINK = $3
HUE_COLOR_MAGENTA = $4
HUE_COLOR_BLUE = $5
HUE_COLOR_INDIGO = $6
HUE_COLOR_SKY_BLUE = $7
HUE_COLOR_ROYAL_BLUE = $8
HUE_COLOR_LIGHT_BLUE = $9
HUE_COLOR_TURQOISE = $A
HUE_COLOR_AQUAMARIN = $B
HUE_COLOR_SEA_GREEN = $C
HUE_COLOR_LIGHT_GREEN = $D
HUE_COLOR_OLIVE = $E
HUE_COLOR_ORANGE_2 = $F
.CODE
.proc main
lda #HUE_COLOR_MAGENTA ; kod odstinu barvy
clc ; vymazat priznak prenosu
asl A ; provest 4x aritmeticky posun doprava
asl A
asl A
asl A
adc #6 ; svetlost v rozsahu 0..14 s krokem 2
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
7. Skoky a rozvětvení
Následují instrukce skoku, popř. skoku a výskoku ze subrutiny (podprogramu). Skákat je možné v rámci celé adresovatelné RAM, tedy v rozsahu 64kB:
| # | Instrukce | Plné jméno | Popis |
|---|---|---|---|
| 20 | JMP | jump | skok (existuje několik adresovacích režimů) |
| 21 | JSR | jump to subroutine | skok do podprogramu s uložením návratové adresy na zásobník |
| 22 | RTS | return from subroutine | návrat z podprogramu |
| 23 | RTI | return from interrupt | návrat z prerušovací rutiny |
Relativní skoky v rámci rozsahu –128 až 127 jsou naproti tomu provedeny na základě vyhodnocení nějaké podmínky, konkrétně testování zvoleného příznakového bitu. Oproti Motorole 6800 byl počet podmíněných skoků snížen na polovinu, takže některé kombinace podmínek neexistují (včetně BRA a BRN):
| # | Instrukce | Plné jméno | Popis |
|---|---|---|---|
| 24 | BCC | branch on carry clear | rozvětvení za podmínky C==0 |
| 25 | BCS | branch on carry set | rozvětvení za podmínky C==1 |
| 26 | BEQ | branch on equal (zero set) | rozvětvení za podmínky Z==1 |
| 27 | BMI | branch on minus (negative set) | rozvětvení za podmínky N==1 |
| 28 | BNE | branch on not equal (zero clear) | rozvětvení za podmínky Z==0 |
| 29 | BPL | branch on plus (negative clear) | rozvětvení za podmínky N==0 |
| 30 | BVC | branch on overflow clear | rozvětvení za podmínky O==0 |
| 31 | BVS | branch on overflow set | rozvětvení za podmínky O==1 |
8. Praktický příklad: skok do podprogramu bez předávání parametrů
Skok do podprogramu lze realizovat instrukcí JSR (Jump to SubRoutine) a návrat z podprogramu instrukcí RTS (ReTurn from Subroutine). Při skoku do podprogramu se na zásobník uloží návratová adresa, konkrétně adresa instrukce za JSR. A pochopitelně při návratu ze subrutiny se adresa získá právě ze zásobníku. Vzhledem k tomu, že zásobník má maximální kapacitu 256 bajtů, lze provést pouze 127 skoků do subrutiny (potom se začnou přepisovat adresy na nejhlubších prvcích zásobníku).
Podívejme se nyní, jak se v assembleru CA65 subrutiny zapisují. Používají se zde konstrukce .proc a .endproc, jejichž výhoda je ta, že identifikátory deklarované v subrutině (tedy většinou návěští) jsou pouze lokální. Vytvoříme novou subrutinu pro tisk znaku na začátek obrazovky. Tuto subrutinu potom zavoláme:
.include "atari.inc"
.CODE
.proc main
jsr print_char
loop: jmp loop
.endproc
.proc print_char
lda #33 ; kod znaku, ktery se bude tisknout
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
rts
.endproc
end: ; potrebujeme znat adresu konce kodoveho segmentu
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word 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
Výsledek by měl vypadat následovně:
9. Skok do podprogramu s předáním parametrů
V mnoha případech je nutné subrutinám předávat parametry. Pro tento účel se používají různé techniky: původně se parametry předávaly přes pracovní registry (což počet parametrů nutně omezuje), posléze se přešlo k předávání parametrů přes zásobník (ovšem nikoli na MOS 6502) a dnes se vracíme k předávání parametrů opět přes registry, kterých je ovšem nyní o řád více, než v případě minimalistického MOS 6502. My si prozatím vystačíme s předáváním několika parametrů, a proto využijeme registry A, X a Y. Až ve chvíli, kdy to nebude dostačující, použijeme odlišný způsob, například předávání přes oblast v nulté stránce paměti atd.
Subrutinu pro tisk znaku upravíme do takové podoby, že bude v akumulátoru A akceptovat kód tisknutého znaku:
.include "atari.inc"
.CODE
.proc main
lda #10 ; kod znaku, ktery se bude tisknout
jsr print_char ; tisk znaku
loop: jmp loop
.endproc
.proc print_char
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
rts
.endproc
end: ; potrebujeme znat adresu konce kodoveho segmentu
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word 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
Výsledek by měl v tomto případě vypadat takto:
10. Rozsáhlejší projekt: tisk hexadecimálních číslic na obrazovku
Zbytek dnešního článku a začátek článku následujícího bude věnován zdánlivě jednoduché úloze, která ovšem v assembleru bude relativně rozsáhlá (a navíc se u ní naučíme mnoho nových postupů). Budeme se snažit o vytvoření subrutiny, která na obrazovku vytiskne hexadecimální hodnotu v rozsahu 0..0×ffff. Ovšem to není triviální úloha, zejména pokud s programováním v assembleru teprve začínáme. Proto si tuto úlohu rozdělíme do více kroků:
- Tisk jediné dekadické cifry 0 až 9 na první místo na obrazovce
- Tisk hexadecimální cifry 0 až F na první místo na obrazovce
- Tisk hexadecimální hodnoty v rozsahu 0 až 0×ff na první dvě místa na obrazovce
- Tisk hexadecimální hodnoty v rozsahu 0 až 0×ffff na první čtyři místa na obrazovce
- Rozšíření podprogramu o umožnění specifikace souřadnic na obrazovce, kde se má provést zápis
- Rozšíření celého I/O systému o koncept textového kurzoru
11. ATASCII
Při programování osmibitových mikropočítačů Atari se dříve či později setkáme se zkratkou ATASCII. Tato zkratka znamená Atari ASCII a jedná se o tabulku, která každému ze 128 tisknutelných znaků přiřazuje hodnotu 0 až 128. ATASCII je do značné míry podobná standardní tabulce ASCII, ovšem některé znaky chybí (složené závorky atd.) a jiné naopak „přebývají“ (ale to platí i pro mnohé další mikropočítače, které většinou doplňují nějaké znaky na pozice 0 až 31). ATASCII vypadá následovně:
| Znak | ATASCII | Znak | ATASCII | Znak | ATASCII | Znak | ATASCII |
|---|---|---|---|---|---|---|---|
| CTRL-, | 0 | SPACE | 32 | @ | 64 | CTRL-. | 96 |
| CTRL-A | 1 | ! | 33 | A | 65 | a | 97 |
| CTRL-B | 2 | " | 34 | B | 66 | b | 98 |
| CTRL-C | 3 | # | 35 | C | 67 | c | 99 |
| CTRL-D | 4 | $ | 36 | D | 68 | d | 100 |
| CTRL-E | 5 | % | 37 | E | 69 | e | 101 |
| CTRL-F | 6 | & | 38 | F | 70 | f | 102 |
| CTRL-G | 7 | ' | 39 | G | 71 | g | 103 |
| CTRL-H | 8 | ( | 40 | H | 72 | h | 104 |
| CTRL-I | 9 | ) | 41 | I | 73 | i | 105 |
| CTRL-J | 10 | * | 42 | J | 74 | j | 106 |
| CTRL-K | 11 | + | 43 | K | 75 | k | 107 |
| CTRL-L | 12 | , | 44 | L | 76 | l | 108 |
| CTRL-M | 13 | – | 45 | M | 77 | m | 109 |
| CTRL-N | 14 | . | 46 | N | 78 | n | 110 |
| CTRL-O | 15 | / | 47 | O | 79 | o | 111 |
| CTRL-P | 16 | 0 | 48 | P | 80 | p | 112 |
| CTRL-Q | 17 | 1 | 49 | Q | 81 | q | 113 |
| CTRL-R | 18 | 2 | 50 | R | 82 | r | 114 |
| CTRL-S | 19 | 3 | 51 | S | 83 | s | 115 |
| CTRL-T | 20 | 4 | 52 | T | 84 | t | 116 |
| CTRL-U | 21 | 5 | 53 | U | 85 | u | 117 |
| CTRL-V | 22 | 6 | 54 | V | 86 | v | 118 |
| CTRL-W | 23 | 7 | 55 | W | 87 | w | 119 |
| CTRL-X | 24 | 8 | 56 | X | 88 | x | 120 |
| CTRL-Y | 25 | 9 | 57 | Y | 89 | y | 121 |
| CTRL-Z | 26 | : | 58 | Z | 90 | z | 122 |
| ESCAPE | 27 | ; | 59 | [ | 91 | CTRL-; | 123 |
| UP ARROW | 28 | < | 60 | \ | 92 | | | 124 |
| DOWN ARROW | 29 | = | 61 | ] | 93 | CLEAR | 125 |
| LEFT ARROW | 30 | > | 62 | ^ | 94 | DELETE | 126 |
| RIGHT ARROW | 31 | ? | 63 | _ | 95 | TAB | 127 |
12. Interní kódy znaků při zápisu do video paměti
Pokusme se tedy přímo do obrazové paměti rezervované pro textový režim zapsat kódy 0 až 255. Prozatím nemáme všechny informace potřebné k tomu naprogramovat tisk celé tabulky přímo v assembleru, proto se uchýlím ke standardnímu Atari BASICu. Celý postup je zřejmý z komentářů: do mřížky 16×16 znaků se zapisují hodnoty 0 až 255, a to do oblasti paměti, která začíná na adrese uložené v bajtech 88 a 89:
1 REM ***************************** 2 REM Vypis vsech znaku primo na 3 REM obrazovku v rezimu GRAPHICS 8 4 REM 5 REM Uprava pro Atari BASIC 6 REM 7 REM ***************************** 8 REM 9 REM 10 GRAPHICS 0 20 REM Pocatecni adresa video RAM 25 START=PEEK(88)+256*PEEK(89) 30 REM Tisk sestnacti radku 35 FOR Y=0 TO 15 40 REM Tisk sestnacti sloupcu 45 FOR X=0 TO 15 50 REM Adresa ve video RAM pro zapis 55 ADDR=START+X+Y*40 60 REM Kod znaku pro zapis 65 CODE=X+Y*16 70 REM Vlastni zapis znaku na obrazovku 75 POKE ADDR,CODE 80 NEXT X 85 NEXT Y 90 REM finito 99 GOTO 99
Výsledek bude vypadat následovně:
Z tohoto screenshotu je zřejmé, že se ve skutečnosti nepoužila ATASCII. Osmibitové Atari mají několik nepříjemných vlastností (skutečně jen několik :-). Patří mezi ně i fakt, že kódy v ATASCII neodpovídají kódu znaku zapsaného na obrazovku a navíc ani neodpovídají kódu odpovídající stisknuté klávesy. Zaměřme se na první rozdíl, tj. na to, jaký znak odpovídá hodnotě 0..255 zapsané do textové obrazovky (přímým zápisem do paměti). Vztah mezi zapsaným bajtem a zobrazeným znakem je následující:
| Znak | Interní kód | Znak | Interní kód | Znak | Interní kód | Znak | Interní kód |
|---|---|---|---|---|---|---|---|
| SPACE | 0 | @ | 32 | CTRL-, | 64 | CTRL-. | 96 |
| ! | 1 | A | 33 | CTRL-A | 65 | a | 97 |
| " | 2 | B | 34 | CTRL-B | 66 | b | 98 |
| # | 3 | C | 35 | CTRL-C | 67 | c | 99 |
| $ | 4 | D | 36 | CTRL-D | 68 | d | 100 |
| % | 5 | E | 37 | CTRL-E | 69 | e | 101 |
| & | 6 | F | 38 | CTRL-F | 70 | f | 102 |
| ' | 7 | G | 39 | CTRL-G | 71 | g | 103 |
| ( | 8 | H | 40 | CTRL-H | 72 | h | 104 |
| ) | 9 | I | 41 | CTRL-I | 73 | i | 105 |
| * | 10 | J | 42 | CTRL-J | 74 | j | 106 |
| + | 11 | K | 43 | CTRL-K | 75 | k | 107 |
| , | 12 | L | 44 | CTRL-L | 76 | l | 108 |
| – | 13 | M | 45 | CTRL-M | 77 | m | 109 |
| . | 14 | N | 46 | CTRL-N | 78 | n | 110 |
| / | 15 | O | 47 | CTRL-O | 79 | o | 111 |
| 0 | 16 | P | 48 | CTRL-P | 80 | p | 112 |
| 1 | 17 | Q | 49 | CTRL-Q | 81 | q | 113 |
| 2 | 18 | R | 50 | CTRL-R | 82 | r | 114 |
| 3 | 19 | S | 51 | CTRL-S | 83 | s | 115 |
| 4 | 20 | T | 52 | CTRL-T | 84 | t | 116 |
| 5 | 21 | U | 53 | CTRL-U | 85 | u | 117 |
| 6 | 22 | V | 54 | CTRL-V | 86 | v | 118 |
| 7 | 23 | W | 55 | CTRL-W | 87 | w | 119 |
| 8 | 24 | X | 56 | CTRL-X | 88 | x | 120 |
| 9 | 25 | Y | 57 | CTRL-Y | 89 | y | 121 |
| : | 26 | Z | 58 | CTRL-Z | 90 | z | 122 |
| ; | 27 | [ | 59 | ESCAPE | 91 | CTRL-; | 123 |
| < | 28 | \ | 60 | UP ARROW | 92 | | | 124 |
| = | 29 | ] | 61 | DOWN ARROW | 93 | CLEAR | 125 |
| > | 30 | ^ | 62 | LEFT ARROW | 94 | DELETE | 126 |
| ? | 31 | _ | 63 | RIGHT ARROW | 95 | TAB | 127 |
13. Realizace tisku jedné decimální číslice
Začneme tím nejjednodušším možným a přitom stále funkčním příkladem. Budeme v něm realizovat podprogram nazvaný dec_digit, který na obrazovku vytiskne číslici, jejíž hodnota je předána v akumulátoru A. Nebudeme přitom (prozatím) provádět žádné kontroly. Realizace je snadná, protože při pohledu do tabulky uvedené v předchozí kapitole je zřejmé, že pouze postačuje přičíst k číslici konstantu 16 a získáme interní kód odpovídajícího znaku:
.include "atari.inc"
.CODE
.proc main
lda #9 ; cislo, ktere se bude tisknout
jsr dec_digit
loop: jmp loop
.endproc
.proc dec_digit
clc ; vymazat priznak prenosu
adc #16 ; prevod hodnoty na interni kod (ne ATASCII!)
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
; (adresa Video RAM je na adresách 88 a 89)
rts ; navrat z podprogramu
.endproc
end: ; potrebujeme znat adresu konce kodoveho segmentu
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word 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
Podívejme se na výsledek, který byl získán pro číslici 9 (což přesně odpovídá zdrojovému kódu):
V podprogramu dec_digit nekontrolujeme meze, takže číslice 10 (neexistující) se vytiskne takto (opět se podívejte na tabulku v předchozí kapitole a zjistíte, že dvojtečka skutečně leží za kódem číslice 9):
14. Převod hodnoty 0..15 na kód znaku odpovídajícího hexadecimální cifře
Předchozí příklad nyní upravíme, a to konkrétně do takové podoby, aby dokázal vytisknout nikoli „pouze“ dekadickou číslici, ale číslici hexadecimální. Konkrétně tedy budeme chtít, aby se pro vstupní hodnotu 0, 1, … 9, 10, … 15 vytiskly znaky 0, 1, … 9, A, … F. Samotná realizace bude vyžadovat buď provedení různých triků založených na binárně-desítkovém kódování (tento trik se využíval na Z80) nebo se jednoduše zjistí, zda je vstupní hodnota větší nebo rovna deseti. Pokud tomu tak je, přičteme k hodnotě sedmičku. Tato konstanta je opět získána z tabulky zobrazené ve dvanácté kapitole. Budeme totiž chtít, aby se pro vstup 10 nevytiskla dvojtečka, ale znak A. Ten má kód 33, zatímco dvojtečka má kód 26, tudíž je rozdíl skutečně roven 33–26=7.
.proc hex_digit
cmp #$0a ; test na hodnotu 0-9 nebo 10-15
bcc skip_add ; je to hodnota 0-9
adc #6 ; pricist sedmicku
skip_add:
adc #16 ; prevod hodnoty na interni kod (ne ATASCII!)
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
; (adresa Video RAM je na adresách 88 a 89)
rts ; navrat z podprogramu
.endproc
15. Realizace tisku jedné hexadecimální číslice
Podprogram hex_digit můžeme snadno zařadit do demonstračního příkladu, jehož úplný zdrojový kód bude vypadat následovně:
.include "atari.inc"
.CODE
.proc main
lda #9 ; cislo, ktere se bude tisknout
jsr hex_digit
loop: jmp loop
.endproc
.proc hex_digit
cmp #$0a ; test na hodnotu 0-9 nebo 10-15
bcc skip_add ; je to hodnota 0-9
adc #6 ; pricist sedmicku
skip_add:
adc #16 ; prevod hodnoty na interni kod (ne ATASCII!)
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
; (adresa Video RAM je na adresách 88 a 89)
rts ; navrat z podprogramu
.endproc
end: ; potrebujeme znat adresu konce kodoveho segmentu
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word 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
Chování příkladu si pochopitelně otestujeme, nejdříve pro vstupní hodnotu v rozsahu 0 až 9:
Ve druhém kroku předáme podprogramu libovolnou hodnotu z rozsahu 10 až 15 a opět zkontrolujeme výsledek:
16. Refaktoring v assembleru?
Programy psané v assembleru jsou psány na relativně nízké úrovni abstrakce. Proto je prakticky nutné nějakým způsobem neustále provádět jejich refaktoring, protože v opačném případě bychom získali „špagetový kód“ skládající se z nic neříkajících instrukcí. Assemblery a do jisté míry i samotné mikroprocesory nám nabízí dvě základní techniky, které je možné využít a různým způsobem je kombinovat. Jedná se o rozdělení úlohy na jednotlivé podprogramy (subrutiny), ideálně s rozumně nastaveným rozhraním, které specifikuje, jakým způsobem se mají do subrutin předávat parametry. A druhou technologií jsou makra, kterými se budeme zabývat v navazujícím článku. Ovšem již samotné rozdělení programu na podprogramy může zvětšit čitelnost a vlastně se dostat na vyšší úroveň abstrakce (o programu budeme uvažovat jako o vzájemně se volajících blocích, nikoli jako o sekvenci instrukcí).
17. Strukturovaná varianta příkladu pro tisk jedné hexadecimální číslice
Program pro tisk jedné hexadecimální číslice se po logické stránce skládá ze tří bloků: hlavního programu, podprogramu pro výpočet interního kódu znaku, který se má tisknout a konečně z podprogramu, který tisk provede. Díky tomu, že se do podprogramů předává pouze jediný parametr, který je navíc osmibitový, lze přenos parametrů realizovat s využitím akumulátoru A. Rozdělení programu do menších celků by tedy mohlo vypadat například následovně:
.include "atari.inc"
.CODE
.proc main
lda #9 ; cislo, ktere se bude tisknout
jsr hex_digit ; prevod na interni kod cislice
jsr print_char ; tisk cislice/znaku
loop: jmp loop
.endproc
.proc hex_digit
cmp #$0a ; test na hodnotu 0-9 nebo 10-15
bcc skip_add ; je to hodnota 0-9
adc #6 ; pricist sedmicku
skip_add:
adc #16 ; prevod hodnoty na interni kod (ne ATASCII!)
rts ; navrat z podprogramu
.endproc
.proc print_char
ldy #0 ; vynulovat registr Y
sta (88), y ; tisk znaku na první místo na obrazovce
; (adresa Video RAM je na adresách 88 a 89)
rts
.endproc
end: ; potrebujeme znat adresu konce kodoveho segmentu
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word 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
Samozřejmě si můžeme otestovat, že vše (stále) funguje tak, jak se očekává:
18. Příloha: Makefile pro překlad všech demonstračních příkladů
Všechny minule 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 \
color_computation_1.xex color_computation_2.xex \
subroutine_1.xex subroutine_2 \
hex_number_1.xex hex_number_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/ - Color names
https://atariwiki.org/wiki/Wiki.jsp?page=Color%20names - ATASCII
https://en.wikipedia.org/wiki/ATASCII - Put characters in display ram isn't ATASCII?
https://forums.atariage.com/topic/359973-put-characters-in-display-ram-isnt-atascii/ - ATASCII And Internal Character Code Values
https://www.atariarchives.org/mapping/appendix10.php










