Kopie datových bloků na ZX Spectru: přenášení bajt po bajtu

2. 5. 2023
Doba čtení: 49 minut

Sdílet

 Autor: Depositphotos
V dalším článku o vývoji programů pro mikropočítač ZX Spectrum se začneme zabývat zdánlivě triviální problematikou: kopií datových bloků, tj. přenesení určitého množství bajtů z jedné adresy na adresu jinou.

Obsah

1. Kopie datových bloků na ZX Spectru (1)

2. Úplný zdrojový kód dnešního prvního demonstračního příkladu

3. Blokový přenos realizovaný naivně naprogramovanou smyčkou

4. Rychlost blokové kopie dat explicitně zapsanou programovou smyčkou

5. Úplný zdrojový kód dnešního druhého demonstračního příkladu

6. Kopie paměťového bloku s využitím instrukce LDIR

7. Rychlost blokové kopie dat instrukcí LDIR

8. Úplný zdrojový kód dnešního třetího demonstračního příkladu

9. Instrukce LDI

10. Programová smyčka založená na instrukci LDI

11. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

12. Instrukce LDI v rozbalené smyčce

13. Doba přenosu jednoho bajtu při použití rozbalené smyčky

14. Úplný zdrojový kód dnešního pátého demonstračního příkladu

15. Korektní forma rozbalené smyčky pro všechny velikosti bloků

16. Úplný zdrojový kód dnešního šestého demonstračního příkladu

17. Obsah navazujícího článku

18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů

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

20. Odkazy na Internetu

1. Kopie datových bloků na ZX Spectru (1)

V pořadí již jedenácté části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum se začneme zabývat zdánlivě triviální problematikou. Jedná se o implementaci podprogramu (subrutiny) zajišťující kopii datových bloků, tj. přenesení určitého množství bajtů z jedné adresy na adresu jinou.

Tato operace je na ZX Spectru velmi často používána, už jen z toho důvodu, že grafický subsystém ZX Spectra nepodporuje ani hardwarově vykreslované sprity ani hardwarový scrolling nebo double buffering (ten lze realizovat až na ZX Spectru 128+). To mj. znamená, že všechny pohybující se objekty ve hrách jsou vlastně bloky dat, které se musí přenášet (což je navíc poněkud komplikováno strukturou video paměti), popř. pohybující se pozadí je taktéž řešeno přes blokové přenosy.

Jet Pac

Obrázek 1: Každý pohybující se objekt ve slavné hře Jet Pac se na obrazovku dostává nějakou formou blokového přesunu dat.

Dnes se zaměříme především na techniky, které bloky přenáší bajt po bajtu. Ovšem existují taktéž techniky pro přenos po dvojici bajtů, což může být (při správném naprogramování) pochopitelně rychlejší, a to i na osmibitovém mikroprocesoru s osmibitovou datovou sběrnicí.

R-type

Obrázek 2: Prostředí známé hry R-type, v němž můžeme vidět rozdělení obrazovky na dvoubarevné bloky 8×8 pixelů. Při kolizích střel či rakety s okolními předměty docházelo ke vzniku barevných chyb, ostatně jako v mnoha dalších hrách. Scrolling i pohyby předmětů ve hře – vše je nutné realizovat blokovými přenosy.

2. Úplný zdrojový kód dnešního prvního demonstračního příkladu

Dnešní první demonstrační příklad bude sloužit jako základ pro příklady vysvětlené v navazujících kapitolách. V tomto příkladu je realizován již známý postup pro vykreslení ASCII tabulky do první třetiny obrazové paměti. Výsledek by měl vypadat následovně:

Obrázek 3: ASCII tabulka vykreslená do první třetiny obrazovky.

Všechny subrutiny použité v příkladu jsme si již v tomto seriálu vysvětlili, takže si pro úplnost pouze uvedeme jeho programový kód:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen      ; vyplnění obrazovky ASCII tabulkami
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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 vypadá následovně:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD0580     CALL 8005
8003:           label finito
8003:18FE       JR 8003
8005:           label fill_in_screen
8005:110040     LD DE, 4000
8008:CD0C80     CALL 800C
800B:C9         RET
800C:           label draw_ascii_table
800C:3E20       LD A, 20
800E:           label next_char
800E:F5         PUSH AF
800F:CD1E80     CALL 801E
8012:3E20       LD A, 20
8014:CD1E80     CALL 801E
8017:F1         POP AF
8018:3C         INC A
8019:FE80       CP 80
801B:20F1       JR NZ, 800E
801D:C9         RET
801E:           label draw_char
801E:01003C     LD BC, 3C00
8021:61         LD H, C
8022:6F         LD L, A
8023:29         ADD HL, HL
8024:29         ADD HL, HL
8025:29         ADD HL, HL
8026:09         ADD HL, BC
8027:0608       LD B, 08
8029:4A         LD C, D
802A:           label loop
802A:7E         LD A, (HL)
802B:12         LD (DE), A
802C:2C         INC L
802D:14         INC D
802E:10FA       DJNZ 802A
8030:1C         INC E
8031:C8         RET Z
8032:51         LD D, C
8033:C9         RET
8034:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8033
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/84-print-ascii-table.asm.

3. Blokový přenos realizovaný naivně naprogramovanou smyčkou

Nyní se podívejme na některé způsoby, které lze použít pro přenos bloku obsahující první třetinu obrazových dat (32×64=2048 bajtů) do druhé třetiny obrazovky. Připravíme si vlastní podprogram (subrutinu) pro kopii dat, přičemž v registrovém páru BC bude uložena zdrojová adresa, v registrovém páru DE cílová adresa a konečně v registrovém páru HL velikost bloku, tj. délka kopírovaných dat:

        ld   bc, SCREEN_ADR
        ld   de, SECOND_SCREEN_BLOCK
        ld   hl, SCREEN_BLOCK_SIZE
        call mem_copy
Poznámka: volba registrových párů není náhodná, jak uvidíme v navazujících kapitolách.

Poněkud naivní podoba subrutiny pro blokový přenos dat může vypadat následovně:

mem_copy:
        ld A, (BC)               ; načtení bajtu ze zdrojového bloku
        ld (DE), A               ; uložení bajtu do cílového bloku
        inc BC                   ; zvýšení ukazatele do zdrojového bloku
        inc DE                   ; zvýšení ukazatele do cílového bloku
        dec HL                   ; snížení hodnoty počitadla
        ld A, H                  ; test, zda počitadlo dosáhlo nuly
        or L
        jp NZ, mem_copy          ; opakovat, pokud se nedosáhlo nuly
        ret                      ; návrat z podprogramu

Vidíme, že se nejdříve ve dvou instrukcích provede přenos jednoho bajtu a zbytek smyčky realizuje zvýšení ukazatelů, snížení hodnoty počitadla a kontrolu, zda již počitadlo dosáhlo nuly.

Výsledek by měl vypadat následovně:

Obrázek 4: Druhá ASCII tabulka vznikla blokovou kopií tabulky první.

4. Rychlost blokové kopie dat explicitně zapsanou programovou smyčkou

Programová smyčka ukázaná v předchozí kapitole je sice pro bloky nenulové délky funkční, ale jak je rychlá? To si můžeme celkem snadno spočítat:

        ld A, (BC)        7
        ld (DE), A        7
        inc BC            6
        inc DE            6
        dec HL            6
        ld A, H           4
        or L              4
        jp NZ, mem_copy   10
      ------------------------
                          50

Celkem je tedy jediný bajt přenesen v padesáti cyklech a celý blok 2048 bajtů ve více než sto tisících cyklech! A to je již poměrně velká hodnota, kterou stojí za to optimalizovat.

5. Úplný zdrojový kód dnešního druhého demonstračního příkladu

Úplný zdrojový kód dnešního druhého demonstračního příkladu s naivně implementovanou subrutinou pro kopii paměťového bloku vypadá následovně:

SCREEN_ADR          equ $4000
SCREEN_BLOCK_SIZE   equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
 
CHAR_ADR            equ $3c00
ENTRY_POINT         equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen      ; vyplnění obrazovky ASCII tabulkami
        ld   bc, SCREEN_ADR
        ld   de, SECOND_SCREEN_BLOCK
        ld   hl, SCREEN_BLOCK_SIZE
        call mem_copy
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
mem_copy:
        ld A, (BC)               ; načtení bajtu ze zdrojového bloku
        ld (DE), A               ; uložení bajtu do cílového bloku
        inc BC                   ; zvýšení ukazatele do zdrojového bloku
        inc DE                   ; zvýšení ukazatele do cílového bloku
        dec HL                   ; snížení hodnoty počitadla
        ld A, H                  ; test, zda počitadlo dosáhlo nuly
        or L
        jp NZ, mem_copy          ; opakovat
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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

Uveďme si u způsob překladu z assembleru do strojového kódu:

SCREEN_ADR      EQU 4000
SCREEN_BLOCK_SIZE EQU 0800
SECOND_SCREEN_BLOCK EQU 4800
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD1180     CALL 8011
8003:010040     LD BC, 4000
8006:110048     LD DE, 4800
8009:210008     LD HL, 0800
800C:CD1880     CALL 8018
800F:           label finito
800F:18FE       JR 800F
8011:           label fill_in_screen
8011:110040     LD DE, 4000
8014:CD2380     CALL 8023
8017:C9         RET
8018:           label mem_copy
8018:0A         LD A, (BC)
8019:12         LD (DE), A
801A:03         INC BC
801B:13         INC DE
801C:2B         DEC HL
801D:7C         LD A, H
801E:B5         OR L
801F:C21880     JP NZ, 8018
8022:C9         RET
8023:           label draw_ascii_table
8023:3E20       LD A, 20
8025:           label next_char
8025:F5         PUSH AF
8026:CD3580     CALL 8035
8029:3E20       LD A, 20
802B:CD3580     CALL 8035
802E:F1         POP AF
802F:3C         INC A
8030:FE80       CP 80
8032:20F1       JR NZ, 8025
8034:C9         RET
8035:           label draw_char
8035:01003C     LD BC, 3C00
8038:61         LD H, C
8039:6F         LD L, A
803A:29         ADD HL, HL
803B:29         ADD HL, HL
803C:29         ADD HL, HL
803D:09         ADD HL, BC
803E:0608       LD B, 08
8040:4A         LD C, D
8041:           label loop
8041:7E         LD A, (HL)
8042:12         LD (DE), A
8043:2C         INC L
8044:14         INC D
8045:10FA       DJNZ 8041
8047:1C         INC E
8048:C8         RET Z
8049:51         LD D, C
804A:C9         RET
804B:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 804A
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/85-copy-ascii-table.asm.

6. Kopie paměťového bloku s využitím instrukce LDIR

Programová smyčka pro kopii bloku dat nebyla příliš rychlá, protože pro kopii každého bajtu (až na bajt poslední) vyžadovala 50 strojových cyklů. Mnohem kratší a taktéž rychlejší je použití specializované instrukce LDIR, která tento přenos (včetně opakování) provádí. Tato instrukce vyžaduje totožné vstupy, jako naše první subrutina, ovšem zapsané v jiných registrech: v registrovém páru HL bude uložena zdrojová adresa, v registrovém páru DE cílová adresa a konečně v registrovém páru BC velikost bloku, tj. délka kopírovaných dat (registr B či dvojice BC je pro tyto účely používána poměrně konzistentně).

Prováděnou operaci můžeme popsat tímto pseudokódem:

do {
    (DE) ← (HL)
    DE ← DE+1
    HL ←- HL+1
    BC ←- BC-1
} while (BC != 0)

Namísto volání subrutiny pro kopii bloku tedy můžeme jednoduše psát:

        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
        ldir                          ; provést blokový přenos

S totožným výsledkem, jaký jsme dosáhli explicitně zapsanou programovou smyčkou:

Obrázek 5: Druhá ASCII tabulka vznikla blokovou kopií tabulky první.

7. Rychlost blokové kopie dat explicitně zapsanou programovou smyčkou

Interně je instrukce LDIR implementována poněkud neefektivně, neboť pro každý přenášený bajt znovu a znovu načítá instrukční kód (2 bajty), provádí ho a poté posunuje obsah registru PC zpět. Z tohoto důvodu, i když ostatní operace mohou být částečně překryty, je délka trvání přenosu jednoho bajtu rovna 21 strojovým cyklům:

fetch instrukce   4
fetch instrukce   4
čtení bajtu       3
zápis bajtu       5
opakování         5
-------------------
                 21

Poslední běh je poněkud rychlejší, neboť již neobsahuje opakování (de facto zpětný skok), takže se ušetří pět cyklů:

fetch instrukce   4
fetch instrukce   4
čtení bajtu       3
zápis bajtu       5
-------------------
                 16
Poznámka: s využitím instrukce LDIR lze tedy dosáhnout podstatného urychlení kopie bloků dat a mohlo by se zdát, že se jedná o nejefektivnější řešení – ostatně právě proto tvůrci mikroprocesoru Z80 instrukci LDIR implementovali, že? Ovšem ve skutečnosti tomu tak není, jak uvidíme v dalším textu.

8. Úplný zdrojový kód dnešního třetího demonstračního příkladu

Třetí demonstrační příklad, v němž je realizována kopie bloku s využitím instrukce LDIR, vypadá takto:

; Example #86:
;    Print ASCII table on screen + copy it to second part of screen using LDIR instruction.
 
SCREEN_ADR          equ $4000
SCREEN_BLOCK_SIZE   equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
 
CHAR_ADR            equ $3c00
ENTRY_POINT         equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen           ; vyplnění obrazovky ASCII tabulkami
        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
        ldir                          ; provést blokový přenos
finito:
        jr finito                     ; ukončit program nekonečnou smyčkou
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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

Překlad tohoto demonstračního příkladu do strojového kódu bude vypadat následovně:

SCREEN_ADR      EQU 4000
SCREEN_BLOCK_SIZE EQU 0800
SECOND_SCREEN_BLOCK EQU 4800
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD1080     CALL 8010
8003:210040     LD HL, 4000
8006:110048     LD DE, 4800
8009:010008     LD BC, 0800
800C:EDB0       LDIR
800E:           label finito
800E:18FE       JR 800E
8010:           label fill_in_screen
8010:110040     LD DE, 4000
8013:CD1780     CALL 8017
8016:C9         RET
8017:           label draw_ascii_table
8017:3E20       LD A, 20
8019:           label next_char
8019:F5         PUSH AF
801A:CD2980     CALL 8029
801D:3E20       LD A, 20
801F:CD2980     CALL 8029
8022:F1         POP AF
8023:3C         INC A
8024:FE80       CP 80
8026:20F1       JR NZ, 8019
8028:C9         RET
8029:           label draw_char
8029:01003C     LD BC, 3C00
802C:61         LD H, C
802D:6F         LD L, A
802E:29         ADD HL, HL
802F:29         ADD HL, HL
8030:29         ADD HL, HL
8031:09         ADD HL, BC
8032:0608       LD B, 08
8034:4A         LD C, D
8035:           label loop
8035:7E         LD A, (HL)
8036:12         LD (DE), A
8037:2C         INC L
8038:14         INC D
8039:10FA       DJNZ 8035
803B:1C         INC E
803C:C8         RET Z
803D:51         LD D, C
803E:C9         RET
803F:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 803E
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/86-copy-ascii-table-B.asm.

9. Instrukce LDI

Připomeňme si, že při použití instrukce LDIR se interně použije celých pět strojových cyklů na vrácení hodnoty registru PC zpět o dva bajty, aby se stejná instrukce mohla načíst znovu. To je poněkud neefektivní a současně je to jediná vlastnost LDIRu, kterou můžeme eliminovat. Namísto LDIR totiž můžeme použít instrukci LDI (bez R na konci), která provádí „pouze“ tuto operaci:

(DE) ← (HL)
DE ← DE+1
HL ←- HL+1
BC ←- BC-1

Oproti LDIR zde tedy chybí test hodnoty BC a zpětný skok (opakování smyčky). A právě z tohoto důvodu je dobra trvání této instrukce rovna 16 strojovým cyklům (což již není špatná hodnota).

Této vlastnosti můžeme využít, protože nám to umožňuje použít instrukci LDI několikrát za sebou a test konce smyčky nedělat pokaždé (samozřejmě za předpokladu, že dopředu víme, že velikost bloku je například dělitelná šestnácti atd.). Jedná se tedy o klasickou optimalizaci nazývanou rozbalení smyčky neboli loop unrolling.

10. Programová smyčka založená na instrukci LDI

Prozatím ovšem rozbalení smyčky nebudeme provádět a ukážeme si pouze, jak by vypadalo (opět poněkud naivní) použití instrukce LDI v programové smyčce, v níž se test na nulovost BC provádí v každé iteraci, tj. po přesunu jediného bajtu:

        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
repeat:
        ldi                           ; provést přenos jednoho bajtu
        jp   pe, repeat               ; ukončit blokový přenos při BC==0

Povšimněte si, že se v instrukci podmíněného skoku testuje příznak P, protože právě tento příznak je instrukcí LDI nastavován, i když logicky by se mělo jednat o příznak Z.

Doba jedné iterace je 16 cyklů pro LDI a 10 cyklů pro JP, což je více, než při použití LDIR. Z toho plyne, že tento způsob využití LDIR není v žádném případě vhodný a je nutné provést rozbalení smyčky, jak uvidíme v dalším textu.

11. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

Dnešní čtvrtý demonstrační příklad, v němž je kopie bloku realizována programovou smyčkou s instrukcí LDI, vypadá následovně:

; Example #87:
;    Print ASCII table on screen + copy it to second part of screen using LDI+JP instructions.
 
SCREEN_ADR          equ $4000
SCREEN_BLOCK_SIZE   equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
 
CHAR_ADR            equ $3c00
ENTRY_POINT         equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen           ; vyplnění obrazovky ASCII tabulkami
        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
repeat:
        ldi                           ; provést přenos jednoho bajtu
        jp   pe, repeat               ; ukončit blokový přenos při BC==0
finito:
        jr finito                     ; ukončit program nekonečnou smyčkou
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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

Opět si uvedeme, jak se tento demonstrační příklad přeloží do strojového kódu:

SCREEN_ADR      EQU 4000
SCREEN_BLOCK_SIZE EQU 0800
SECOND_SCREEN_BLOCK EQU 4800
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD1380     CALL 8013
8003:210040     LD HL, 4000
8006:110048     LD DE, 4800
8009:010008     LD BC, 0800
800C:           label repeat
800C:EDA0       LDI
800E:EA0C80     JP PE, 800C
8011:           label finito
8011:18FE       JR 8011
8013:           label fill_in_screen
8013:110040     LD DE, 4000
8016:CD1A80     CALL 801A
8019:C9         RET
801A:           label draw_ascii_table
801A:3E20       LD A, 20
801C:           label next_char
801C:F5         PUSH AF
801D:CD2C80     CALL 802C
8020:3E20       LD A, 20
8022:CD2C80     CALL 802C
8025:F1         POP AF
8026:3C         INC A
8027:FE80       CP 80
8029:20F1       JR NZ, 801C
802B:C9         RET
802C:           label draw_char
802C:01003C     LD BC, 3C00
802F:61         LD H, C
8030:6F         LD L, A
8031:29         ADD HL, HL
8032:29         ADD HL, HL
8033:29         ADD HL, HL
8034:09         ADD HL, BC
8035:0608       LD B, 08
8037:4A         LD C, D
8038:           label loop
8038:7E         LD A, (HL)
8039:12         LD (DE), A
803A:2C         INC L
803B:14         INC D
803C:10FA       DJNZ 8038
803E:1C         INC E
803F:C8         RET Z
8040:51         LD D, C
8041:C9         RET
8042:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8041
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/87-copy-ascii-table-C.asm.

12. Instrukce LDI v rozbalené smyčce

Blok, který přenášíme, má velikost 2048 bajtů a jeho délka v bajtech je tedy dělitelná šestnácti. To znamená, že test na nulovost registrového páru BC můžeme provést vždy až po přenosu šestnácti bajtů a přitom nenastane situace, kdybychom konec smyčky „minuli“. Již výše zmíněný loop unrolling může vypadat následovně:

        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
repeat:
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        jp   pe, repeat               ; ukončit blokový přenos při BC==0

Vidíme, že test na nulovost BC je skutečně proveden až po přenosu 16 bajtů a proto je tento kód vhodný pouze pro bloky, které mají velikost celočíselného násobku 16.

13. Doba přenosu jednoho bajtu při použití rozbalené smyčky

Za kolik strojových cyklů se tedy celý blok přenese? Zkusme počítat:

  • Provede se 2048 instrukcí "DI: 2048×16=32768 cyklů
  • Provede se 2048/16=128 skoků: 128×10=1280 (u JP nezáleží na tom, zda je skok proveden či nikoli)

Celkem je tedy vyžadováno 32768+1280=34048 cyklů. Pro přenos jediného bajtu tedy potřebujeme 34048/2048=16.62 cyklů, což je opět znatelné urychlení oproti 50 cyklům resp. 21 cyklům (přesněji řečeno necelým 21 cyklům, protože poslední běh instrukce LDIR by byl zkrácen o pět cyklů – to je však u takto velkého bloku zanedbatelné).

14. Úplný zdrojový kód dnešního pátého demonstračního příkladu

V pátém demonstračním příkladu je realizována kopie bloku s využitím sekvence instrukcí LDI. Jeho úplný zdrojový kód vypadá následovně:

; Example #88:
;    Print ASCII table on screen + copy it to second part of screen using unrolled loop.
 
SCREEN_ADR          equ $4000
SCREEN_BLOCK_SIZE   equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
 
CHAR_ADR            equ $3c00
ENTRY_POINT         equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen           ; vyplnění obrazovky ASCII tabulkami
        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
repeat:
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        ldi                           ; provést přenos jednoho bajtu
        jp   pe, repeat               ; ukončit blokový přenos při BC==0
finito:
        jr finito                     ; ukončit program nekonečnou smyčkou
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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

A takto je demonstrační příklad přeložen z assembleru do strojového kódu:

SCREEN_ADR      EQU 4000
SCREEN_BLOCK_SIZE EQU 0800
SECOND_SCREEN_BLOCK EQU 4800
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD3180     CALL 8031
8003:210040     LD HL, 4000
8006:110048     LD DE, 4800
8009:010008     LD BC, 0800
800C:           label repeat
800C:EDA0       LDI
800E:EDA0       LDI
8010:EDA0       LDI
8012:EDA0       LDI
8014:EDA0       LDI
8016:EDA0       LDI
8018:EDA0       LDI
801A:EDA0       LDI
801C:EDA0       LDI
801E:EDA0       LDI
8020:EDA0       LDI
8022:EDA0       LDI
8024:EDA0       LDI
8026:EDA0       LDI
8028:EDA0       LDI
802A:EDA0       LDI
802C:EA0C80     JP PE, 800C
802F:           label finito
802F:18FE       JR 802F
8031:           label fill_in_screen
8031:110040     LD DE, 4000
8034:CD3880     CALL 8038
8037:C9         RET
8038:           label draw_ascii_table
8038:3E20       LD A, 20
803A:           label next_char
803A:F5         PUSH AF
803B:CD4A80     CALL 804A
803E:3E20       LD A, 20
8040:CD4A80     CALL 804A
8043:F1         POP AF
8044:3C         INC A
8045:FE80       CP 80
8047:20F1       JR NZ, 803A
8049:C9         RET
804A:           label draw_char
804A:01003C     LD BC, 3C00
804D:61         LD H, C
804E:6F         LD L, A
804F:29         ADD HL, HL
8050:29         ADD HL, HL
8051:29         ADD HL, HL
8052:09         ADD HL, BC
8053:0608       LD B, 08
8055:4A         LD C, D
8056:           label loop
8056:7E         LD A, (HL)
8057:12         LD (DE), A
8058:2C         INC L
8059:14         INC D
805A:10FA       DJNZ 8056
805C:1C         INC E
805D:C8         RET Z
805E:51         LD D, C
805F:C9         RET
8060:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 805F
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/88-copy-ascii-table-D.asm.

15. Korektní forma rozbalené smyčky pro všechny velikosti bloků

Rozbalení smyčky s instrukcí LDI se zdá být správná cesta pro to, aby se dosáhlo rychlého přenosu bloků dat. Ovšem ideální by bylo, aby se jednalo o univerzální smyčku vhodnou pro bloky o libovolné délce. I toho je možné dosáhnout a jeden z použitých triků je založen na samomodifikujícím se kódu. To znamená, že program na základě nějakého výpočtu mění sám sebe. Podívejme se tedy na to, jak daný trik vypadá v tomto konkrétním případě.

Podprogram pro kopii bloku dat je nyní rozdělen na tři části. V první části se testuje, zda není blok prázdný a navíc se zde počítá relativní adresa 0 až 30 konkrétní instrukce LDI v rozbalené smyčce. Tato relativní adresa je počítána z velikosti přenášeného bloku dat:

mem_copy:
        ld   a, b                ; kontrola BC na nulu
        or   c
        ret  z                   ; při prázdném bloku podprogram ukončíme
 
        ld   a, 16               ; na základě hodnoty v C vypočteme, kolik
        sub  c                   ; insturkcí LDI se má na začátku přenosu přeskočit
        and  15                  ; maximálně se přeskočí 15 instrukcí ze 16
 
        add  a, a                ; vynásobit dvěma protože LDI je dvoubajtová instrukce

Pro bloky o velikosti dělitelné 16 je relativní adresa rovna nule, pro bloky, jejichž velikost je o bajt větší se vypočte adresa 30 atd.

Nyní nastal čas pro použití samomodifikujícího se kódu: vypočtenou relativní adresu uložíme za operační kód instrukce JR (relativní skok), protože víme, že tato instrukce má délku dvou bajtů: operační kód+relativní adresa. To se v assembleru Pasmo provede takto (a trik bude funkční i v dalších assemblerech, které rozeznávají symbol $ jako aktuální adresu zpracovávané instrukce):

        ld   (jump_address), a   ; uložíme offset do tohoto paměťového místa
        jr   $                   ; relativní skok přečte offset z ^
jump_address equ $-1             ; trik jak zasáhnout do operandu instrukce JR

Nyní je již situace jednoduchá, protože JR provede skok doprostřed rozbalené smyčky. Pro bloky dělitelné 16 skočí na začátek smyčky, pro ostatní bloky na jednu ze zbývajících 15 instrukcí LDI. Další iterace pak budou provedeny pro všech 16 instrukcí LDI:

repeat:
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        jp   pe, repeat          ; ukončit blokový přenos při BC==0
        ret                      ; návrat z podprogramu

16. Úplný zdrojový kód dnešního šestého demonstračního příkladu

Na závěr si ukažme, jak vypadá úplný kód demonstračního příkladu realizujícího kopii paměťových bloků založeného na sekvenci instrukcí LDI:

; Example #88:
;    Print ASCII table on screen + copy it to second part of screen using unrolled loop.
 
SCREEN_ADR          equ $4000
SCREEN_BLOCK_SIZE   equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
 
CHAR_ADR            equ $3c00
ENTRY_POINT         equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen           ; vyplnění obrazovky ASCII tabulkami
        ld   hl, SCREEN_ADR           ; adresa zdrojového bloku
        ld   de, SECOND_SCREEN_BLOCK  ; adresa cílového bloku
        ld   bc, SCREEN_BLOCK_SIZE    ; velikost přenášených dat
        call mem_copy
finito:
        jr finito                     ; ukončit program nekonečnou smyčkou
 
mem_copy:
        ld   a, b                ; kontrola BC na nulu
        or   c
        ret  z                   ; při prázdném bloku podprogram ukončíme
 
        ld   a, 16               ; na základě hodnoty v C vypočteme, kolik
        sub  c                   ; insturkcí LDI se má na začátku přenosu přeskočit
        and  15                  ; maximálně se přeskočí 15 instrukcí ze 16
 
        add  a, a                ; vynásobit dvěma protože LDI je dvoubajtová instrukce
        ld   (jump_address), a   ; uložíme offset do tohoto paměťového místa
        jr   $                   ; relativní skok přečte offset z ^
jump_address equ $-1             ; trik jak zasáhnout do operandu instrukce JR
 
repeat:
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        ldi                      ; provést přenos jednoho bajtu
        jp   pe, repeat          ; ukončit blokový přenos při BC==0
        ret                      ; návrat z podprogramu
 
 
fill_in_screen:
        ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami
        ;
        ; vstupy:
        ; žádné
        ld de, SCREEN_ADR        ; adresa pro vykreslení prvního bloku znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        ret                      ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        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

Překlad do strojového kódu:

SCREEN_ADR      EQU 4000
SCREEN_BLOCK_SIZE EQU 0800
SECOND_SCREEN_BLOCK EQU 4800
CHAR_ADR EQU 3C00
ENTRY_POINT EQU 8000
ORG 8000
8000: label start
8000:CD4380 CALL 8043
8003:210040 LD HL, 4000
8006:110048 LD DE, 4800
8009:010008 LD BC, 0800
800C:CD1180 CALL 8011
800F: label finito
800F:18FE JR 800F
8011: label mem_copy
8011:78 LD A, B
8012:B1 OR C
8013:C8 RET Z
8014:3E10 LD A, 10
8016:91 SUB C
8017:E60F AND 0F
8019:87 ADD A, A
801A:321E80 LD (801E), A
801D:18FE JR 801D
jump_address EQU 801E
801F: label repeat
801F:EDA0 LDI
8021:EDA0 LDI
8023:EDA0 LDI
8025:EDA0 LDI
8027:EDA0 LDI
8029:EDA0 LDI
802B:EDA0 LDI
802D:EDA0 LDI
802F:EDA0 LDI
8031:EDA0 LDI
8033:EDA0 LDI
8035:EDA0 LDI
8037:EDA0 LDI
8039:EDA0 LDI
803B:EDA0 LDI
803D:EDA0 LDI
803F:EA1F80 JP PE, 801F
8042:C9 RET
8043: label fill_in_screen
8043:110040 LD DE, 4000
8046:CD4A80 CALL 804A
8049:C9 RET
804A: label draw_ascii_table
804A:3E20 LD A, 20
804C: label next_char
804C:F5 PUSH AF
804D:CD5C80 CALL 805C
8050:3E20 LD A, 20
8052:CD5C80 CALL 805C
8055:F1 POP AF
8056:3C INC A
8057:FE80 CP 80
8059:20F1 JR NZ, 804C
805B:C9 RET
805C: label draw_char
805C:01003C LD BC, 3C00
805F:61 LD H, C
8060:6F LD L, A
8061:29 ADD HL, HL
8062:29 ADD HL, HL
8063:29 ADD HL, HL
8064:09 ADD HL, BC
8065:0608 LD B, 08
8067:4A LD C, D
8068: label loop
8068:7E LD A, (HL)
8069:12 LD (DE), A
806A:2C INC L
806B:14 INC D
806C:10FA DJNZ 8068
806E:1C INC E
806F:C8 RET Z
8070:51 LD D, C
8071:C9 RET
8072: END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8071
Poznámka: úplný zdrojový kód tohoto demonstračního příkladu najdete na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/89-copy-ascii-table-E.asm.

17. Obsah navazujícího článku

Mohlo by se zdát, že trik s rozbalenou programovou smyčkou založenou na instrukci LDI je to nejlepší, čeho můžeme na mikroprocesoru Z80 dosáhnout. Ve skutečnosti tomu tak není, protože byl objeven i další trik, který využívá instrukce pro ukládání a obnovování obsahu registrových párů (dvou bajtů!) na zásobníku. Příště si ukážeme, jak byl tento trik využíván. Jen pro ukázku – přenos šestnácti bajtů může být realizován i takto, přičemž doba trvání přenosu jednoho bajtu klesne na přibližně 13 cyklů:

bitcoin školení listopad 24

        ld   sp, SCREEN_ADR+32*9
        pop  af
        pop  bc
        pop  de
        pop  hl
        exx
        ex   af, af'
        pop  af
        pop  bc
        pop  de
        pop  hl
        ld   sp, SECOND_SCREEN_BLOCK+16
        push hl
        push de
        push bc
        push af
        exx
        ex   af, af'
        push hl
        push de
        push bc
        push af

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 deseti článcích [1] [2], [3], [4], [5], [6], [7], [8], [9], [10], 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 50.tap \
    51.tap 52.tap 53.tap 54.tap 55.tap 56.tap 57.tap 58.tap 59.tap 60.tap \
    61.tap 62.tap 63.tap 64.tap 65.tap 66.tap 67.tap 68.tap 69.tap 70.tap \
    71.tap 72.tap 73.tap 74.tap 75.tap 76.tap 77.tap 78.tap 79.tap 80.tap \
    81.tap 82.tap 83.tap 84.tap 85.tap 86.tap 87.tap 88.tap 80.tap 90.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
 
50.tap: 50-ascii-table.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 50-ascii-table.lst
 
51.tap: 51-plot-block.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 51-plot-block.lst
 
52.tap: 52-plot-pixel.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 52-plot-pixel.lst
 
53.tap: 53-plot-pixel.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 53-plot-pixel.lst
 
54.tap: 54-plot-pixel-on-background.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 54-plot-pixel-on-background.lst
 
55.tap: 55-plot-pixel-on-background.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 55-plot-pixel-on-background.lst
 
56.tap: 56-inverse-ascii-table.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 56-inverse-ascii-table.lst
 
57.tap: 57-plot-pixel-on-inverse-background.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 57-plot-pixel-on-inverse-background.lst
 
58.tap: 58-plot-inverse-pixel-on-inverse-background.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 58-plot-inverse-pixel-on-inverse-background.lst
 
59.tap: 59-configurable-ascii-table.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 59-configurable-ascii-table.lst
 
60.tap: 60-plot-over.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 60-plot-over.lst
 
61.tap: 61-print-number-A.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 61-print-number-A.lst
 
62.tap: 62-print-number-B.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 62-print-number-B.lst
 
63.tap: 63-print-number-C.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 63-print-number-C.lst
 
64.tap: 64-print-number-D.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 64-print-number-D.lst
 
65.tap: 65-more-numbers-A.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 65-more-numbers-A.lst
 
66.tap: 66-more-numbers-B.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 66-more-numbers-B.lst
 
67.tap: 67-print-flags-1.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 67-print-flags-1.lst
 
68.tap: 68-print-flags-2.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 68-print-flags-2.lst
 
69.tap: 69-print-flags-3.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 69-print-flags-3.lst
 
70.tap: 70-print-flags-4.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 70-print-flags-4.lst
 
71.tap: 71-print-flags-5.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 71-print-flags-5.lst
 
72.tap: 72-print-flags-6.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 72-print-flags-6.lst
 
73.tap: 73-print-flags-7.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 73-print-flags-7.lst
 
74.tap: 74-print-hex-number.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 74-print-hex-number.lst
 
75.tap: 75-print-hex-number.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 75-print-hex-number.lst
 
76.tap: 76-print-hex-numbers.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 76-print-hex-numbers.lst
 
77.tap: 77-add-hex-numbers.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 77-add-hex-numbers.lst
 
78.tap: 78-add-bcd-numbers.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 78-add-bcd-numbers.lst
 
79.tap: 79-print-hex-digit-jmp.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 79-print-hex-digit-jmp.lst
 
80.tap: 80-print-hex-digit-overflow.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 80-print-hex-digit-overflow.lst
 
81.tap: 81-print-hex-digit-daa.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 81-print-hex-digit-daa.lst
 
82.tap: 82-print-hex-numbers-daa.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 82-print-hex-numbers-daa.lst
 
83.tap: 83-print-fp-numbers.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 83-print-fp-numbers.lst
 
84.tap: 84-print-ascii-table.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 84-print-ascii-table.lst
 
85.tap: 85-copy-ascii-table.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 85-copy-ascii-table.lst
 
86.tap: 86-copy-ascii-table-B.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 86-copy-ascii-table-B.lst
 
87.tap: 87-copy-ascii-table-C.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 87-copy-ascii-table-C.lst
 
88.tap: 88-copy-ascii-table-D.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 88-copy-ascii-table-D.lst
 
89.tap: 89-copy-ascii-table-E.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 89-copy-ascii-table-E.lst
 
90.tap: 90-copy-ascii-table-F.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 90-copy-ascii-table-F.lst
 
x.tap:  x.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > x.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 50-ascii-table.asm tisk několika bloků ASCII tabulky https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/50-ascii-table.asm
51 51-plot-block.asm vykreslení pixelu verze 1: zápis celého bajtu na pozici pixelu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/51-plot-block.asm
52 52-plot-pixel.asm vykreslení pixelu verze 2: korektní vykreslení jednoho pixelu, ovšem překreslení celého bajtu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/52-plot-pixel.asm
53 53-plot-pixel.asm vykreslení pixelu verze 3: vylepšená verze předchozího demonstračního příkladu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/53-plot-pixel.asm
54 54-plot-pixel-on-background.asm vykreslení pixelu vůči pozadí (nekorektní varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/54-plot-pixel-on-background.asm
55 55-plot-pixel-on-background.asm vykreslení pixelu vůči pozadí (korektní varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/55-plot-pixel-on-background.asm
       
56 56-inverse-ascii-table.asm vykreslení ASCII tabulky inverzní barvou (inkoust vs. papír) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/56-inverse-ascii-table.asm
57 57-plot-pixel-on-inverse-background.asm vykreslení pixelů barvou papíru proti inverzní ASCII tabulce https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/57-plot-pixel-on-inverse-background.asm
58 58-plot-inverse-pixel-on-inverse-background.asm vykreslení pixelů inverzní barvou proti inverzní ASCII tabulce https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm58-plot-inverse-pixel-on-inverse-background.asm/
59 59-configurable-ascii-table.asm vykreslení ASCII tabulky buď přímo inkoustem nebo inverzně https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/59-configurable-ascii-table.asm
60 60-plot-over.asm přibližná implementace příkazu PLOT OVER https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/60-plot-over.asm
       
61 61-print-number-A.asm ukázka použití podprogramu pro tisk celého čísla https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/61-print-number-A.asm
62 62-print-number-B.asm pokus o vytištění záporných čísel https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/62-print-number-B.asm
63 63-print-number-C.asm tisk maximální podporované hodnoty 9999 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/63-print-number-C.asm
64 64-print-number-D.asm tisk vyšší než podporované hodnoty 10000 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/64-print-number-D.asm
65 65-more-numbers-A.asm vytištění číselné řady https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/65-more-numbers-A.asm
66 66-more-numbers-B.asm kombinace tisku celočíselných hodnot s dalšími subrutinami https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/66-more-numbers-B.asm
67 67-print-flags-1.asm příznakové bity po provedení celočíselné operace 1+2 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/67-print-flags-1.asm
68 68-print-flags-2.asm příznakové bity po provedení celočíselné operace 0+0 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/68-print-flags-2.asm
69 69-print-flags-3.asm příznakové bity po provedení operace 255+1 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/69-print-flags-3.asm
70 70-print-flags-4.asm příznakové bity po provedení operace 254+1 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/70-print-flags-4.asm
71 71-print-flags-5.asm příznakové bity po provedení operace 255+255 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/71-print-flags-5.asm
72 72-print-flags-6.asm výsledek operace 100+100, nastavení příznakových bitů https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/72-print-flags-6.asm
73 73-print-flags-7.asm výsledek operace 128+128, nastavení příznakových bitů https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/73-print-flags-7.asm
       
74 74-print-hex-number.asm tisk hexadecimálního čísla v rozsahu 0×00 až 0×ff (neoptimalizovaná varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/74-print-hex-number.asm
75 75-print-hex-number.asm tisk hexadecimálního čísla v rozsahu 0×00 až 0×ff (optimalizovaná varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/75-print-hex-number.asm
76 76-print-hex-numbers.asm tisk několika hexadecimálních hodnot https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/76-print-hex-numbers.asm
77 77-add-hex-numbers.asm součet dvou osmibitových hexadecimálních hodnot s tiskem všech výsledků https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/77-add-hex-numbers.asm
78 78-add-bcd-numbers.asm součet dvou osmibitových BCD hodnot s tiskem všech výsledků https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/78-add-bcd-numbers.asm
       
79 79-print-hex-digit-jmp.asm tisk jedné hexadecimální cifry s využitím podmíněného skoku https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/79-print-hex-digit-jmp.asm
80 80-print-hex-digit-overflow.asm otestování, jaký znak je vytištěn pro hodnoty větší než 15 https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/80-print-hex-digit-overflow.asm
81 81-print-hex-digit-daa.asm tisk jedné hexadecimální cifry s využitím instrukce DAA https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/81-print-hex-digit-daa.asm
82 82-print-hex-numbers-daa.asm tisk série hexadecimálních hodnot s využitím instrukce DAA https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/82-print-hex-numbers-daa.asm
83 83-print-fp-numbers.asm tisk numerických hodnot reprezentovaných v systému plovoucí řádové tečky https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/83-print-fp-numbers.asm
       
84 84-print-ascii-table.asm tisk jednoho bloku s ASCII tabulkou https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/84-print-ascii-table.asm
85 85-copy-ascii-table.asm kopie bloku bajt po bajtu založená na naivní programové smyčce https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/85-copy-ascii-table.asm
86 86-copy-ascii-table-B.asm kopie bloku s využitím instrukce LDIR https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/86-copy-ascii-table-B.asm
87 87-copy-ascii-table-C.asm kopie bloku bajt po bajtu založená na programové smyčce a instrukci LDI https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/87-copy-ascii-table-C.asm
88 88-copy-ascii-table-D.asm rozbalení programové smyčky s instrukcí LDI https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/88-copy-ascii-table-D.asm
89 89-copy-ascii-table-E.asm korektní smyčka pro všechny možné velikosti bloků https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/89-copy-ascii-table-E.asm
90 90-copy-ascii-table-F.asm kostra programu, který pro kopii bloků využívá zásobník https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/90-copy-ascii-table-F.asm
       
91 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

  1. z80 standalone assembler
    https://www.asm80.com/one­page/asmz80.html
  2. The ZX BASIC Compiler
    https://www.boriel.com/pages/the-zx-basic-compiler.html
  3. Z80 Assembly programming for the ZX Spectrum
    https://www.chibiakumas.com/z80/ZXSpec­trum.php
  4. 8-BIT SMACKDOWN! 65C02 vs. Z80: slithy VLOGS #6
    https://www.youtube.com/wat­ch?v=P1paVoFEvyc
  5. Instrukce mikroprocesoru Z80
    https://clrhome.org/table/
  6. Z80 instructions: adresní režimy atd.
    https://jnz.dk/z80/instructions.html
  7. Z80 Instruction Groups
    https://jnz.dk/z80/instgroups.html
  8. Elena, New programming language for the ZX Spectrum Next
    https://vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next/
  9. Sinclair BASIC
    https://worldofspectrum.net/legacy-info/sinclair-basic/
  10. Grafika na osmibitových počítačích firmy Sinclair
    https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair/
  11. Grafika na osmibitových počítačích firmy Sinclair II
    https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/
  12. HiSoft BASIC
    https://worldofspectrum.net/in­foseekid.cgi?id=0008249
  13. YS MegaBasic
    https://worldofspectrum.net/in­foseekid.cgi?id=0008997
  14. Beta Basic
    https://worldofspectrum.net/in­foseekid.cgi?id=0007956
  15. BASIC+
    https://worldofspectrum.net/in­foseekid.php?id=0014277
  16. Spectrum ROM Memory Map
    https://skoolkit.ca/disas­semblies/rom/maps/all.html
  17. Goto subroutine
    https://skoolkit.ca/disas­semblies/rom/asm/7783.html
  18. Spectrum Next: The Evolution of the Speccy
    https://www.specnext.com/about/
  19. Sedmdesátiny assemblerů: lidsky čitelný strojový kód
    https://www.root.cz/clanky/sed­mdesatiny-assembleru-lidsky-citelny-strojovy-kod/
  20. Programovací jazyk BASIC na osmibitových mikropočítačích
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich/
  21. Programovací jazyk BASIC na osmibitových mikropočítačích (2)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich-2/#k06
  22. Programovací jazyk BASIC na osmibitových mikropočítačích (3)
    https://www.root.cz/clanky/pro­gramovaci-jazyk-basic-na-osmibitovych-mikropocitacich-3/
  23. Sinclair BASIC (Wikipedia CZ)
    http://cs.wikipedia.org/wi­ki/Sinclair_BASIC
  24. Assembly Language: Still Relevant Today
    http://wilsonminesco.com/AssyDefense/
  25. Programovani v assembleru na OS Linux
    http://www.cs.vsb.cz/gryga­rek/asm/asmlinux.html
  26. Why Assembly Language Programming? (Why Learning Assembly Language Is Still a Good Idea)
    https://wdc65×x.com/market­s/education/why-assembly-language-programming/
  27. Low Fat Computing
    http://www.ultratechnology­.com/lowfat.htm
  28. Assembly Language
    https://www.cleverism.com/skills-and-tools/assembly-language/
  29. Why do we need assembly language?
    https://cs.stackexchange.com/qu­estions/13287/why-do-we-need-assembly-language
  30. Assembly language (Wikipedia)
    https://en.wikipedia.org/wi­ki/Assembly_language#Histo­rical_perspective
  31. Assembly languages
    https://curlie.org/Computer­s/Programming/Languages/As­sembly/
  32. vasm
    http://sun.hasenbraten.de/vasm/
  33. B-ELITE
    https://jsj.itch.io/b-elite
  34. ZX-Spectrum Child
    http://www.dotkam.com/2008/11/19/zx-spectrum-child/
  35. Speccy.cz
    http://www.speccy.cz/
  36. Planet Sinclair
    http://www.nvg.ntnu.no/sinclair/
  37. World of Spectrum
    http://www.worldofspectrum.org/
  38. The system variables
    https://worldofspectrum.or­g/ZXBasicManual/zxmanchap25­.html
  39. ZX Spectrum manual: chapter #17 Graphics
    https://worldofspectrum.or­g/ZXBasicManual/zxmanchap17­.html
  40. Why does Sinclair BASIC have two formats for storing numbers in the same structure?
    https://retrocomputing.stac­kexchange.com/questions/8834/why-does-sinclair-basic-have-two-formats-for-storing-numbers-in-the-same-structu
  41. Plovoucí řádová čárka na ZX Spectru
    https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/#k05
  42. Norma IEEE 754 a příbuzní: formáty plovoucí řádové tečky
    https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/#k05
  43. 1A1B: THE ‚REPORT AND LINE NUMBER PRINTING‘ SUBROUTINE
    https://skoolkid.github.i­o/rom/asm/1A1B.html
  44. 2DE3: THE ‚PRINT A FLOATING-POINT NUMBER‘ SUBROUTINE
    https://skoolkid.github.i­o/rom/asm/2DE3.html
  45. 5C63: STKBOT – Address of bottom of calculator stack
    https://skoolkid.github.i­o/rom/asm/5C63.html
  46. 5C65: STKEND – Address of start of spare space
    https://skoolkid.github.i­o/rom/asm/5C65.html
  47. Why does Sinclair BASIC have two formats for storing numbers in the same structure?
    https://retrocomputing.stac­kexchange.com/questions/8834/why-does-sinclair-basic-have-two-formats-for-storing-numbers-in-the-same-structu
  48. Chapter 24: The memory
    https://worldofspectrum.or­g/ZXBasicManual/zxmanchap24­.html
  49. Survey of Floating-Point Formats  
    https://mrob.com/pub/math/flo­atformats.html
  50. Convert an 8bit number to hex in z80 assembler
    https://stackoverflow.com/qu­estions/22838444/convert-an-8bit-number-to-hex-in-z80-assembler
  51. 80 MICROPROCESSOR Instruction Set Summary
    http://www.textfiles.com/pro­gramming/CARDS/z80
  52. Extended Binary Coded Decimal Interchange Code
    http://en.wikipedia.org/wiki/EBCDIC
  53. ASCII/EBCDIC Conversion Table
    http://docs.hp.com/en/32212–90008/apcs01.html
  54. EBCDIC
    http://www.hansenb.pdx.edu/DMKB/dic­t/tutorials/ebcdic.php
  55. EBCDIC tables
    http://home.mnet-online.de/wzwz.de/temp/eb­cdic/cc_en.htm
  56. The Mainframe Blog
    http://mainframe.typepad.com/blog/2006/11/my_per­sonal_mai.html
  57. Binary-coded decimal
    https://en.wikipedia.org/wiki/Binary-coded_decimal
  58. BCD
    https://cs.wikipedia.org/wiki/BCD
  59. Z80 heaven: Floating Point
    http://z80-heaven.wikidot.com/floating-point
  60. Z80, the 8-bit Number Cruncher
    http://www.andreadrian.de/ol­dcpu/Z80_number_cruncher.html
  61. Floating-point library for Z80
    https://github.com/DW0RKiN/Floating-point-Library-for-Z80
  62. z80float
    https://github.com/Zeda/z80float
  63. Fixed point arithmetic
    https://www.root.cz/clanky/fixed-point-arithmetic/
  64. ZX Spectrum BASIC Programming – 2nd Edition
    https://archive.org/details/zx-spectrum-basic-programming/page/n167/mode/2up
  65. ZX Spectrum BASIC Programming – 2nd Edition
    https://archive.org/details/zx-spectrum-basic-programming/page/n169/mode/2up
  66. How fast is memcpy on the Z80?
    https://retrocomputing.stac­kexchange.com/questions/4744/how-fast-is-memcpy-on-the-z80
  67. How do Z80 Block Transfer instructions work?
    https://retrocomputing.stac­kexchange.com/questions/5416/how-do-z80-block-transfer-instructions-work

Autor článku

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