Programování pro osmibitová Atari: makra asm CA65, trik s instrukcí RTS

Dnes
Doba čtení: 36 minut

Sdílet

Projekt Atari perex.
Autor: Michal Tauchman, podle licence: CC BY-SA 4.0
Projekt Atari perex.
Ve třetím článku o vývoji programů pro osmibitové počítače Atari si popíšeme základy použití maker v assembleru CA65. Vytvoříme příklad pro výpis dvouciferné hexadecimální hodnoty a zmíníme se o triku s instrukcí RTS.

Obsah

1. Makroassemblery

2. Makra bez parametrů v ca65

3. Původní tvar demonstračního příkladu založený na volání subrutin

4. Přepis demonstračního příkladu s využitím maker

5. Jak se liší výsledný strojový kód?

6. Automaticky generovaná návěští

7. Realizace tisku dvouciferného hexadecimálního čísla

8. Příprava projektu pro tisk dvouciferného hexadecimálního čísla

9. Uložení a obnovení obsahu akumulátoru s využitím zásobníku

10. Funkční varianta programu pro tisk hexadecimální hodnoty v rozsahu 0 až ff

11. Makro s realizací pseudoinstrukce pro bitový posun doprava o zadaný počet bitů

12. Úplný zdrojový kód výsledného příkladu pro tisk dvouciferné hexadecimální hodnoty

13. Velikost výsledného strojového kódu

14. Realizace čekání na stisk klávesy

15. Podprogram pro čekání na stisk klávesy a vrácení kódu znaku

16. Úplný zdrojový kód výsledného demonstračního příkladu

17. Příloha A: Seznam všech doposud popsaných instrukcí mikroprocesoru MOS 6502

18. Příloha B: Makefile pro překlad všech demonstračních příkladů

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Makroassemblery

Nástroje typu „assembler“ (mezi něž patří i ca65, který v tomto seriálu primárně používáme) je možné podle principu jejich práce rozdělit do několika kategorií. Do první kategorie spadají assemblery interaktivní, které uživateli nabízejí poměrně komfortní vývojové prostředí, v němž je v případě potřeby možné zapisovat jednotlivé instrukce, spouštět programy, krokovat je, vypisovat obsahy pracovních registrů mikroprocesoru, prohlížet si obsah operační paměti, zásobníku atd. Výhodou byla nezávislost těchto assemblerů na rychlém externím paměťovém médiu, proto jsme se s nimi mohli setkat například na osmibitových domácích mikropočítačích či dnes na různých zařízeních typu IoT (i když zde úlohu pouhého interaktivního assembleru mnohdy přebírá interaktivní debugger).

Druhý typ assemblerů je široce používán dodnes – jedná se vlastně o běžné překladače, kterým se na vstupu předloží zdrojový kód a po překladu se výsledný nativní kód taktéž uloží na paměťové médium (odkud ho lze přímo spustit, což se dělo například v operačním systému DOS, popř. ho ještě před spuštěním slinkovat, což je případ Linuxu a dalších moderních operačních systémů, ovšem i osmibitových mikropočítačů Atari).

Assemblery spadající do druhé kategorie jsou mnohdy vybaveny více či méně dokonalým systémem maker. Odtud ostatně pochází i jejich často používané označení macroassembler. Makra, která se většinou aplikují na zdrojový kód v první fázi překladu, je možné použít pro různé činnosti, ať již se jedná o zjednodušení zápisu kódu či o jeho zkrácení a zpřehlednění. Existují například sady poměrně složitých maker, které do assembleru přidávají některé konstrukce známé z vyšších programovacích jazyků – rozvětvení, programové smyčky, deklaraci objektů atd. Všechny moderní assemblery práci s makry podporují, i když se způsob zápisu maker i jejich základní vlastnosti od sebe odlišují. Z tohoto důvodu se v dnešním článku budeme věnovat (prozatím) pouze makrům ve variantě používané cross assemblerem ca65.

Poznámka: i přes podporu maker je ca65 relativně triviálním jednoprůchodovým assemblerem.

2. Makra bez parametrů v ca65

Makra v assembleru, tedy i v ca65, většinou provádí přímé textové substituce, což mj. znamená, že expanze maker je vykonána v první fázi překladu. V případě assembleru ca65 deklarace makra začíná direktivou .macro a končí direktivou .endmacro (pozor: obě tyto direktivy se zapisují včetně teček na začátku). Za direktivou .macro musí následovat jméno makra a popř. i jeho parametry. Na dalších řádcích je pak vlastní text makra:

.macro 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)
.endmacro

Použití makra je ještě jednodušší než jeho deklarace – kdekoli se prostě uvede jméno makra s případnými parametry. Jakmile assembler zjistí, že se ve zdrojovém kódu nachází jméno makra, provede jeho expanzi, takže se vlastně případné instrukce, ze kterých se text makra skládá, přímo vloží do kódu na místo volání makra:

.proc main
        lda #65                 ; znak, ktery se bude tisknout
        print_char              ; tisk cislice/znaku
loop:   jmp loop
.endproc
Poznámka: existují i standardní makra, které z pohledu programátora rozšiřují instrukční soubor mikroprocesoru 6502 (interně se však makro expanduje obecně na větší množství instrukcí). Následují příklady těchto maker:
; add - Add without carry
.macro  add     Arg1, Arg2
        clc
        .if .paramcount = 2
                adc     Arg1, Arg2
        .else
                adc     Arg1
        .endif
.endmacro
 
; sub - subtract without borrow
.macro  sub     Arg1, Arg2
        sec
        .if .paramcount = 2
                sbc     Arg1, Arg2
        .else
                sbc     Arg1
        .endif
.endmacro
 
; bgt - jump if unsigned greater
.macro  bgt     Arg
        .local  L
        beq     L
        bcs     Arg
L:
.endmacro

3. Původní tvar demonstračního příkladu založený na volání subrutin

Vraťme se na chvíli k demonstračnímu příkladu, který jsme si uvedli na konci předchozího článku. V tomto příkladu se provádí tisk hexadecimální číslice s využitím dvou podprogramů (subrutin), které jsou volány instrukcí JSR a návrat ze subrutin zajišťuje instrukce RTS:

.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 (6+carry)
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

Výsledek získaný po překladu a spuštění tohoto příkladu vypadá následovně:

Obrázek :

Obrázek 1: Hexadecimální číslice vypsaná příkladem. 

Autor: tisnik, podle licence: Rights Managed

4. Přepis demonstračního příkladu s využitím maker

Subrutina je v assembleru ca65 uzavřena do příkazů .proc a .endproc. Typicky taková subrutina končí instrukcí RTS:

.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 (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
        rts                     ; navrat z podprogramu
.endproc

Makro je naproti tomu uzavřeno do příkazů .macro a .endmacro a díky tomu, že se nevolá (ale expanduje do kódu), nemusí být poslední instrukce RTS uvedena:

.macro hex_digit
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
.endmacro

Podprogram (subrutina) se volá s využitím instrukce JSR, tedy následujícím způsobem:

        lda #9                  ; cislo, ktere se bude tisknout
        jsr hex_digit           ; prevod na interni kod cislice

Ovšem makra nejsou volána, ale expandována, takže se do zdrojového kódu zařazují přímo svým jménem (bez volání):

        lda #9                  ; cislo, ktere se bude tisknout
        hex_digit               ; prevod na interni kod cislice

Podobným způsobem můžeme realizovat i makro pro tisk znaku:

.macro 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)
.endmacro

Úplný zdrojový kód takto upraveného příkladu nyní bude vypadat následovně:

.include "atari.inc"
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro hex_digit
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
.endmacro
 
 
.macro 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)
.endmacro
 
 
; ---------------------------------------------------------------------
; Kodovy segment
; ---------------------------------------------------------------------
 
.CODE
 
 
.proc main
        lda #9                  ; cislo, ktere se bude tisknout
        hex_digit               ; prevod na interni kod cislice
        print_char              ; tisk cislice/znaku
loop:   jmp loop
.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

5. Jak se liší výsledný strojový kód?

Zajímavé bude porovnat strojové kódy demonstračního příkladu, který používá subrutiny s příkladem založeným na makrech. Pochopitelně nebudeme zkoumat přímo binární soubory .xex, ale podíváme se na listingy, tj. na textové soubory generované assemblerem. Každý listing má pevný formát, přičemž nás bude nyní zajímat především třetí sloupec obsahující kódy strojových instrukcí i jejich operandů.

Kód příkladu založeného na subrutinách. Povšimněte si instrukcí JSRRTS:

000000r 1               .CODE
000000r 1
000000r 1
000000r 1               .proc main
000000r 1  A9 09                lda #9                  ; cislo, ktere se bude tisknout
000002r 1  20 rr rr             jsr hex_digit           ; prevod na interni kod cislice
000005r 1  20 rr rr             jsr print_char          ; tisk cislice/znaku
000008r 1  4C rr rr     loop:   jmp loop
00000Br 1               .endproc
00000Br 1
00000Br 1
00000Br 1               .proc hex_digit
00000Br 1  C9 0A                cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
00000Dr 1  90 02                bcc skip_add            ; je to hodnota 0-9
00000Fr 1  69 06                adc #6                  ; pricist sedmicku
000011r 1               skip_add:
000011r 1  69 10                adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
000013r 1  60                   rts                     ; navrat z podprogramu
000014r 1               .endproc
000014r 1
000014r 1
000014r 1               .proc print_char
000014r 1  A0 00                ldy #0                  ; vynulovat registr Y
000016r 1  91 58                sta (88), y             ; tisk znaku na první místo na obrazovce
000018r 1                                               ; (adresa Video RAM je na adresách 88 a 89)
000018r 1  60                   rts
000019r 1               .endproc
000019r 1
000019r 1               end:                            ; potrebujeme znat adresu konce kodoveho segmentu
000019r 1
000019r 1
000019r 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   end - 1                 ; konec kodoveho segmentu
000006r 1
000006r 1
000006r 1               .segment "AUTOSTRT"             ; segment s pocatecni adresou
000000r 1  E0 02        .word   RUNAD                   ; naplni se pouze adresy RUNAD a RUNAD+1
000002r 1  E1 02        .word   RUNAD+1
000004r 1  rr rr        .word   main                    ; adresa vstupniho bodu do programu
000006r 1
000006r 1               ; finito
000006r 1

Kód příkladu založeného na expanzi maker. Nyní jsou všechny instrukce vypsány na pouhých pěti řádcích (od řádku 000000r až po řádek 00000Er:

000000r 1               .CODE
000000r 1
000000r 1
000000r 1               .proc main
000000r 1  A9 09                lda #9                  ; cislo, ktere se bude tisknout
000002r 1  C9 0A 90 02          hex_digit               ; prevod na interni kod cislice
000006r 1  69 06 69 10
00000Ar 1  A0 00 91 58          print_char              ; tisk cislice/znaku
00000Er 1  4C rr rr     loop:   jmp loop
000011r 1               .endproc
000011r 1
000011r 1
000011r 1               end:                            ; potrebujeme znat adresu konce kodoveho segmentu
000011r 1
000011r 1
000011r 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   end - 1                 ; konec kodoveho segmentu
000006r 1
000006r 1
000006r 1               .segment "AUTOSTRT"             ; segment s pocatecni adresou
000000r 1  E0 02        .word   RUNAD                   ; naplni se pouze adresy RUNAD a RUNAD+1
000002r 1  E1 02        .word   RUNAD+1
000004r 1  rr rr        .word   main                    ; adresa vstupniho bodu do programu
000006r 1
000006r 1               ; finito
000006r 1
Poznámka: druhý kód je v tomto případě kratší, ovšem to jen díky tomu, že se každé makro použilo pouze jedenkrát. Každá další opakovaná expanze maker bude kód „natahovat“ o délku makra, zatímco volání subrutiny přidá pouhé tři bajty pro instrukci JSR.

6. Automaticky generovaná návěští

Jen pro úplnost se zmiňme o jedné zajímavé a v některých situacích i užitečné vlastnosti assembleru CA65. Ten totiž umožňuje pracovat s automaticky generovanými návěštími (labels), která jsou ve zdrojových kódech označena pouze dvojtečkou (bez textu). Na tato návěští se pak můžeme odkazovat přes :- (směrem k začátku kódu, tedy na řádky předchozí) nebo :+ (směrem ke konci kódu, tedy na řádky následující). Není to sice příliš čitelný způsob zápisu, ovšem má své přednosti, o nichž se zmíníme v navazujících článcích. Nyní si pouze ukažme příklady použití takových návěští.

Prvním příkladem je programová smyčka, konkrétně smyčka, která dokáže vymazat několik bloků paměti. Důležitý je třetí řádek začínající dvojtečkou a poslední skok, který naopak na „dvojtečku“ ukazuje:

.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro

Ovšem podobně je možné realizovat skok dopředu, což je i náš případ. Makro hex_digit lze přepsat i do následující podoby (opět si povšimněte řádku začínajícího dvojtečkou a podmíněného skoku bcc):

.macro hex_digit
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc :+                  ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
.endmacro

Upravený zdrojový kód našeho demonstračního příkladu dostane následující tvar:

.include "atari.inc"
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro hex_digit
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc :+                  ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
.endmacro
 
 
.macro 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)
.endmacro
 
 
; ---------------------------------------------------------------------
; Kodovy segment
; ---------------------------------------------------------------------
 
.CODE
 
 
.proc main
        lda #9                  ; cislo, ktere se bude tisknout
        hex_digit               ; prevod na interni kod cislice
        print_char              ; tisk cislice/znaku
loop:   jmp loop
.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

A opět si ukažme, jak bude vypadat vygenerovaný strojový kód (třetí sloupec):

000000r 1               .proc main
000000r 1  A9 09                lda #9                  ; cislo, ktere se bude tisknout
000002r 1  C9 0A 90 02          hex_digit               ; prevod na interni kod cislice
000006r 1  69 06 69 10
00000Ar 1  A0 00 91 58          print_char              ; tisk cislice/znaku
00000Er 1  4C rr rr     loop:   jmp loop
000011r 1               .endproc
Poznámka: v tomto případě bude výsledek naprosto stejný, jako v příkladu předchozím, což by nemělo být překvapující.

7. Realizace tisku dvouciferného hexadecimálního čísla

Ve druhé části dnešního článku příklad, který jsme až doposud rozšiřovali, vylepšíme tak, aby dokázal tisknout dvouciferné hexadecimální číslo. Teoreticky se jedná o relativně triviální rozšíření stávající funkcionality, ovšem bude vyžadovat určité zásahy. V první řadě budeme muset upravit podprogram pro tisk jedné číslice – nově totiž nebude tisk probíhat pouze do levého horního rohu obrazovky, ale také o jeden znak vpravo od rohu. Dále je nutné nějakým způsobem získat z obsahu akumulátoru (rozsah 0 až 255) hodnotu horní i dolní hexadecimální cifry. To znamená použití instrukce pro maskování bitů (AND) a taktéž instrukce pro logický posun doprava (LSR). A navíc je nutné obsah akumulátoru mezi tisky jednotlivých hexadecimálních cifer uložit a posléze obnovit. K tomu využijeme zásobník a instrukce PHA a PLA (tedy push accumulator a pull accumulator).

8. Příprava projektu pro tisk dvouciferného hexadecimálního čísla

Původní kód demonstračního příkladu je nutné, jak je to patrné z předchozí kapitoly, upravit. Přidáme do něj podprogram nazvaný print2_hex_digits, jehož kostra může vypadat takto:

.proc print_2_hex_digits
        ; TODO: ziskat jen vyssi cislici
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ; TODO: urceni relativniho mista na obrazovce
        jsr print_char          ; tisk cislice/znaku
 
        ; TODO: ziskat jen nizsi cislici
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ; TODO: urceni relativniho mista na obrazovce
        jsr print_char          ; tisk cislice/znaku
.endproc

Z výpisu je patrné, že se volá podprogram nibble_to_hex_char, který převede hodnotu 0..16 na kód znaku. Tento podprogram již známe, jen jsme změnili jeho jméno. A – alespoň prozatím – jsou v kódu chybějící místa označená poznámkami TODO. Ty postupně doplníme.

Zdrojový kód demonstračního příkladu bude po všech výše uvedených úpravách vypadat následovně:

.include "atari.inc"
 
.CODE
 
 
.proc main
        lda #99                 ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
loop:   jmp loop
.endproc
 
 
.proc print_2_hex_digits
        ; TODO: ziskat jen vyssi cislici
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ; TODO: urceni relativniho mista na obrazovce
        jsr print_char          ; tisk cislice/znaku
 
        ; TODO: ziskat jen nizsi cislici
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ; TODO: urceni relativniho mista na obrazovce
        jsr print_char          ; tisk cislice/znaku
.endproc
 
 
.proc nibble_to_hex_char
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
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

9. Uložení a obnovení obsahu akumulátoru s využitím zásobníku

Pro získání vyšších čtyř bitů akumulátoru je zapotřebí provést opakovaný logický posun doprava, což (logicky) poškodí jeho původní obsah. Je tedy nutné původní obsah akumulátoru uložit na vhodné místo a posléze ho (po tisku vyšší číslice) obnovit. Nabízí se pochopitelně zásobník (stack). A přesně pro tento účel mají mikroprocesory MOS 6502 dvojici instrukcí:

# Instrukce Plné jméno Popis
32 PHA push accumulator on stack uložení obsahu akumulátoru na zásobník
33 PLA pull accumulator from stack obnovení obsahu akumulátoru ze zásobníku

Podprogram pro rozdělení hodnoty na dvě hexadecimální číslice s jejich následným tiskem tedy může vypadat takto:

.proc print_2_hex_digits
        pha                     ; ulozit akumulator na zasobnik
        nejvyšší 4 bity přesunout do nižších 4 bitů
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
 
        pla                     ; obnovit obsah akumulátoru
        vymaskovat vyšší 4 bity akumulátoru
        and #$0f                ; ziskat jen nizsi nibble
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku

Převedeno do konkrétních instrukcí mikroprocesoru MOS 6502:

.proc print_2_hex_digits
        pha                     ; ulozit akumulator na zasobnik
        lsr                     ; posun 4 nejvyssich bitu do bitu nejnizsich
        lsr
        lsr
        lsr
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
 
        pla                     ; obnovit obsah akumulátoru
        and #$0f                ; ziskat jen nizsi nibble
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
.endproc
Poznámka: navíc si povšimněte, jak je vyřešen tisk číslice na vhodné místo – úpravou obsahu index registru Y:
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
        ...
        ...
        ...
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
        ...
        ...
        ...
.proc print_char_at_y
        sta (88), y             ; tisk znaku na první místo na obrazovce
                                ; (adresa Video RAM je na adresách 88 a 89)
        rts
.endproc

10. Funkční varianta programu pro tisk hexadecimální hodnoty v rozsahu 0 až ff

Po všech výše popsaných úpravách bude výsledný program vypadat následovně:

.include "atari.inc"
 
.CODE
 
 
.proc main
        lda #$42                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
loop:   jmp loop
.endproc
 
 
.proc print_2_hex_digits
        ; TODO: ziskat jen vyssi cislici
        pha                     ; ulozit akumulator na zasobnik
        lsr                     ; posun 4 nejvyssich bitu do bitu nejnizsich
        lsr
        lsr
        lsr
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
 
        pla
        and #$0f                ; ziskat jen nizsi nibble
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
.endproc
 
 
.proc nibble_to_hex_char
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
        rts                     ; navrat z podprogramu
.endproc
 
 
.proc print_char_at_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

A takto vypadá výsledek po překladu a spuštění příkladu:

Obrázek :

Obrázek 2: Hexadecimální číslo 0x42. 

Autor: tisnik, podle licence: Rights Managed

11. Makro s realizací pseudoinstrukce pro bitový posun doprava o zadaný počet bitů

Logický posun akumulátoru doprava o čtyři bity bylo nutné realizovat čtveřicí instrukcí LSR, protože mikroprocesor MOS 6502 nemá (což je naprosto v souladu s cíli jeho designérů) instrukci pro posun doprava o n bitů. Ovšem samozřejmě nám nic nebrání v deklaraci makra, které po své expanzi logický posun o n bitů provede. Využijeme zde příkazů assembleru .repeat a .endrep, které umožňují opakovat blok:

.macro lsr_ shift_count
        .repeat shift_count
        lsr
        .endrep
.endmacro

Toto makro se ve zdrojovém kódu použije zcela triviálním způsobem:

        lsr_ 4                  ; posun 4 nejvyssich bitu do bitu nejnizsich

Výsledkem expanze bude v tomto konkrétním případě čtveřice instrukcí:

        lsr
        lsr
        lsr
        lsr

To si ověříme v listingu, který ukazuje, že pseudoinstrukce lsr_ 4 je přeložena do čtveřice bajtů 0×4A:

000009r 1  4A 4A 4A 4A      lsr_ 4                  ; posun 4 nejvyssich bitu do bitu nejnizsich

12. Úplný zdrojový kód výsledného příkladu pro tisk dvouciferné hexadecimální hodnoty

Po úpravě příkladu zahrnutím makra lsr_, bude zdrojový kód upraveného demonstračního příkladu vypadat takto:

.include "atari.inc"
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro lsr_ shift_count
        .repeat shift_count
        lsr
        .endrep
.endmacro
 
 
.CODE
 
 
.proc main
        lda #$42                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
loop:   jmp loop
.endproc
 
 
.proc print_2_hex_digits
        pha                     ; ulozit akumulator na zasobnik
        lsr_ 4                  ; posun 4 nejvyssich bitu do bitu nejnizsich
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
 
        pla
        and #$0f                ; ziskat jen nizsi nibble
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
.endproc
 
 
.proc nibble_to_hex_char
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
        rts                     ; navrat z podprogramu
.endproc
 
 
.proc print_char_at_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

13. Velikost výsledného strojového kódu

Demonstrační příklad je již poměrně rozsáhlý, takže je na čase zjistit, jaká je vlastně velikost výsledného strojového kódu. Tu zjistíme nahlédnutím do souboru hex_number8.map, jenž je vytvořen linkerem:

Modules list:
-------------
hex_number_8.o:
    CODE              Offs=000000  Size=00002C  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  00202B  00002C  00001
 
 
Exports list by name:
---------------------
 
 
 
Exports list by value:
----------------------
 
 
 
Imports list:
-------------

Velikost je uvedena na řádku CODE: 2C=44 bajtů.

14. Realizace čekání na stisk klávesy

V závěrečné části dnešního článku se pokusíme demonstrační příklad vylepšit takovým způsobem, aby po stisku (libovolné) klávesy dokázat zobrazit další hexadecimální číslo. Budeme tedy potřebovat přidat podprogram get_key (nebo možná lépe wait_key):

.proc main
        lda #$42                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
        jsr get_key
        lda #$ff                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
loop:   jmp loop
.endproc

Chování tohoto programu si můžeme naznačit dvěma screenshoty:

Obrázek :

Obrázek 3: Výpis prvního čísla a čekání na stisk klávesy. 

Autor: tisnik, podle licence: Rights Managed

Obrázek :

Obrázek 4: Po stisku klávesy se vypíše druhé hexadecimální číslo. 

Autor: tisnik, podle licence: Rights Managed

Samotné čekání na stisk klávesy se může realizovat různými způsoby, například přímým čtením stavu klávesnice. Ovšem můžeme využít i toho, že operační systém Atari (Atari OS) podporuje takzvaná zařízení (devices), přičemž ke každému zařízení náleží několik podprogramů (otevření, zavření, čtení bajtu, zápis bajtu atd.). Zařízení lze vytvářet dynamicky, ovšem několik jich existuje již po nastartování počítače. Jedno z těchto výchozích zařízení se jmenuje E: (editor) a operace čtení (GET) provádí (mj. ) i čekání na stisk klávesy.

Podprogramy pro ovládání zařízení E: jsou uloženy v ROM a samotné vektory (tj. adresy) těchto podprogramů jsou uloženy v tabulce začínající na adrese 0×e420 (tedy taktéž v ROM). Konkrétně nás bude zajímat vektor nazvaný KBHANDLER, tj. adresa podprogramu uložená na adrese 0×e424:

KBHANDLER = $e424               ; rutina pro cteni klavesy

Teoreticky by tedy mělo být možné provést nepřímý skok (indirect jump), který z vektoru na adrese 0×e424 přečte adresu a skočí na ni:

        jmp (KBHANDLER)

V praxi to ovšem takto jednoduše nepůjde, protože z určitých důvodů obsahuje vektor adresu posunutou o jedničku dolů (tedy „pravá“ adresa musí být o jedničku větší). Teoreticky by bylo možné tento problém řešit například vytvořením a uložením nového vektoru:

get_key_vector:
    jmp $FFFF         ; bude přepsáno

init_key_vector:
    lda $E424         ; přečteme původní adresu a přičteme jedničku
    clc
    adc #1
    sta get_key_vector+1
    lda $E425
    adc #0            ; zde se řeší přetečení přes stránku paměti
    sta get_key_vector+2
    rts

Použití:

    jsr init_key_vector
    jsr get_key_vector

To je sice funkční, ale příliš dlouhé řešení, které navíc zbytečně zabírá další tři bajty paměti. Existuje ovšem i trik, který si vysvětlíme v navazující kapitole.

15. Podprogram pro čekání na stisk klávesy a vrácení kódu znaku

Celý trik je založen na znalosti toho, jak pracují instrukce JSR a RTS. Teoreticky je to snadné: první z těchto instrukcí uloží na zásobník návratovou adresu a následně provede skok, druhá instrukce návratovou adresu ze zásobníku přečte a vrátí se na ni. Ovšem interně se na zásobník neukládá adresa instrukce za JSR (což by bylo logické), ale adresa o jedničku menší! To souvisí s interní činností mikroprocesoru a umožnilo to vývojářům zkrátit instrukci JSR o jeden takt.

MOS 6502 tedy pracuje tak, že JSR na zásobník uloží adresu JSR+2 (nebo další instrukce-1, což je to samé) a RTS neprovede skok na přečtenou adresu, ale na adresu+1. A právě tohoto detailu využijeme – na zásobník uložíme adresu z vektoru KBHANDLER (a tím simulujeme instrukci JSR, resp. její část) a následně provedeme skok instrukcí RTS, která automaticky provede přičtení jedničky k této adrese:

.proc get_key
        lda KBHANDLER+1         ; cteni horni casti adresy ulozene v ROM
        pha                     ; ulozeni na zasobnik
        lda KBHANDLER           ; cteni dolni casti adresy ulozene v ROM
        pha                     ; ulozeni na zasobnik
        rts                     ; vyber adresy ze zasobniku + skok
                                ; zde neni nutne mit RTS
.endproc

Elegantní, že?

Poznámka: tato metoda se nazývá Paulův trik a stále existuje nejasnost v tom, o kterého Paula se vlastně jedná (možná o jednoho z tvůrců Atari OS).

16. Úplný zdrojový kód výsledného demonstračního příkladu

Na závěr si ukážeme úplný zdrojový kód demonstračního příkladu, který po svém překladu a spuštění vytiskne několik hexadecimálních hodnot. Mezi každým tiskem se čeká na stisk klávesy:

.include "atari.inc"
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro lsr_ shift_count
        .repeat shift_count
        lsr
        .endrep
.endmacro
 
 
 
.CODE
 
KBHANDLER = $e424               ; rutina pro cteni klavesy
 
.proc main
        lda #$42                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
        jsr get_key
        lda #$ff                ; cislo, ktere se bude tisknout
        jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
loop:   jmp loop
.endproc
 
.proc get_key
        lda KBHANDLER+1         ; cteni horni casti adresy ulozene v ROM
        pha                     ; ulozeni na zasobnik
        lda KBHANDLER           ; cteni dolni casti adresy ulozene v ROM
        pha                     ; ulozeni na zasobnik
        rts                     ; vyber adresy ze zasobniku + skok
                                ; zde neni nutne mit RTS
.endproc
 
.proc print_2_hex_digits
        ; TODO: ziskat jen vyssi cislici
        pha                     ; ulozit akumulator na zasobnik
        lsr_ 4                  ; posun 4 nejvyssich bitu do bitu nejnizsich
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #0                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
 
        pla
        and #$0f                ; ziskat jen nizsi nibble
        jsr nibble_to_hex_char  ; prevod na interni kod cislice
        ldy #1                  ; pozice na obrazovce
        jsr print_char_at_y     ; tisk cislice/znaku
.endproc
 
 
.proc nibble_to_hex_char
        cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
        bcc skip_add            ; je to hodnota 0-9
        adc #6                  ; pricist sedmicku (6+carry)
skip_add:
        adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
        rts                     ; navrat z podprogramu
.endproc
 
 
.proc print_char_at_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

Jedná se o prozatím nejdelší příklad, takže si pro zajímavost ještě uvedeme relevantní části z jeho listingu produkovaného assemblerem:

000000r 1               ; ---------------------------------------------------------------------
000000r 1               ; Definice maker
000000r 1               ; ---------------------------------------------------------------------
000000r 1
000000r 1               .macro lsr_ shift_count
000000r 1                       .repeat shift_count
000000r 1                       lsr
000000r 1                       .endrep
000000r 1               .endmacro
000000r 1
000000r 1
000000r 1
000000r 1               .CODE
000000r 1
000000r 1               KBHANDLER = $e424               ; rutina pro cteni klavesy
000000r 1
000000r 1               .proc main
000000r 1  A9 42                lda #$42                ; cislo, ktere se bude tisknout
000002r 1  20 rr rr             jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
000005r 1  20 rr rr             jsr get_key
000008r 1  A9 FF                lda #$ff                ; cislo, ktere se bude tisknout
00000Ar 1  20 rr rr             jsr print_2_hex_digits  ; tisk dvouciferne hexadecimalni hodnoty
00000Dr 1  4C rr rr     loop:   jmp loop
000010r 1               .endproc
000010r 1
000010r 1               .proc get_key
000010r 1  AD 25 E4             lda KBHANDLER+1         ; cteni horni casti adresy ulozene v ROM
000013r 1  48                   pha                     ; ulozeni na zasobnik
000014r 1  AD 24 E4             lda KBHANDLER           ; cteni dolni casti adresy ulozene v ROM
000017r 1  48                   pha                     ; ulozeni na zasobnik
000018r 1  60                   rts                     ; vyber adresy ze zasobniku + skok
000019r 1                                               ; zde neni nutne mit RTS
000019r 1               .endproc
000019r 1
000019r 1               .proc print_2_hex_digits
000019r 1                       ; TODO: ziskat jen vyssi cislici
000019r 1  48                   pha                     ; ulozit akumulator na zasobnik
00001Ar 1  4A 4A 4A 4A          lsr_ 4                  ; posun 4 nejvyssich bitu do bitu nejnizsich
00001Er 1  20 rr rr             jsr nibble_to_hex_char  ; prevod na interni kod cislice
000021r 1  A0 00                ldy #0                  ; pozice na obrazovce
000023r 1  20 rr rr             jsr print_char_at_y     ; tisk cislice/znaku
000026r 1
000026r 1  68                   pla
000027r 1  29 0F                and #$0f                ; ziskat jen nizsi nibble
000029r 1  20 rr rr             jsr nibble_to_hex_char  ; prevod na interni kod cislice
00002Cr 1  A0 01                ldy #1                  ; pozice na obrazovce
00002Er 1  20 rr rr             jsr print_char_at_y     ; tisk cislice/znaku
000031r 1               .endproc
000031r 1
000031r 1
000031r 1               .proc nibble_to_hex_char
000031r 1  C9 0A                cmp #$0a                ; test na hodnotu 0-9 nebo 10-15
000033r 1  90 02                bcc skip_add            ; je to hodnota 0-9
000035r 1  69 06                adc #6                  ; pricist sedmicku (6+carry)
000037r 1               skip_add:
000037r 1  69 10                adc #16                 ; prevod hodnoty na interni kod (ne ATASCII!)
000039r 1  60                   rts                     ; navrat z podprogramu
00003Ar 1               .endproc
00003Ar 1
00003Ar 1
00003Ar 1               .proc print_char_at_y
00003Ar 1  91 58                sta (88), y             ; tisk znaku na první místo na obrazovce
00003Cr 1                                               ; (adresa Video RAM je na adresách 88 a 89)
00003Cr 1  60                   rts
00003Dr 1               .endproc
00003Dr 1
00003Dr 1               end:                            ; potrebujeme znat adresu konce kodoveho segmentu
00003Dr 1
00003Dr 1
00003Dr 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   end - 1                 ; konec kodoveho segmentu
000006r 1
000006r 1
000006r 1               .segment "AUTOSTRT"             ; segment s pocatecni adresou
000000r 1  E0 02        .word   RUNAD                   ; naplni se pouze adresy RUNAD a RUNAD+1
000002r 1  E1 02        .word   RUNAD+1
000004r 1  rr rr        .word   main                    ; adresa vstupniho bodu do programu
000006r 1
000006r 1               ; finito
000006r 1

17. Příloha A: Seznam všech doposud popsaných instrukcí mikroprocesoru MOS 6502

Všechny doposud popsané instrukce mikroprocesoru MOS 6502 jsou vypsány v následující tabulce:

# 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)
       
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
       
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
       
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
       
32 PHA push accumulator on stack uložení obsahu akumulátoru na zásobník
33 PLA pull accumulator from stack obnovení obsahu akumulátoru ze zásobníku

18. Příloha B: Makefile pro překlad všech demonstračních příkladů

Všechny předminule, 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:

Školení Hacking

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.xex \
         hex_number_1.xex        hex_number_2.xex \
         hex_number_3.xex        hex_number_4.xex \
         hex_number_5.xex        hex_number_6.xex \
         hex_number_7.xex        hex_number_8.xex \
         hex_number_9.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 předchozích článcích i v článku dnešním 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:

# Příklad Stručný popis příkladu Adresa
1 Makefile definice cílů pro překlad všech demonstračních příkladů z této tabulky https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/Makefile
2 linker.cfg konfigurační soubor pro linker https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/linker.cfg
       
3 dummy.asm pouze nekonečná smyčka https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/dummy.asm
4 dummy_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/dummy_list.asm
5 dummy_list.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/dummy.map
       
6 background_color1.asm změna barvy pozadí – základní varianta příkladu https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color1.asm
7 background_color1_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color1_list.asm
8 background_color1.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color1.map
       
9 background_color2.asm změna barvy pozadí – využití předdefinovaných konstant https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color2.asm
10 background_color2_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color2_list.asm
11 background_color2.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/background_color2.map
       
12 print_a.asm tisk znaku přímo do obrazové paměti https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/print_a.asm
13 print_a_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/print_a_list.asm
14 print_a.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/print_a.map
       
15 color_computation1.asm výpočet barvy, varianta bez bitových posunů https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation1.asm
16 color_computation1_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation1_list.asm
17 color_computation1.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation1.map
       
18 color_computation2.asm výpočet barvy, varianta s bitovými posuny https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation2.asm
19 color_computation2_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation2_list.asm
20 color_computation2.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/color_computation2.map
       
21 subroutine1.asm skok do podprogramu bez předávání parametrů https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine1.asm
22 subroutine1_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine1_list.asm
23 subroutine1.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine1.map
       
24 subroutine2.asm skok do podprogramu s předáním parametru https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine2.asm
25 subroutine2_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine2_list.asm
26 subroutine2.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/subroutine2.map
       
27 hex_number1.asm tisk jedné hexadecimální číslice (nekorektní varianta) https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number1.asm
28 hex_number1_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number1_list.asm
29 hex_number1.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number1.map
       
30 hex_number2.asm tisk jedné hexadecimální číslice (korektní varianta) https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number2.asm
31 hex_number2_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number2_list.asm
32 hex_number2.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number2.map
       
33 hex_number3.asm tisk jedné hexadecimální číslice (korektní varianta po refaktoringu) https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number3.asm
34 hex_number3_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number3_list.asm
35 hex_number3.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number3.map
       
36 hex_number4.asm tisk jedné hexadecimální číslice, varianta postavená na makrech https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number4.asm
37 hex_number4_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number4_list.asm
38 hex_number4.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number4.map
       
39 hex_number5.asm tisk jedné hexadecimální číslice, varianta postavená na makrech, lokální automaticky generovaná návěští https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number5.asm
40 hex_number5_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number5_list.asm
41 hex_number5.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number5.map
       
42 hex_number6.asm příprava pro tisk dvouciferné hexadecimální hodnoty https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number6.asm
43 hex_number6_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number6_list.asm
44 hex_number6.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number6.map
       
45 hex_number7.asm realizace tisku dvouciferné hexadecimální hodnoty https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number7.asm
46 hex_number7_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number7_list.asm
47 hex_number7.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number7.map
       
48 hex_number8.asm makro pro logický posun doprava o zadaný počet bitů https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8.asm
49 hex_number8_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8_list.asm
50 hex_number8.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8.map
       
51 hex_number9.asm čekání na stisk klávesy https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8.asm
52 hex_number9_list.asm „listing“ vygenerovaný assemblerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8_list.asm
53 hex_number9.map mapa paměti; soubor vytvořený linkerem https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/hex_number8.map
       
54 chars.bas tisk všech znaků na obrazovku (varianta naprogramovaná v BASICu) https://github.com/tisnik/8bit-fame/blob/master/Atari800-ca65/chars.bas

20. Odkazy na Internetu

  1. MOS 6502 instruction set
    http://www.6502.org/users/o­belisk/6502/instructions.html
  2. EXE File Format Description
    https://gury.atari8.info/ref­s/file_formats_exe.php
  3. XEX Filter – A toolkit to analyze and manipulate Atari binary files
    https://www.vitoco.cl/atari/xex-filter/index.html
  4. chkxex.py
    https://raw.githubusercon­tent.com/seban-slt/tcx_tools/refs/heads/mas­ter/chkxex.py
  5. ca65 Users Guide
    https://cc65.github.io/doc/ca65.html
  6. cc65 Users Guide
    https://cc65.github.io/doc/cc65.html
  7. ld65 Users Guide
    https://cc65.github.io/doc/ld65.html
  8. da65 Users Guide
    https://cc65.github.io/doc/da65.html
  9. Překladače jazyka C pro historické osmibitové mikroprocesory
    https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/
  10. 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/
  11. Getting Started Programming in C: Coding a Retro Game with C Part 2
    https://retrogamecoders.com/getting-started-with-c-cc65/
  12. NES game development in 6502 assembly – Part 1
    https://kibrit.tech/en/blog/nes-game-development-part-1
  13. 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/
  14. Minimal NES example using ca65
    https://github.com/bbbradsmith/NES-ca65-example
  15. List of 6502-based Computers and Consoles
    https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/
  16. 6502 – the first RISC µP
    http://ericclever.com/6500/
  17. 3 Generations of Game Machine Architecture
    http://www.atariarchives.or­g/dev/CGEXPO99.html
  18. “Hello, world” from scratch on a 6502 — Part 1
    https://www.youtube.com/wat­ch?v=LnzuMJLZRdU
  19. A Tour of 6502 Cross-Assemblers
    https://bumbershootsoft.wor­dpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/
  20. Adventures with ca65
    https://atariage.com/forum­s/topic/312451-adventures-with-ca65/
  21. example ca65 startup code
    https://atariage.com/forum­s/topic/209776-example-ca65-startup-code/
  22. 6502 PRIMER: Building your own 6502 computer
    http://wilsonminesco.com/6502primer/
  23. 6502 Instruction Set
    https://www.masswerk.at/6502/6502_in­struction_set.html
  24. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  25. Single-board computer
    https://en.wikipedia.org/wiki/Single-board_computer
  26. www.6502.org
    http://www.6502.org/
  27. 6502 PRIMER: Building your own 6502 computer – clock generator
    http://wilsonminesco.com/6502pri­mer/ClkGen.html
  28. Great Microprocessors of the Past and Present (V 13.4.0)
    http://www.cpushack.com/CPU/cpu.html
  29. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  30. Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
    https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/
  31. 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/
  32. 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/
  33. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  34. Comparison of instruction set architectures
    https://en.wikipedia.org/wi­ki/Comparison_of_instructi­on_set_architectures
  35. How To Start Learning Atari 8 Bit Assembly For Free
    https://forums.atariage.com/to­pic/300732-how-to-start-learning-atari-8-bit-assembly-for-free/
  36. WUDSN (Demo Group)
    https://www.wudsn.com/
  37. Machine Language For Beginners
    https://www.atariarchives.org/mlb/
  38. Assembly language: all about I/O
    https://www.atarimagazines­.com/v3n8/AllAbout_IO.html
  39. Sedmdesátiny assemblerů: lidsky čitelný strojový kód
    https://www.root.cz/clanky/sed­mdesatiny-assembleru-lidsky-citelny-strojovy-kod/
  40. Color names
    https://atariwiki.org/wiki/Wi­ki.jsp?page=Color%20names
  41. ATASCII
    https://en.wikipedia.org/wiki/ATASCII
  42. Put characters in display ram isn't ATASCII?
    https://forums.atariage.com/to­pic/359973-put-characters-in-display-ram-isnt-atascii/
  43. ATASCII And Internal Character Code Values
    https://www.atariarchives­.org/mapping/appendix10.php
  44. Reading ATASCII from the keyboard in assembly
    https://forums.atariage.com/to­pic/361733-reading-atascii-from-the-keyboard-in-assembly/
  45. Why does the 6502 JSR instruction only increment the return address by 2 bytes?
    https://retrocomputing.stac­kexchange.com/questions/19543/why-does-the-6502-jsr-instruction-only-increment-the-return-address-by-2-bytes
  46. Pushing return address to stack off by 1 byte
    https://forums.atariage.com/to­pic/378206-pushing-return-address-to-stack-off-by-1-byte/
  47. Intel x86 documentation has more pages than the 6502 has transistors
    https://www.righto.com/2013/09/intel-x86-documentation-has-more-pages.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.



Nejnovější články