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