Obsah
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
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.
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
; 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 1: Hexadecimální číslice vypsaná příkladem.
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í JSR i RTS:
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
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
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
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 2: Hexadecimální číslo 0x42.
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 3: Výpis prvního čísla a čekání na stisk klávesy.
Obrázek 4: Po stisku klávesy se vypíše druhé hexadecimální číslo.
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?
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:
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:
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 - Reading ATASCII from the keyboard in assembly
https://forums.atariage.com/topic/361733-reading-atascii-from-the-keyboard-in-assembly/ - Why does the 6502 JSR instruction only increment the return address by 2 bytes?
https://retrocomputing.stackexchange.com/questions/19543/why-does-the-6502-jsr-instruction-only-increment-the-return-address-by-2-bytes - Pushing return address to stack off by 1 byte
https://forums.atariage.com/topic/378206-pushing-return-address-to-stack-off-by-1-byte/ - Intel x86 documentation has more pages than the 6502 has transistors
https://www.righto.com/2013/09/intel-x86-documentation-has-more-pages.html
