Obsah
1. Instrukce pro bitové posuny a rotace
2. Náhrada čtveřice operací rra za trojici operací rrca
3. Vylepšený výpočet adresy masky znaku uloženého v paměti ROM
4. Rutina pro vykreslení znaku, která vrátí adresu pro zápis dalšího znaku
5. Vykreslení znaku na libovolné místo na obrazovce
6. Rozbalení programových smyček
7. Realizace rozbalení programové smyčky
8. První pokus o vytištění celého řetězce
9. Úplný zdrojový kód celého demonstračního příkladu
10. Kde vlastně došlo k chybě?
11. Uložení hodnot na zásobník; obnova hodnot ze zásobníku
12. Oprava podprogramu pro tisk řetězce
13. Jedná se o korektní opravu?
14. Úplný zdrojový kód opraveného demonstračního příkladu
15. Realizace operace typu PLOT – vykreslení pixelu
16. Bitové operace prováděné na mikroprocesoru Zilog Z80
17. Operace pro otestování, nastavení a vynulování konkrétního bitu
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
19. Repositář s demonstračními příklady
1. Instrukce pro bitové posuny a rotace
Instrukce mikroprocesoru Zilog Z80, které jsme si popsali v předchozích čtyřech článcích [1] [2], [3], [4], nám již dnes nebudou postačovat, neboť začneme při tisku do obrazové paměti provádět posuny a rotace dat. Na rozdíl od minimalisticky pojatého čipu MOS 6502 nabízí Z80 programátorům celou řadu instrukcí pro rotaci (přes osm bitů nebo přes devět bitů s carry) i pro aritmetický a bitový posun doleva a doprava. V tabulce pro úplnost uvádím i instrukci SLL, kterou lze použít, i když nebyla oficiálně zdokumentovaná:
Instrukce | Popis prováděné operace |
---|---|
RLCA | rotace akumulátoru doleva, sedmý bit do carry |
RLA | rotace akumulátoru i s carry doleva |
RRCA | rotace akumulátoru doleva, nultý bit do carry |
RRA | rotace akumulátoru i s carry doprava |
RLC | rotace registru doleva, sedmý bit do carry |
RL | rotace registru i s carry doleva |
RRC | rotace registru doleva, nultý bit do carry |
RR | rotace registru i s carry doprava |
SLA | aritmetický posun doleva (násobení dvěma), sedmý bit do carry |
SLL* | dtto, ovšem do nultého bitu se nasune jednička (nedokumentovaná instrukce) |
SRA | aritmetický posun doprava (dělení dvěma), nultý bit do carry |
SRL | logický posun doprava (do sedmého bitu se nasune nula) |
Obrázek 1: Obrazovka vyplněná barevným textem vykresleným příkladem (ROM rutina si navíc vynutí pokračování vykreslování po stisku klávesy).
2. Náhrada čtveřice operací rra za trojici operací rrca
Připomeňme si nejdříve, jak vlastně vypadal program, který dokázal na určené místo na obrazovce v rastru 32×24 znaků vykreslit blok o velikosti 8×8 pixelů. Tento program nejdříve vypočítal adresu prvního zapisovaného bajtu s tím, že adresa bajtu na dalším obrazovém řádku je díky „podivné“ struktuře obrazové paměti ZX Spectra vlastně umístěna na adrese o 256 bajtů vyšší. A přechod o 256 bajtů výše se provádí až triviálně snadno – pouhou inkrementací hodnoty vyššího bajtu adresy (což je rychlá osmibitová operace).
Zdrojový kód tohoto programu vypadá následovně:
SCREEN_ADR equ $4000 ENTRY_POINT equ $8000 PATTERN equ $ff org ENTRY_POINT start: ld b, 0 ; x-ová souřadnice ld c, 0 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 2 ; x-ová souřadnice ld c, 2 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 31 ; x-ová souřadnice ld c, 23 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok finish: jr finish ; žádný návrat do systému calc_block_address: ; parametry: ; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis bloku ; ; vzor adresy: ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rra rra rra rra ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld l, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu fill_block: ; parametry: ; A - pattern ; HL - adresa vykreslení bloku ld b, 8 ; počitadlo zapsaných bajtů loop: ld (hl), PATTERN ; zápis hodnoty na adresu (HL) inc h ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy ret ; návrat z podprogramu end ENTRY_POINT
Obrázek 2: Bloky vykreslené popisovaným demonstračním příkladem.
Zajímat nás nyní bude především sekvence instrukcí, která vysune tři bity y-ové souřadnice (tedy vlastně číslo obrazového řádku v rámci znaku vysokého osm pixelů) do nejvyšších třech bitů. Tuto operaci jsme realizovali maskou a následným čtyřnásobným posunem doprava přes carry (popř. pětinásobným posunem doleva):
ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rra rra rra rra ; nyní jsou čísla řádků v horních třech bitech ; Y2 Y1 Y0 0 0 0 0 0
Ve skutečnosti je možné tento bitový posun (což je vlastně rotace) o jednu instrukci zkrátit, když nebudeme rotovat přes carry, ale pouze v rámci jednoho bajtu (carry se sice nastavuje, ale to nás nemusí trápit):
ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech ; Y2 Y1 Y0 0 0 0 0 0
Výsledný program bude vypadat takto:
SCREEN_ADR equ $4000 ENTRY_POINT equ $8000 PATTERN equ $ff org ENTRY_POINT start: ld b, 0 ; x-ová souřadnice ld c, 0 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 2 ; x-ová souřadnice ld c, 2 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok ld b, 31 ; x-ová souřadnice ld c, 23 ; y-ová souřadnice call calc_block_address ; výpočet adresy ld a, PATTERN call fill_block ; vykreslit blok finish: jr finish ; žádný návrat do systému calc_block_address: ; parametry: ; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis bloku ; ; vzor adresy: ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld l, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu fill_block: ; parametry: ; A - pattern ; HL - adresa vykreslení bloku ld b, 8 ; počitadlo zapsaných bajtů loop: ld (hl), PATTERN ; zápis hodnoty na adresu (HL) inc h ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy ret ; návrat z podprogramu end ENTRY_POINT
Výsledek by měl být přitom stejný, jako tomu bylo u originálního příkladu:
Obrázek 3: Bloky vykreslené upraveným demonstračním příkladem.
Pro úplnost se podívejme na způsob překladu do strojového kódu:
SCREEN_ADR EQU 4000 ENTRY_POINT EQU 8000 PATTERN EQU 00FF ORG 8000 8000: label start 8000:0600 LD B, 00 8002:0E00 LD C, 00 8004:CD3280 CALL 8032 8007:3EFF LD A, FF 8009:CD4180 CALL 8041 800C:060F LD B, 0F 800E:0E0C LD C, 0C 8010:CD3280 CALL 8032 8013:3EFF LD A, FF 8015:CD4180 CALL 8041 8018:0602 LD B, 02 801A:0E02 LD C, 02 801C:CD3280 CALL 8032 801F:3EFF LD A, FF 8021:CD4180 CALL 8041 8024:061F LD B, 1F 8026:0E17 LD C, 17 8028:CD3280 CALL 8032 802B:3EFF LD A, FF 802D:CD4180 CALL 8041 8030: label finish 8030:18FE JR 8030 8032: label calc_block_address 8032:79 LD A, C 8033:E607 AND 07 8035:0F RRCA 8036:0F RRCA 8037:0F RRCA 8038:B0 OR B 8039:6F LD L, A 803A:79 LD A, C 803B:E618 AND 18 803D:F640 OR 40 803F:67 LD H, A 8040:C9 RET 8041: label fill_block 8041:0608 LD B, 08 8043: label loop 8043:36FF LD (HL), FF 8045:24 INC H 8046:10FB DJNZ 8043 8048:C9 RET 8049: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8048
3. Vylepšený výpočet adresy masky znaku uloženého v paměti ROM
Dalším kódem, který je možné optimalizovat, je kód pro výpočet adresy masky znaku uloženého v paměti ROM. Původní tvar výpočtu pro kód znaku zapsaného do akumulátoru A vypadal následovně:
draw_char: ld hl, CHAR_ADR ; adresa, od níž začínají masky znaků ld b, 0 ld c, a ; kód znaku je nyní ve dvojici BC sla c rl b sla c rl b sla c rl b ; vynásobení BC osmi add hl, bc ; přičíst adresu k offsetu masky znaku
Povšimněte si, že jsme kód znaku nejdříve uložili do dvojice registrů BC a poté bitovými posuny a rotacemi vynásobili tuto hodnotu osmi. Výsledek byl přičten k bázové adrese masek znaků, která byla uložena do dvojice registrů HL.
I tento kód můžeme zkrátit a urychlit, a to tak, že prohodíme význam dvojic BC s HL. Bázová adresa bude nyní uložena ve dvojici BC, zatímco HL bude použit pro výpočet offsetu. Proč zrovna HL? Protože je možné bitové rotace a posuny nahradit šestnáctibitovým součtem (16bitové operace nejsou ortogonální, tj. nejsou dostupné pro všechny dvojice registrů):
draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků
Obrázek 4: Tisk znaků upraveným demonstračním příkladem.
Upravený zdrojový kód demonstračního příkladu vypadá následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld de, SCREEN_ADR ; adresa pro zápis ld a, 'A' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld de, SCREEN_ADR+1 ; adresa pro zápis ld a, 'B' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld de, SCREEN_ADR+128+31 ; adresa pro zápis ld a, '?' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku finish: ret ; ukončit program draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy ret ; návrat z podprogramu end ENTRY_POINT
Překlad do objektového kódu:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:110040 LD DE, 4000 8003:3E41 LD A, 41 8005:CD1980 CALL 8019 8008:110140 LD DE, 4001 800B:3E42 LD A, 42 800D:CD1980 CALL 8019 8010:119F40 LD DE, 409F 8013:3E3F LD A, 3F 8015:CD1980 CALL 8019 8018: label finish 8018:C9 RET 8019: label draw_char 8019:01003C LD BC, 3C00 801C:61 LD H, C 801D:6F LD L, A 801E:29 ADD HL, HL 801F:29 ADD HL, HL 8020:29 ADD HL, HL 8021:09 ADD HL, BC 8022:0608 LD B, 08 8024: label loop 8024:7E LD A, (HL) 8025:12 LD (DE), A 8026:2C INC L 8027:14 INC D 8028:10FA DJNZ 8024 802A:C9 RET 802B: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 802A
4. Rutina pro vykreslení znaku, která vrátí adresu pro zápis dalšího znaku
Podprogram draw_char určený pro tisk jednoho znaku očekává, že v registrovém páru DE je uložena adresa do obrazové paměti, kam se má uložit první mikrořádek znaku (tedy prvních osm pixelů). Většinou budeme chtít tisknout více znaků za sebou, takže by bylo vhodné, aby rutina automaticky zvýšila adresu DE tak, aby bylo možné ihned začít tisknout další znak. Původní tvar podprogramu ovšem tento výpočet nedělal, protože se sice zvyšovala hodnota v registru D, ale takovým způsobem, že by se další znak vytisknul pod znak předchozí (navíc ne vždy, protože obrazovka je rozdělena do třech oblastí):
draw_char: ... ... ... loop: ... ... ... inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy ret ; návrat z podprogramu
Můžeme ovšem provést relativně malé množství úprav, které změnu adresy DE na adresu dalšího znaku zajistí. Musíme si uvědomit, že další znak na stejném řádku začíná na adrese DE+1, takže by se smyčka dala upravit takto:
draw_char: ... ... ... ld c, d loop: ... ... ... inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ld d, c ret ; E=E+1
Musíme ovšem počítat i s tím, že na konci celého bloku je nutné se přesunout na blok další, tj. že hodnota D vypočtená uvnitř smyčky je vlastně korektní a pouze zvýšíme hodnotu v registru E (navíc nemusíme řešit přetečení do D, protože víme, že E je na konci bloku nulové). Úprava bude nyní následující:
draw_char: ... ... ... ld c, d loop: ... ... ... inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
Obrázek 5: Tisk tří znaků za sebou upraveným demonstračním příkladem.
Upravená varianta demonstračního příkladu vypadá následovně. Povšimněte si, že znaky A, B a ? vytiskneme za sebou bez úpravy adresy:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld de, SCREEN_ADR ; adresa pro zápis ld a, 'A' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, 'B' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, '?' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku finish: ret ; ukončit program draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Způsob překladu z assembleru do strojového kódu:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:110040 LD DE, 4000 8003:3E41 LD A, 41 8005:CD1380 CALL 8013 8008:3E42 LD A, 42 800A:CD1380 CALL 8013 800D:3E3F LD A, 3F 800F:CD1380 CALL 8013 8012: label finish 8012:C9 RET 8013: label draw_char 8013:01003C LD BC, 3C00 8016:61 LD H, C 8017:6F LD L, A 8018:29 ADD HL, HL 8019:29 ADD HL, HL 801A:29 ADD HL, HL 801B:09 ADD HL, BC 801C:0608 LD B, 08 801E:4A LD C, D 801F: label loop 801F:7E LD A, (HL) 8020:12 LD (DE), A 8021:2C INC L 8022:14 INC D 8023:10FA DJNZ 801F 8025:1C INC E 8026:C8 RET Z 8027:51 LD D, C 8028:C9 RET 8029: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8028
5. Vykreslení znaku na libovolné místo na obrazovce
V této chvíli již máme k dispozici programový kód pro umístění a vykreslení bloku kamkoli na obrazovku a současně umíme vykreslit znak, jehož maska je přečtena z ROM. Tyto dvě znalosti je nutné zkombinovat a vytvořit rutinu pro tisk znaků popř. (později) řetězců kamkoli na obrazovku. Postup je vlastně triviální, protože potřebujeme znát a využít pouze trojici hodnot: kód znaku, x-ovou souřadnici 0..31 a y-ovou souřadnici 0..23. Ze souřadnic vypočteme adresu znaku na obrazovce a poté znak vykreslíme:
ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld a, 'A' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku
Obrázek 6: Trojice znaků vykreslená na různá místa na obrazovce.
Upravený zdrojový kód demonstračního příkladu bude nyní vypadat následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld a, 'A' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, 'B' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku ld b, 31 ; x-ová souřadnice ld c, 23 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld a, '?' ; kód vykreslovaného znaku call draw_char ; zavolat subrutinu pro vykreslení znaku finish: jr finish ; žádný návrat do systému calc_char_address: ; parametry: ; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech) ; ; návratové hodnoty: ; DE - adresa pro zápis bloku ; ; vzor adresy: ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld e, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld d, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Po překladu získáme strojový kód o velikosti celých 67 bajtů:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:060F LD B, 0F 8002:0E0C LD C, 0C 8004:CD1F80 CALL 801F 8007:3E41 LD A, 41 8009:CD2E80 CALL 802E 800C:3E42 LD A, 42 800E:CD2E80 CALL 802E 8011:061F LD B, 1F 8013:0E17 LD C, 17 8015:CD1F80 CALL 801F 8018:3E3F LD A, 3F 801A:CD2E80 CALL 802E 801D: label finish 801D:18FE JR 801D 801F: label calc_char_address 801F:79 LD A, C 8020:E607 AND 07 8022:0F RRCA 8023:0F RRCA 8024:0F RRCA 8025:B0 OR B 8026:5F LD E, A 8027:79 LD A, C 8028:E618 AND 18 802A:F640 OR 40 802C:57 LD D, A 802D:C9 RET 802E: label draw_char 802E:01003C LD BC, 3C00 8031:61 LD H, C 8032:6F LD L, A 8033:29 ADD HL, HL 8034:29 ADD HL, HL 8035:29 ADD HL, HL 8036:09 ADD HL, BC 8037:0608 LD B, 08 8039:4A LD C, D 803A: label loop 803A:7E LD A, (HL) 803B:12 LD (DE), A 803C:2C INC L 803D:14 INC D 803E:10FA DJNZ 803A 8040:1C INC E 8041:C8 RET Z 8042:51 LD D, C 8043:C9 RET 8044: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8043
6. Rozbalení programových smyček
Ještě jednou se podívejme na tu část vykreslovací rutiny, která postupně načte osm masek znaku a vykreslí je, tj. přesune je do obrazové paměti:
ld b, 8 ; počitadlo zapsaných bajtů loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy
Můžeme zde vidět, že se jedná o programovou smyčku, která se bude opakovat osmkrát. Samotná obsluha smyčky (tj. instrukce, které jsou nutné pro její provedení) je realizována jednou osmibitovou instrukcí ld a osmkrát volanou instrukcí djnz. Celkem tedy obsluha smyčky vyžaduje relativně velký počet strojových cyklů:
Instrukce | Opakování | Počet cyklů na instrukci | Celkem cyklů |
---|---|---|---|
ld b, 8 | 1× | 7 | 7 |
djnz loop pro b>1 | 7× | 13 | 91 |
djnz loop pro b==1 | 1× | 8 | 8 |
Celkem tedy i prázdná programová smyčka opakovaná osmkrát vyžaduje 7+91+8=106 strojových cyklů!
7. Realizace rozbalení programové smyčky
Za cenu poněkud větší spotřeby operační paměti můžeme smyčku ručně (nebo s využitím makra) rozbalit, a to tak, že jednoduše sekvenci instrukcí osmkrát zopakujeme. Dosáhneme tak vyšší rychlosti vykreslování (konkrétně ušetříme 106 strojových cyklů), ovšem za cenu větší spotřeby RAM, což je pro osmibitové stroje zcela typické dilema, které je nutné řešit prakticky neustále:
ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc d ; posun na definici dalšího obrazového řádku
Všechny výše uvedené instrukce jsou jednobajtové, takže rozbalení smyčky zabere 4×7+3=31 bajtů. To může být relativně dobrá cena, kterou zaplatíme za urychlení vykreslování.
Obrázek 7: Trojice znaků vykreslená na různá místa na obrazovce rozbalenou smyčkou.
8. První pokus o vytištění celého řetězce
Ve druhé části dnešního článku si ukážeme, jak můžeme upravený podprogram pro vytištění znaku použít pro vytištění celého řetězce. Využijeme přitom toho, že podprogram správně spočítá adresu dalšího znaku, takže teoreticky by mohlo být vytištění řetězce (speciálně ASCII řetězce ukončeného nulou) realizováno velmi jednoduchým způsobem:
print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce call draw_char ; tisk jednoho znaku inc hl ; přechod na další znak jr print_string ; na další znak
Tento podprogram vyžaduje, aby při jeho volání byla v dvojici registrů HL uložena adresa řetězce, který budeme chtít vytisknout. Dále podprogram vyžaduje uložení adresy prvního znaku v obrazové paměti v registrovém páru DE (tuto hodnotu nám připraví podprogram calc_char_address.
9. Úplný zdrojový kód celého demonstračního příkladu
Podívejme se nyní na úplný zdrojový kód takto upraveného a rozšířeného demonstračního příkladu. Jedná se sice o poněkud delší kód, který by však stále měl být relativně snadno pochopitelný:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld hl, TEXT ; adresa prvního znaku v řetězci call print_string ; tisk celého řetězce finish: jr finish ; žádný návrat do systému calc_char_address: ; parametry: ; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech) ; ; návratové hodnoty: ; DE - adresa pro zápis bloku ; ; vzor adresy: ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld e, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld d, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce call draw_char ; tisk jednoho znaku inc hl ; přechod na další znak jr print_string ; na další znak draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 ; nulou ukončený řetězec TEXT: db "Hello, Speccy!", 0 end ENTRY_POINT
Po jeho překladu a spuštění se však namísto očekávané zprávy „Hello, Speccy!“ objeví chybný text:
Obrázek 8: Nekorektní text zobrazený tímto demonstračním příkladem.
10. Kde vlastně došlo k chybě?
Program uvedený v předchozí kapitole sice vykreslil špatný řetězec, ovšem aspoň skončil :-), vytiskl neplatný řetězec na správné místo na obrazovce a navíc je první znak korektní („H“). To může znamenat, že chyba se nachází v té části programu, který počítá adresy znaků v řetězci. Adresa znaku se nachází ve dvojici registrů HL, jejíž hodnota se vy smyčce postupně zvyšuje (a to korektně):
print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce call draw_char ; tisk jednoho znaku inc hl ; přechod na další znak jr print_string ; na další znak
Problém tedy musíme hledat přímo v podprogramu draw_char. A skutečně – zde se s HL skutečně manipuluje:
draw_char: ... ... ... add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ... ... ...
Chybu jsme tedy našli – volaný podprogram nám přepisuje obsah registrů. Nyní se ji pokusíme nějakým způsobem opravit.
11. Uložení hodnot na zásobník; obnova hodnot ze zásobníku
Ještě předtím, než si ukážeme, jakým způsobem je možné program z předchozí kapitoly opravit, si popíšeme strojové instrukce sloužící pro uložení hodnot na zásobník a instrukce pro následnou obnovu hodnot ze zásobníku. Zásobníkem je v kontextu mikroprocesoru Zilog Z80 myšlena oblast operační paměti, do níž se data (většinou) ukládají pod adresu uloženou ve speciálním 16bitovém registru SP, přičemž při uložení hodnot se adresa v SP sníží a při obnově hodnot (načtení ze zásobníku) naopak zvýší. Zásobník tedy roste směrem dolů. Zajímavostí mikroprocesorů Z80 (a 8080) je to, že se na zásobník vždy ukládají dvě osmibitové hodnoty, což je buď návratová adresa nebo dvojice osmibitových registrů. K dispozici jsou následující instrukce, které nějakým způsobem manipulují s obsahem zásobníku:
Instrukce | Délka | Stručný popis |
---|---|---|
push BC | 1 | uložení registrového páru BC na zásobník |
push DE | 1 | uložení registrového páru DE na zásobník |
push HL | 1 | uložení registrového páru HL na zásobník |
push AF | 1 | uložení akumulátoru a příznakového registru na zásobník |
push IX | 2 | uložení index registru IX na zásobník |
push IY | 2 | uložení index registru IY na zásobník |
pop BC | 1 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru BC |
pop DE | 1 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru DE |
pop HL | 1 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru HL |
pop AF | 1 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do akumulátoru a příznakového registru |
pop IX | 2 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do index registru IX |
pop IY | 2 | vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do index registru IY |
call adresa | 3 | volání podprogramu (uložení návratové adresy na zásobník) |
call příznak, adresa | 3 | volání podprogramu při splnění podmínky (uložení návratové adresy na zásobník) |
rst x | 1 | volání podprogramu, zkrácená verze pro vybrané adresy (uložení návratové adresy na zásobník) |
ret | 1 | návrat z podprogramu (přečtení návratové adresy ze zásobníku) |
ret příznak | 1 | návrat z podprogramu při splnění podmínky (přečtení návratové adresy ze zásobníku) |
retn | 2 | návrat z nemaskovatelného přerušení (přečtení návratové adresy ze zásobníku) |
reti | 2 | návrat z maskovatelného přerušení (přečtení návratové adresy ze zásobníku) |
12. Oprava podprogramu pro tisk řetězce
Podprogram pro tisk řetězce můžeme opravit velmi snadno. Víme totiž, že se v podprogramu draw_char, který se postupně volá pro každý tisknutý znak, mj. „ničí“ obsah registrového páru HL, který ovšem potřebujeme, protože obsahuje adresu právě zpracovávaného znaku. Celá oprava tedy bude spočívat v tom, že obsah tohoto registrového páru před voláním draw_char uložíme na zásobník a ihned po návratu z podprogramu obsah registrového páru obnovíme, takže bude použitelný pro načtení dalšího znaku v řetězci, který se má vytisknout. Oprava může vypadat následovně:
print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce push hl ; uschovat HL na zásobník call draw_char ; tisk jednoho znaku pop hl ; obnovit obsah HL ze zásobníku inc hl ; přechod na další znak jr print_string ; na další znak
S aplikací této opravy by měl výpis řetězce dopadnout následovně:
Obrázek 9: Výpis řetězce na obrazovku – nyní je vše korektní.
13. Jedná se o korektní opravu?
Na tomto místě je vhodné si položit otázku, jestli je oprava korektní, a to jak z hlediska činnosti programu, tak i z hlediska jeho čitelnosti resp. „očekávatelnosti“ chování. Program jsme evidentně opravili, protože tiskne správný řetězec. Ovšem při volání podprogramu draw_char si neustále musíme dávat pozor na uložení a opětovné obnovení obsahu HL. Z tohoto pohledu by mohlo být lepší, aby se instrukce push hl a pop hl vložily přímo do podprogramu draw_char (popř. by se mohl podprogram upravit do takové míry, aby nemodifikoval žádné registry, v nichž nebudou předány návratové hodnoty).
14. Úplný zdrojový kód opraveného demonstračního příkladu
Pro úplnost se nyní podívejme na to, jak vlastně vypadá opravená varianta demonstračního příkladu pro výpis řetězce na libovolné místo na obrazovce (opět počítáno v „textovém“ rastru 32×24 znaků):
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld hl, TEXT ; adresa prvního znaku v řetězci call print_string ; tisk celého řetězce finish: jr finish ; žádný návrat do systému calc_char_address: ; parametry: ; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech) ; ; návratové hodnoty: ; DE - adresa pro zápis bloku ; ; vzor adresy: ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld e, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld d, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce push hl ; uschovat HL na zásobník call draw_char ; tisk jednoho znaku pop hl ; obnovit obsah HL ze zásobníku inc hl ; přechod na další znak jr print_string ; na další znak draw_char: ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 ; nulou ukončený řetězec TEXT: db "Hello, Speccy!", 0 end ENTRY_POINT
15. Realizace operace typu PLOT – vykreslení pixelu
Nyní již relativně dobře umíme na obrazovku ZX Spectra vypsat řetězec. Dokonce máme k dispozici několik metod. Můžeme využít subrutinu zapsanou přímo v ROM ZX Spectra (ta umí kromě dalších věcí rozeznávat řídicí kódy) nebo můžeme zavolat naši subrutinu, která je sice původně navržena pro tisk znaků v masce 8×8 znaků, ale relativně snadno ji lze upravit na různé výšky znaků. Ovšem zajímavější (i když možná méně praktické) bude zjistit, jakým způsobem je možné realizovat operaci typu PLOT. Jedná se o standardní příklad Sinclair BASICu sloužící pro vykreslení jediného pixelu. Aby byla situace nepatrně zajímavější, existuje tento příkaz v několika variantách:
Příkaz | Stručný popis příkazu |
---|---|
PLOT x,y | vykreslení pixelu na souřadnice [x,y] barvou inkoustu |
PLOT INVERSE 1; x, y | vykreslení pixelu barvou papíru |
PLOT OVER 1; x, y | negace harvy pixelu |
PLOT INVERSE 1;OVER 1; | pouze přesune grafický „kurzor“ na nové souřadnice |
Obrázek 10: Slavná hra Elite používá velké množství grafických entit: body, úsečky, kružnice, vyplněné bloky a znaky.
16. Bitové operace prováděné na mikroprocesoru Zilog Z80
Vykreslování na úrovni jednotlivých pixelů bude vzhledem ke struktuře obrazové paměti ZX Spectra vyžadovat provádění bitových operací. Jaké operace ovšem vůbec máme k dispozici? To nám napoví následující tabulka, v níž jsou příslušné instrukce vypsány:
Instrukce | Stručný popis instrukce |
---|---|
AND r | bitový součin akumulátoru s osmibitovým registrem |
AND n | bitový součin akumulátoru s osmibitovou konstantou |
AND (HL) | bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese HL |
AND (IX+d) | bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d |
AND (IY+d) | bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d |
OR r | bitový součet akumulátoru s osmibitovým registrem |
OR n | bitový součet akumulátoru s osmibitovou konstantou |
OR (HL) | bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese HL |
OR (IX+d) | bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d |
OR (IY+d) | bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d |
XOR r | bitová nonekvivalence akumulátoru s osmibitovým registrem |
XOR n | bitová nonekvivalence akumulátoru s osmibitovou konstantou |
XOR (HL) | bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese HL |
XOR (IX+d) | bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d |
XOR (IY+d) | bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d |
CPL | bitová negace všech bitů v akumulátoru |
17. Operace pro otestování, nastavení a vynulování konkrétního bitu
Instrukční soubor osmibitového mikroprocesoru Zilog Z80 obsahuje přibližně 1200 operačních kódů instrukcí, přičemž velkou část z tohoto množství zaberou instrukce BIT, SET a RES. Tyto instrukce slouží pro test jednoho bitu (výsledek je ukládán do příznaku zero), nastavení konkrétního bitu na jedničku nebo vynulování zvoleného bitu. Důvodem, proč tyto instrukce zabírají velké množství operačních kódů je fakt, že v operačním kódu instrukce je zakódován i index testovaného, nastavovaného či nulovaného bitu, tj. každá instrukce vlastně existuje v osmi variantách, což je dále násobeno několika adresovacími režimy (může se použít libovolný registr, adresa v HL, navíc je někdy nutné uložit i offset d atd.):
Instrukce | Stručný popis instrukce |
---|---|
BIT b,r | test bitu b uloženého v některém osmibitovém registru |
BIT b,(HL) | test bitu b uloženého na adrese HL |
BIT b,(IX+d) | test bitu b uloženého na adrese IX+d |
BIT b,(IY+d) | test bitu b uloženého na adrese IY+d |
SET b,r | nastavení bitu b uloženého v některém osmibitovém registru |
SET b,(HL) | nastavení bitu b uloženého na adrese HL |
SET b,(IX+d) | nastavení bitu b uloženého na adrese IX+d |
SET b,(IY+d) | nastavení bitu b uloženého na adrese IY+d |
RES b,r | vynulování bitu b uloženého v některém osmibitovém registru |
RES b,(HL) | vynulování bitu b uloženého na adrese HL |
RES b,(IX+d) | vynulování bitu b uloženého na adrese IX+d |
RES b,(IY+d) | vynulování bitu b uloženého na adrese IY+d |
Tyto instrukce mají většinou prefix 0×CB, 0×DD 0×CB nebo 0×FD 0×CB. Jejich délka je dva či čtyři bajty.
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
Výše uvedené demonstrační příklady i příklady, které již byly popsány v předchozích čtyřech článcích [1] [2], [3], [4], je možné přeložit s využitím souboru Makefile, jehož aktuální verze vypadá následovně (pro překlad a slinkování je použit assembler Pasmo):
ASSEMBLER := pasmo all: 01.tap 02.tap 03.tap 04.tap 05.tap 06.tap 07.tap 08.tap 09.tap 10.tap \ 11.tap 12.tap 13.tap 14.tap 15.tap 16.tap 17.tap 18.tap 19.tap 20.tap \ 21.tap 22.tap 23.tap 24.tap 25.tap 26.tap 27.tap 28.tap 29.tap 30.tap \ 31.tap 32.tap 33.tap 34.tap 35.tap 36.tap 37.tap 38.tap 39.tap 40.tap \ 41.tap 42.tap 43.tap 44.tap 45.tap 46.tap 47.tap 48.tap 49.tap clean: rm -f *.tap .PHONY: all clean 01.tap: 01-color-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 01-color-attribute.lst 02.tap: 02-blinking-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 02-blinking-attribute.lst 03.tap: 03-symbolic-names.asm $(ASSEMBLER) -v -d --tap $< $@ > 03-symbolic-names.lst 04.tap: 04-operators.asm $(ASSEMBLER) -v -d --tap $< $@ > 04-operators.lst 05.tap: 05-better-symbols.asm $(ASSEMBLER) -v -d --tap $< $@ > 05-better-symbols.lst 06.tap: 06-tapbas-v1.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 06-tapbas-v1.lst 07.tap: 07-tapbas-v2.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 07-tapbas-v2.lst 08.tap: 08-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 08-loop.lst 09.tap: 09-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 09-loop.lst 10.tap: 10-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 10-loop.lst 11.tap: 11-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 11-loop.lst 12.tap: 12-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 12-loop.lst 13.tap: 13-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 13-loop.lst 14.tap: 14-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 14-loop.lst 15.tap: 15-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 15-loop.lst 16.tap: 16-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 16-loop.lst 17.tap: 17-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 17-loop.lst 18.tap: 18-cls.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 18-cls.lst 19.tap: 19-print-char-call.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 19-print-char-call.lst 20.tap: 20-print-char-rst.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 20-print-char-rst.lst 21.tap: 21-print-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 21-print-char.lst 22.tap: 22-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 22-print-all-chars.lst 23.tap: 23-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 23-print-all-chars.lst 24.tap: 24-change-color.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 24-change-color.lst 25.tap: 25-change-flash.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 25-change-flash.lst 26.tap: 26-print-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 26-print-at.lst 27.tap: 27-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 27-print-string.lst 28.tap: 28-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 28-print-string.lst 29.tap: 29-print-colorized-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 29-print-colorized-string.lst 30.tap: 30-print-string-ROM.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 30-print-string-ROM.lst 31.tap: 31-attributes.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 31-attributes.lst 32.tap: 32-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 32-fill-in-vram.lst 33.tap: 33-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 33-fill-in-vram-no-ret.lst 34.tap: 34-fill-in-vram-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 34-fill-in-vram-pattern.lst 35.tap: 35-slow-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 35-slow-fill-in-vram.lst 36.tap: 36-slow-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 36-slow-fill-in-vram-no-ret.lst 37.tap: 37-fill-block.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 37-fill-block.lst 38.tap: 38-fill-block-with-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 38-fill-block-with-pattern.lst 39.tap: 39-fill-block-optimized.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 39-fill-block-optimized.lst 40.tap: 40-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 40-draw-char.lst 41.tap: 41-draw-any-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 41-draw-any-char.lst 42.tap: 42-block-anywhere.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 42-block-anywhere.lst 43.tap: 43-block-anywhere-rrca.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 43-block-anywhere-rrca.lst 44.tap: 44-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 44-better-draw-char.lst 45.tap: 45-even-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 45-even-better-draw-char.lst 46.tap: 46-draw-char-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 46-draw-char-at.lst 47.tap: 47-draw-char-at-unrolled.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 47-draw-char-at-unrolled.lst 48.tap: 48-incorrect-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 48-incorrect-print-string.lst 49.tap: 49-correct-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 49-correct-print-string.lst
19. Repositář s demonstračními příklady
V tabulce zobrazené pod tímto odstavcem jsou uvedeny odkazy na všechny prozatím popsané demonstrační příklady určené pro překlad a spuštění na osmibitovém domácím mikropočítači ZX Spectrum (libovolný model či jeho klon), které jsou psány v assembleru mikroprocesoru Zilog Z80. Pro překlad těchto demonstračních příkladů je možné použít například assembler Pasmo (viz též úvodní článek):
# | Soubor | Stručný popis | Adresa |
---|---|---|---|
1 | 01-color-attribute.asm | modifikace jednoho barvového atributu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/01-color-attribute.asm |
2 | 02-blinking-attribute.asm | barvový atribut s nastavením bitů pro blikání a vyšší intenzitu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/02-blinking-attribute.asm |
3 | 03-symbolic-names.asm | symbolická jména v assembleru | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/03-symbolic-names.asm |
4 | 04-operators.asm | operátory a operace se symbolickými hodnotami | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/04-operators.asm |
5 | 05-better-symbols.asm | tradičnější symbolická jména | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/05-better-symbols.asm |
6 | 06-tapbas-v1.asm | vygenerování BASICovského loaderu (neúplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/06-tapbas-v1.asm |
7 | 07-tapbas-v2.asm | vygenerování BASICovského loaderu (úplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/07-tapbas-v2.asm |
8 | 08-loop.asm | jednoduchá počítaná programová smyčka: naivní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/08-loop.asm |
9 | 09-loop.asm | programová smyčka: zkrácení kódu pro vynulování použitých pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/09-loop.asm |
10 | 10-loop.asm | programová smyčka: optimalizace skoku na konci smyčky (instrukce DJNZ) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/10-loop.asm |
11 | 11-loop.asm | programová smyčka: optimalizace využití pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/11-loop.asm |
12 | 12-loop.asm | programová smyčka: použití pracovního registru IX | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/12-loop.asm |
13 | 13-loop.asm | programová smyčka: použití pracovního registru IY | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/13-loop.asm |
14 | 14-loop.asm | programová smyčka se šestnáctibitovým počitadlem, základní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/14-loop.asm |
15 | 15-loop.asm | programová smyčka se šestnáctibitovým počitadlem, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/15-loop.asm |
16 | 16-loop.asm | použití relativního skoku a nikoli skoku absolutního | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/16-loop.asm |
17 | 17-loop.asm | programová smyčka: inc l namísto inc hl | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/17-loop.asm |
18 | 18-cls.asm | smazání obrazovky a otevření kanálu číslo 2 (screen) přes funkci v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/18-cls.asm |
19 | 19-print-char-call.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce CALL) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/19-print-char-call.asm |
20 | 20-print-char-rst.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce RST) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/20-print-char-rst.asm |
21 | 21-print-char.asm | pouze výpis jednoho znaku na obrazovku bez jejího smazání | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/21-print-char.asm |
22 | 22-print-all-chars.asm | výpis znakové sady znak po znaku (nekorektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/22-print-all-chars.asm |
23 | 23-print-all-chars.asm | výpis znakové sady znak po znaku (korektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/23-print-all-chars.asm |
24 | 24-change-color.asm | změna barvových atributů (popředí a pozadí) vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/24-change-color.asm |
25 | 25-change-flash.asm | povolení či zákaz blikání vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/25-change-flash.asm |
26 | 26-print-at.asm | výpis znaku či znaků na určené místo na obrazovce | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/26-print-at.asm |
27 | 27-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (základní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/27-print-string.asm |
28 | 28-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (vylepšená varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/28-print-string.asm |
29 | 29-print-colorized-string.asm | výpis řetězce, který obsahuje i řídicí znaky pro změnu barvy atd. | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/29-print-colorized-string.asm |
30 | 30-print-string-ROM.asm | výpis řetězce s využitím služby/subrutiny uložené v ROM ZX Spectra | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/30-print-string-ROM.asm |
31 | 31-attributes.asm | modifikace atributů pro tisk řetězce subrutinou uloženou v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/31-attributes.asm |
32 | 32-fill-in-vram.asm | vyplnění celé bitmapy barvou popředí, návrat do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/32-fill-in-vram.asm |
33 | 33-fill-in-vram-no-ret.asm | vyplnění celé bitmapy barvou popředí, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/33-fill-in-vram-no-ret.asm |
34 | 34-fill-in-vram-pattern.asm | vyplnění celé bitmapy zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/34-fill-in-vram-pattern.asm |
35 | 35-slow-fill-in-vram.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/35-slow-fill-in-vram.asm |
36 | 36-slow-fill-in-vram-no-ret.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/36-slow-fill-in-vram-no-ret.asm |
37 | 37-fill-block.asm | vykreslení bloku 8×8 pixelů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/37-fill-block.asm |
38 | 38-fill-block-with-pattern.asm | vykreslení bloku 8×8 pixelů zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/38-fill-block-with-pattern.asm |
39 | 39-fill-block-optimized.asm | optimalizace předchozího příkladu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/39-fill-block-optimized.asm |
40 | 40-draw-char.asm | vykreslení znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/40-draw-char.asm |
41 | 41-draw-any-char.asm | podprogram pro vykreslení libovolně zvoleného znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/41-draw-any-char.asm |
42 | 42-block-anywhere.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/42-block-anywhere.asm |
43 | 43-block-anywhere-rrca.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/43-block-anywhere-rrca.asm |
44 | 44-better-draw-char.asm | vykreslení znaku v masce 8×8 pixelů, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/44-better-draw-char.asm |
45 | 45-even-better-draw-char.asm | posun offsetu pro vykreslení dalšího znaku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/45-even-better-draw-char.asm |
46 | 46-draw-char-at.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/46-draw-char-at.asm |
47 | 47-draw-char-at-unrolled.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/47-draw-char-at-unrolled.asm |
48 | 48-incorrect-print-string.asm | tisk řetězce, nekorektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/48-incorrect-print-string.asm |
49 | 49-correct-print-string.asm | tisk řetězce, korektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/49-correct-print-string.asm |
50 | Makefile | Makefile pro překlad a slinkování všech demonstračních příkladů do podoby obrazu magnetické pásky | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/Makefile |
20. Odkazy na Internetu
- z80 standalone assembler
https://www.asm80.com/onepage/asmz80.html - The ZX BASIC Compiler
https://www.boriel.com/pages/the-zx-basic-compiler.html - Z80 Assembly programming for the ZX Spectrum
https://www.chibiakumas.com/z80/ZXSpectrum.php - 8-BIT SMACKDOWN! 65C02 vs. Z80: slithy VLOGS #6
https://www.youtube.com/watch?v=P1paVoFEvyc - Instrukce mikroprocesoru Z80
https://clrhome.org/table/ - Z80 instructions: adresní režimy atd.
https://jnz.dk/z80/instructions.html - Z80 Instruction Groups
https://jnz.dk/z80/instgroups.html - Elena, New programming language for the ZX Spectrum Next
https://vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next/ - Sinclair BASIC
https://worldofspectrum.net/legacy-info/sinclair-basic/ - Grafika na osmibitových počítačích firmy Sinclair
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair/ - Grafika na osmibitových počítačích firmy Sinclair II
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/ - HiSoft BASIC
https://worldofspectrum.net/infoseekid.cgi?id=0008249 - YS MegaBasic
https://worldofspectrum.net/infoseekid.cgi?id=0008997 - Beta Basic
https://worldofspectrum.net/infoseekid.cgi?id=0007956 - BASIC+
https://worldofspectrum.net/infoseekid.php?id=0014277 - Spectrum ROM Memory Map
https://skoolkit.ca/disassemblies/rom/maps/all.html - Goto subroutine
https://skoolkit.ca/disassemblies/rom/asm/7783.html - Spectrum Next: The Evolution of the Speccy
https://www.specnext.com/about/ - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/ - Programovací jazyk BASIC na osmibitových mikropočítačích
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich/ - Programovací jazyk BASIC na osmibitových mikropočítačích (2)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-2/#k06 - Programovací jazyk BASIC na osmibitových mikropočítačích (3)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-3/ - Sinclair BASIC (Wikipedia CZ)
http://cs.wikipedia.org/wiki/Sinclair_BASIC - Assembly Language: Still Relevant Today
http://wilsonminesco.com/AssyDefense/ - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Why Assembly Language Programming? (Why Learning Assembly Language Is Still a Good Idea)
https://wdc65×x.com/markets/education/why-assembly-language-programming/ - Low Fat Computing
http://www.ultratechnology.com/lowfat.htm - Assembly Language
https://www.cleverism.com/skills-and-tools/assembly-language/ - Why do we need assembly language?
https://cs.stackexchange.com/questions/13287/why-do-we-need-assembly-language - Assembly language (Wikipedia)
https://en.wikipedia.org/wiki/Assembly_language#Historical_perspective - Assembly languages
https://curlie.org/Computers/Programming/Languages/Assembly/ - vasm
http://sun.hasenbraten.de/vasm/ - B-ELITE
https://jsj.itch.io/b-elite - ZX-Spectrum Child
http://www.dotkam.com/2008/11/19/zx-spectrum-child/ - Speccy.cz
http://www.speccy.cz/ - Planet Sinclair
http://www.nvg.ntnu.no/sinclair/ - World of Spectrum
http://www.worldofspectrum.org/ - The system variables
https://worldofspectrum.org/ZXBasicManual/zxmanchap25.html - ZX Spectrum manual: chapter #17 Graphics
https://worldofspectrum.org/ZXBasicManual/zxmanchap17.html