Hlavní navigace

Vývoj pro ZX Spectrum: dokončení realizace příkazu PLOT

28. 3. 2023
Doba čtení: 56 minut

Sdílet

 Autor: Depositphotos
Dokončíme poměrně rozsáhlé téma, kterému jsme se věnovali minule i předminule. Jedná se o vlastní implementaci podprogramu v assembleru mikroprocesoru Zilog Z80, jenž bude napodobovat příkaz PLOT ze Sinclair BASICu.

Obsah

1. Dokončení realizace příkazu PLOT

2. Vyplnění obrazovky inverzními znaky z ASCII tabulky

3. Úplný zdrojový kód demonstračního příkladu pro tisk inverzních ASCII tabulek

4. Původní rutina pro vykreslení pixelu použitá společně s inverzní ASCII tabulkou

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

6. Vykreslení jediného pixelu barvou papíru

7. Úprava vykreslovací rutiny PLOT pro korektní kreslení barvou papíru

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

9. Jak vytvořit subrutinu pro tisk normální i inverzní ASCII tabulky?

10. Samomodifikující se kód na mikroprocesoru Zilog Z80

11. Úprava subrutiny pro vykreslení znaku

12. Řízená modifikace předchozí subrutiny v čase běhu programu

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

14. Vykreslení pixelu metodou PLOT OVER (inverze)

15. Inverze celé obrazovky tím nejpomalejším způsobem – pixel po pixelu

16. Úplný zdrojový kód dnešního poslední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. Dokončení realizace příkazu PLOT

V pořadí již sedmé části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum dokončíme až kupodivu velmi rozsáhlé a netriviální téma, kterému jsme se věnovali předminuleminule. Jedná se o vlastní implementaci podprogramu (neboli subrutiny) naprogramované v assembleru osmibitového mikroprocesoru Zilog Z80, přičemž tato subrutina bude napodobovat příkaz PLOT ze Sinclair BASICu. Tento příkaz slouží pro vykreslení jediného pixelu na obrazovku; programátor přitom může určit, zda se vykreslení provede barvou inkoustu (v BASICu se používá jméno ink), barvou papíru (paper) nebo zda se má již nakreslený pixel invertovat (z barvy inkoustu na barvu papíru či naopak).

Obrázek 1: První verze programu realizujícího rutinu PLOT: postupné vykreslování úsečky z „blokových pixelů“.

V závěru předchozího článku, konkrétně v sedmnácté kapitole, je uveden výpis demonstračního příkladu, jenž dokáže vykreslit pixel (resp. pixely) barvou inkoustu a přitom nepřemazat pixely v okolí. Úspěšně jsme tedy realizovali první krok k implementaci plnohodnotného příkazu PLOT. Dnes si ukážeme další dvě možné varianty příkazu PLOT, a to konkrétně vykreslení pixelu barvou papíru a inverzi pixelu.

Obrázek 2: Druhá verze programu realizujícího rutinu PLOT: postupné vykreslování úsečky z „úzkých pixelů“, ovšem s přepisem pozadí (ostatních sedmi pixelů v bloku).

Budeme se zabývat i tvorbou takzvaného samomodifikujícího se kódu. Jedná se o zajímavou techniku, v níž běžící program přepíše část sebe samého takovým způsobem, aby se dosáhlo kýženého efektu. Tato technika kromě optimalizace na velikost programu, popř. na rychlost jeho běhu navíc umožňuje, aby se minimalizoval počet parametrů vstupujících do složitěji strukturovaného kódu, protože obecně platí, že předávání většího množství parametrů je na (vlastně všech) typech mikroprocesorů relativně problematické.

Obrázek 3: Třetí verze programu realizujícího rutinu PLOT: postupné vykreslování úsečky z „úzkých pixelů“.

2. Vyplnění obrazovky inverzními znaky z ASCII tabulky

Ještě předtím, než se budeme zabývat úpravou podprogramu (subrutiny) určené pro vykreslení pixelu, si, ostatně podobně jako v předchozím článku, ukážeme program, který po svém spuštění vykreslí na obrazovku ZX Spectra několik ASCII tabulek, ovšem inverzně – to konkrétně znamená, že text (znak) bude vykreslen barvou papíru a okolí textu barvou inkoustu. Tento podprogram budeme potřebovat z toho prostého důvodu, abychom zaplnili plochu pozadí nějakým vzorkem, na němž bude patrné, zda vykreslovací rutina PLOT pracuje korektně či nikoli. Výsledná obrazovka vykreslená novou subrutinou by přitom měla vypadat následovně:

Obrázek 4: Inverzní znaky z ASCII tabulky.

Úprava podprogramu pro tisk znaků tak, aby se znaky vytiskly inverzně, je velmi jednoduchá, protože postačuje přidat strojovou instrukci pro negaci celého bajtu, který je zapisován na obrazovku (jak uvidíme dále, je tato operace pro znaky jednodušší, než pro jednotlivé pixely). Původní podprogram vypadal takto:

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
 
loop2:
        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 loop2               ; 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

Po výše zmíněné úpravě získáme následující subrutinu (viz podtržená část kódu, která byla modifikována):

draw_char_inv:
        ; Vytištění jednoho inverzního 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
        cpl                      ; negace hodnoty v akumulátoru
        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

3. Úplný zdrojový kód demonstračního příkladu pro tisk inverzních ASCII tabulek

Úplný zdrojový kód demonstračního příkladu popsaného ve druhé kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/56-inverse-ascii-table.asm:

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_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu
 
 
draw_ascii_table_inv:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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_inv       ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char_inv       ; 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_inv:
        ; Vytištění jednoho inverzního 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
        cpl                      ; negace hodnoty v akumulátoru
        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

Výsledek překladu tohoto programu do strojového kódu vypadá takto:

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:CD1580     CALL 8015
800B:CD1580     CALL 8015
800E:CD1580     CALL 8015
8011:CD1580     CALL 8015
8014:C9         RET
8015:           label draw_ascii_table_inv
8015:3E20       LD A, 20
8017:           label next_char
8017:F5         PUSH AF
8018:CD2780     CALL 8027
801B:3E20       LD A, 20
801D:CD2780     CALL 8027
8020:F1         POP AF
8021:3C         INC A
8022:FE80       CP 80
8024:20F1       JR NZ, 8017
8026:C9         RET
8027:           label draw_char_inv
8027:01003C     LD BC, 3C00
802A:61         LD H, C
802B:6F         LD L, A
802C:29         ADD HL, HL
802D:29         ADD HL, HL
802E:29         ADD HL, HL
802F:09         ADD HL, BC
8030:0608       LD B, 08
8032:4A         LD C, D
8033:           label loop
8033:7E         LD A, (HL)
8034:2F         CPL
8035:12         LD (DE), A
8036:2C         INC L
8037:14         INC D
8038:10F9       DJNZ 8033
803A:1C         INC E
803B:C8         RET Z
803C:51         LD D, C
803D:C9         RET
803E:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 803D

4. Původní rutina pro vykreslení pixelu použitá společně s inverzní ASCII tabulkou

Jen pro úplnost si ukažme, jak bude vypadat obrazovka ZX Spectra, pokud na ní nejdříve vykreslíme sadu inverzních ASCII tabulek a následně do této scény vykreslíme diagonální úsečku s černými pixely, resp. přesněji řečeno s pixely vykreslenými inkoustem (ink). Výsledná úsečka nebude (zcela podle očekávání) příliš viditelná:

Obrázek 5: Úsečka vytvořená černými pixely vykreslenými nad (převážně) tmavým pozadím (logicky není moc viditelná).

Poznámka: tento příklad v rámci navazujícího textu upravíme takovým způsobem, že úsečka bude vykreslena z pixelů s barvou papíru (paper).

Samotné tělo programu se nebude příliš lišit od příkladů ukázaných v předchozím článku. Jeho základní kostra (main) zůstane zcela zachována:

start:
        call fill_in_screen      ; vyplnění obrazovky ASCII tabulkami
 
        ld b, 0                  ; x-ová souřadnice vykreslovaného pixelu
        ld c, 0                  ; y-ová souřadnice vykreslovaného pixelu
loop:
        call plot                ; vykreslení pixelu
        call delay
        inc b                    ; posun na další souřadnici
        inc c
        ld  a, b
        cp  192                  ; test na ukončení smyčky
        jr nz, loop              ; opakovat, dokud není vykreslena celá šikmá "úsečka"
finito:
        jr finito                ; ukončit program nekonečnou smyčkou

Rozdíl spočívá v poněkud odlišně realizované rutině fill_in_screen, která bude vykreslovat inverzní ASCII tabulky:

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_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu

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 je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/57-plot-pixel-on-inverse-background.asm a vypadá následovně:

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
 
        ld b, 0                  ; x-ová souřadnice vykreslovaného pixelu
        ld c, 0                  ; y-ová souřadnice vykreslovaného pixelu
loop:
        call plot                ; vykreslení pixelu
        call delay
        inc b                    ; posun na další souřadnici
        inc c
        ld  a, b
        cp  192                  ; test na ukončení smyčky
        jr nz, loop              ; opakovat, dokud není vykreslena celá šikmá "úsečka"
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
 
 
delay:
        ; zpožďovací rutina
        ; nemění žádné registry
        push bc                  ; uschovat hodnoty registrů, které se používají ve smyčkách
        ld   b, 20               ; počitadlo vnější zpožďovací smyčky
outer_loop:
        ld   c, 0                ; počitadlo vnitřní zpožďovací smyčky
inner_loop:
        dec  c                   ; snížení hodnoty počitadla (v první iteraci 256->255)
        jr   NZ, inner_loop      ; opakovat, dokud není dosaženo nuly
        djnz outer_loop          ; opakovat vnější smyčku, nyní s počitadlem v B
        pop  bc                  ; obnovit hodnoty registrů změněných smyčkami
        ret                      ; návrat z podprogramu
 
 
plot:
        ; třetí varianta podprogramu pro vykreslení pixelu
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        or d                     ; použít vypočtenou masku pro nastavení jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu
 
 
calc_pixel_value:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; A - hodnota pixelu
        push bc                  ; zapamatovat si hodnotu v registru B
        ld   a, b                ; A: X7 X6 X5 X4 X3 X2 X1 X0 
        and  %00000111           ; A: 0  0  0  0  0  X2 X1 X0
        ld b, a                  ; počitadlo smyčky (neměníme příznaky)
        ld a, %10000000          ; výchozí maska (neměníme příznaky)
        jr z, end_calc           ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec
 
next_shift:
        srl a                    ; posunout masku doprava
        djnz next_shift          ; 1x až 7x
end_calc:
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
 
 
calc_pixel_address:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; HL - adresa pro zápis pixelu
        ;
        ; pozměněné registry:
        ; A
        ;
        ; vzor adresy:
        ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0)
                              ; A: 0 0 0 0 0 Y2 Y1 Y0
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  h, a              ; část horního bajtu adresy je vypočtena
                              ; H: 0 1 0 0 0 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3
        and %00011000         ; zamaskovat
                              ; A: 0  0  0 Y7 Y6  0  0  0
        or  h                 ; a přidat k vypočtenému mezivýsledku
        ld  h, a              ; H: 0  1  0 Y7 Y6 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rla
        rla                   ; A:  Y5 Y4 Y3 Y2 Y1 Y0 xx xx
        and %11100000         ; A:  Y5 Y4 Y3 0  0  0  0  0
        ld  l, a              ; část spodního bajtu adresy je vypočtena
 
        ld  a, b              ; všech osm bitů X-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> 0  0  0  X7 X6 X5 X4
        and %00011111         ; A: 0  0  0  X7 X6 X5 X4 X3
        or  l                 ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3
        ld  l, a              ; spodní bajt adresy je vypočten
 
        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_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu
 
 
draw_ascii_table_inv:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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_inv       ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char_inv       ; 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_inv:
        ; Vytištění jednoho inverzního 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
 
loop2:
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        cpl                      ; negace hodnoty v akumulátoru
        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 loop2               ; 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

Pochopitelně si opět pro úplnost ukážeme, jakým způsobem byl proveden překlad do strojového kódu:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD5880     CALL 8058
8003:0600       LD B, 00
8005:0E00       LD C, 00
8007:           label loop
8007:CD2280     CALL 8022
800A:CD1680     CALL 8016
800D:04         INC B
800E:0C         INC C
800F:78         LD A, B
8010:FEC0       CP C0
8012:20F3       JR NZ, 8007
8014:           label finito
8014:18FE       JR 8014
8016:           label delay
8016:C5         PUSH BC
8017:0614       LD B, 14
8019:           label outer_loop
8019:0E00       LD C, 00
801B:           label inner_loop
801B:0D         DEC C
801C:20FD       JR NZ, 801B
801E:10F9       DJNZ 8019
8020:C1         POP BC
8021:C9         RET
8022:           label plot
8022:CD3B80     CALL 803B
8025:CD2C80     CALL 802C
8028:56         LD D, (HL)
8029:B2         OR D
802A:77         LD (HL), A
802B:C9         RET
802C:           label calc_pixel_value
802C:C5         PUSH BC
802D:78         LD A, B
802E:E607       AND 07
8030:47         LD B, A
8031:3E80       LD A, 80
8033:2804       JR Z, 8039
8035:           label next_shift
8035:CB3F       SRL A
8037:10FC       DJNZ 8035
8039:           label end_calc
8039:C1         POP BC
803A:C9         RET
803B:           label calc_pixel_address
803B:79         LD A, C
803C:E607       AND 07
803E:F640       OR 40
8040:67         LD H, A
8041:79         LD A, C
8042:1F         RRA
8043:1F         RRA
8044:1F         RRA
8045:E618       AND 18
8047:B4         OR H
8048:67         LD H, A
8049:79         LD A, C
804A:17         RLA
804B:17         RLA
804C:E6E0       AND E0
804E:6F         LD L, A
804F:78         LD A, B
8050:1F         RRA
8051:1F         RRA
8052:1F         RRA
8053:E61F       AND 1F
8055:B5         OR L
8056:6F         LD L, A
8057:C9         RET
8058:           label fill_in_screen
8058:110040     LD DE, 4000
805B:CD6880     CALL 8068
805E:CD6880     CALL 8068
8061:CD6880     CALL 8068
8064:CD6880     CALL 8068
8067:C9         RET
8068:           label draw_ascii_table_inv
8068:3E20       LD A, 20
806A:           label next_char
806A:F5         PUSH AF
806B:CD7A80     CALL 807A
806E:3E20       LD A, 20
8070:CD7A80     CALL 807A
8073:F1         POP AF
8074:3C         INC A
8075:FE80       CP 80
8077:20F1       JR NZ, 806A
8079:C9         RET
807A:           label draw_char_inv
807A:01003C     LD BC, 3C00
807D:61         LD H, C
807E:6F         LD L, A
807F:29         ADD HL, HL
8080:29         ADD HL, HL
8081:29         ADD HL, HL
8082:09         ADD HL, BC
8083:0608       LD B, 08
8085:4A         LD C, D
8086:           label loop2
8086:7E         LD A, (HL)
8087:2F         CPL
8088:12         LD (DE), A
8089:2C         INC L
808A:14         INC D
808B:10F9       DJNZ 8086
808D:1C         INC E
808E:C8         RET Z
808F:51         LD D, C
8090:C9         RET
8091:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8090

6. Vykreslení jediného pixelu barvou papíru

Nyní se již konečně můžeme zabývat problémem, který budeme chtít v dnešním článku vyřešit – jakým způsobem se zajistí realizace příkazu PLOT INVERSE, tedy vykreslení jediného pixelu barvou papíru (paper)? Teoreticky můžeme postupovat stejným způsobem, jako při tisku pixelu barvou inkoustu:

  1. Výpočet adresy v obrazové paměti, kde je uložen vykreslovaný pixel i sedm dalších sousedních pixelů
  2. Výpočet masky, tj. osmibitové hodnoty, která se bude aplikovat na původní bajt přečtený z obrazové paměti
  3. Aplikace masky, tj. kombinace původní hodnoty přečtené z obrazové paměti s vypočtenou maskou
  4. Zápis výsledku na (stejnou) adresu v obrazové paměti

Obecný postup tedy již známe a máme ho realizován pro pixel v barvě inkoustu. Jak se však tento postup bude konkrétně implementovat v našem „inverzním“ případě? Evidentně nebudeme měnit první bod, protože adresa pro čtení/zápis bajtu z/do obrazové paměti zůstane stejná. Podobně nebudeme měnit ani bod poslední, protože i ten musí zůstat zachován. Změnit tedy budeme muset výpočet masky a aplikaci této masky.

Pro vykreslení pixelu barvou inkoustu je nutné nastavit vybraný bit na jedničku. K tomu slouží instrukce or a maska, která má jeden bit nastavený na jedničku a ostatní bity nulové:

původní hodnota   x x x x x x x x
maska             0 0 0 1 0 0 0 0
=================================
hodnota OR maska  x x x 1 x x x x

Naproti tomu pro vykreslení pixelu barvou papíru je nutné vynulovat vybraný bit. K tomu slouží instrukce and a maska, která má jeden bit nastavený na nulu o ostatní bity jedničkové:

původní hodnota   x x x x x x x x
maska             1 1 1 0 1 1 1 1
=================================
hodnota AND maska x x x 0 x x x x

7. Úprava vykreslovací rutiny PLOT pro korektní kreslení barvou papíru

Pro vykreslení pixelu barvou inkoustu vypadala realizace výše uvedeného postupu následovně:

plot:
        ; třetí varianta podprogramu pro vykreslení pixelu
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        or d                     ; použít vypočtenou masku pro nastavení jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu

V upravené variantě jsme přidali instrukci cpl určenou pro negaci obsahu akumulátoru (complement, tím je myšlen jedničkový doplněk) a instrukce or d byla nahrazena za instrukci and d:

plot_inverse:
        ; varianta podprogramu pro vykreslení pixelu barvou papíru
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        cpl                      ; inverze masky
        and d                    ; použít vypočtenou masku pro vynulování jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu
Poznámka: zbytek programu mohl zůstat stejný; týká se to i výpočtu adresy pixelu i výpočtu masky.

Obrázek 6: Úsečka vykreslená z pixelů s barvou papíru, nikoli inkoustu.

Obrázek 7: Úsečka vykreslená z pixelů s barvou papíru, nikoli inkoustu.

Obrázek 8: Úsečka vykreslená z pixelů s barvou papíru, nikoli inkoustu.

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

Úplný zdrojový kód dnešního v pořadí již třetího demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/58-plot-inverse-pixel-on-inverse-background.asm a vypadá následovně:

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
 
        ld b, 0                  ; x-ová souřadnice vykreslovaného pixelu
        ld c, 0                  ; y-ová souřadnice vykreslovaného pixelu
loop:
        call plot_inverse        ; vykreslení pixelu barvou papíru
        call delay
        inc b                    ; posun na další souřadnici
        inc c
        ld  a, b
        cp  192                  ; test na ukončení smyčky
        jr nz, loop              ; opakovat, dokud není vykreslena celá šikmá "úsečka"
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
 
 
delay:
        ; zpožďovací rutina
        ; nemění žádné registry
        push bc                  ; uschovat hodnoty registrů, které se používají ve smyčkách
        ld   b, 20               ; počitadlo vnější zpožďovací smyčky
outer_loop:
        ld   c, 0                ; počitadlo vnitřní zpožďovací smyčky
inner_loop:
        dec  c                   ; snížení hodnoty počitadla (v první iteraci 256->255)
        jr   NZ, inner_loop      ; opakovat, dokud není dosaženo nuly
        djnz outer_loop          ; opakovat vnější smyčku, nyní s počitadlem v B
        pop  bc                  ; obnovit hodnoty registrů změněných smyčkami
        ret                      ; návrat z podprogramu
 
 
plot_inverse:
        ; varianta podprogramu pro vykreslení pixelu barvou papíru
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        cpl                      ; inverze masky
        and d                    ; použít vypočtenou masku pro vynulování jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu
 
 
calc_pixel_value:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; A - hodnota pixelu
        push bc                  ; zapamatovat si hodnotu v registru B
        ld   a, b                ; A: X7 X6 X5 X4 X3 X2 X1 X0 
        and  %00000111           ; A: 0  0  0  0  0  X2 X1 X0
        ld b, a                  ; počitadlo smyčky (neměníme příznaky)
        ld a, %10000000          ; výchozí maska (neměníme příznaky)
        jr z, end_calc           ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec
 
next_shift:
        srl a                    ; posunout masku doprava
        djnz next_shift          ; 1x až 7x
end_calc:
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
 
 
calc_pixel_address:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; HL - adresa pro zápis pixelu
        ;
        ; pozměněné registry:
        ; A
        ;
        ; vzor adresy:
        ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0)
                              ; A: 0 0 0 0 0 Y2 Y1 Y0
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  h, a              ; část horního bajtu adresy je vypočtena
                              ; H: 0 1 0 0 0 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3
        and %00011000         ; zamaskovat
                              ; A: 0  0  0 Y7 Y6  0  0  0
        or  h                 ; a přidat k vypočtenému mezivýsledku
        ld  h, a              ; H: 0  1  0 Y7 Y6 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rla
        rla                   ; A:  Y5 Y4 Y3 Y2 Y1 Y0 xx xx
        and %11100000         ; A:  Y5 Y4 Y3 0  0  0  0  0
        ld  l, a              ; část spodního bajtu adresy je vypočtena
 
        ld  a, b              ; všech osm bitů X-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> 0  0  0  X7 X6 X5 X4
        and %00011111         ; A: 0  0  0  X7 X6 X5 X4 X3
        or  l                 ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3
        ld  l, a              ; spodní bajt adresy je vypočten
 
        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_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        call draw_ascii_table_inv ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu
 
 
draw_ascii_table_inv:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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_inv       ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char_inv       ; 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_inv:
        ; Vytištění jednoho inverzního 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
 
loop2:
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        cpl                      ; negace hodnoty v akumulátoru
        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 loop2               ; 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 tohoto příkladu do strojového kódu je následující:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:CD5980     CALL 8059
8003:0600       LD B, 00
8005:0E00       LD C, 00
8007:           label loop
8007:CD2280     CALL 8022
800A:CD1680     CALL 8016
800D:04         INC B
800E:0C         INC C
800F:78         LD A, B
8010:FEC0       CP C0
8012:20F3       JR NZ, 8007
8014:           label finito
8014:18FE       JR 8014
8016:           label delay
8016:C5         PUSH BC
8017:0614       LD B, 14
8019:           label outer_loop
8019:0E00       LD C, 00
801B:           label inner_loop
801B:0D         DEC C
801C:20FD       JR NZ, 801B
801E:10F9       DJNZ 8019
8020:C1         POP BC
8021:C9         RET
8022:           label plot_inverse
8022:CD3C80     CALL 803C
8025:CD2D80     CALL 802D
8028:56         LD D, (HL)
8029:2F         CPL
802A:A2         AND D
802B:77         LD (HL), A
802C:C9         RET
802D:           label calc_pixel_value
802D:C5         PUSH BC
802E:78         LD A, B
802F:E607       AND 07
8031:47         LD B, A
8032:3E80       LD A, 80
8034:2804       JR Z, 803A
8036:           label next_shift
8036:CB3F       SRL A
8038:10FC       DJNZ 8036
803A:           label end_calc
803A:C1         POP BC
803B:C9         RET
803C:           label calc_pixel_address
803C:79         LD A, C
803D:E607       AND 07
803F:F640       OR 40
8041:67         LD H, A
8042:79         LD A, C
8043:1F         RRA
8044:1F         RRA
8045:1F         RRA
8046:E618       AND 18
8048:B4         OR H
8049:67         LD H, A
804A:79         LD A, C
804B:17         RLA
804C:17         RLA
804D:E6E0       AND E0
804F:6F         LD L, A
8050:78         LD A, B
8051:1F         RRA
8052:1F         RRA
8053:1F         RRA
8054:E61F       AND 1F
8056:B5         OR L
8057:6F         LD L, A
8058:C9         RET
8059:           label fill_in_screen
8059:110040     LD DE, 4000
805C:CD6980     CALL 8069
805F:CD6980     CALL 8069
8062:CD6980     CALL 8069
8065:CD6980     CALL 8069
8068:C9         RET
8069:           label draw_ascii_table_inv
8069:3E20       LD A, 20
806B:           label next_char
806B:F5         PUSH AF
806C:CD7B80     CALL 807B
806F:3E20       LD A, 20
8071:CD7B80     CALL 807B
8074:F1         POP AF
8075:3C         INC A
8076:FE80       CP 80
8078:20F1       JR NZ, 806B
807A:C9         RET
807B:           label draw_char_inv
807B:01003C     LD BC, 3C00
807E:61         LD H, C
807F:6F         LD L, A
8080:29         ADD HL, HL
8081:29         ADD HL, HL
8082:29         ADD HL, HL
8083:09         ADD HL, BC
8084:0608       LD B, 08
8086:4A         LD C, D
8087:           label loop2
8087:7E         LD A, (HL)
8088:2F         CPL
8089:12         LD (DE), A
808A:2C         INC L
808B:14         INC D
808C:10F9       DJNZ 8087
808E:1C         INC E
808F:C8         RET Z
8090:51         LD D, C
8091:C9         RET
8092:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8091

9. Jak vytvořit subrutinu pro tisk normální i inverzní ASCII tabulky?

V dalších kapitolách si mj. ukážeme způsob vykreslování „inverzních pixelů“, což je vlastně další varianta BASICovského příkazu PLOT. Pixel, který měl původně barvu inkoustu se změní na barvu pozadí a naopak. Aby byla tato operace dobře viditelná, pozměníme nejprve nepatrně pozadí celé scény zobrazené na obrazovce. Opět vykreslíme několik sad ASCII znaků, tentokrát však bude první polovina obrazovky vyplněna inverzními ASCII tabulky a druhá polovina původními (neinverzními) tabulkami:

Obrázek 9: Obrazovka ZX Spectra vykreslená jak neinverzní ASCII tabulkou, tak i její inverzní variantou.

Jak lze tohoto efektu dosáhnout? Nejprimitivnější by pochopitelně bylo ponechat v programovém kódu obě subrutiny pro tisk ASCII tabulek, tedy jak subrutinu pro tisk neinverzní tabulky, tak i tabulky inverzní:

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

a:

draw_ascii_table_inv:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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_inv       ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char_inv       ; 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

Jenže když se podíváte na obě subrutiny, je ihned patrné, že se neliší jejich „vlastní“ instrukce (to by nebyl největší problém, protože se jedná pouze o několik instrukcí), ale že se volají i odlišné subrutiny pro tisk znaků, resp. inverzních znaků. A tyto subrutiny jsou již dosti rozsáhlé a asi není nejlepší nápad zaplňovat omezený prostor RAM (48 kB včetně obrazové paměti) několika kopiemi prakticky totožného programového kódu:

Samozřejmě by bylo možné nějakým způsobem do těchto subrutin předat další parametr typu „vykresluj znaky normálně“ nebo „vykresluj znaky inverzně“. Problém spočívá v omezeném počtu volných registrů (takové registry neexistují) a taktéž v tom, že by se uvnitř subrutin musel tento parametr neustále testovat a tím pádem by došlo ke zpomalení celého kódu.

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
 
loop2:
        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 loop2               ; 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

a:

draw_char_inv:
        ; Vytištění jednoho inverzního 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
        cpl                      ; negace hodnoty v akumulátoru
        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

10. Samomodifikující se kód na mikroprocesoru Zilog Z80

Zkusme namísto snahy o předání dalšího parametru subrutině draw_char použít zcela odlišný přístup. Nejprve si obě subrutiny porovnejme instrukci po instrukci:

           label draw_char                 label draw_char_inv
01003C     LD BC, 3C00          01003C     LD BC, 3C00
61         LD H, C              61         LD H, C
6F         LD L, A              6F         LD L, A
29         ADD HL, HL           29         ADD HL, HL
29         ADD HL, HL           29         ADD HL, HL
29         ADD HL, HL           29         ADD HL, HL
09         ADD HL, BC           09         ADD HL, BC
0608       LD B, 08             0608       LD B, 08
4A         LD C, D              4A         LD C, D
           label loop                      label loop
7E         LD A, (HL)           7E         LD A, (HL)
                                2F         CPL
12         LD (DE), A           12         LD (DE), A
2C         INC L                2C         INC L
14         INC D                14         INC D
10FA       DJNZ 8033            10F9       DJNZ 8033
1C         INC E                1C         INC E
C8         RET Z                C8         RET Z
51         LD D, C              51         LD D, C
C9         RET                  C9         RET

Z výše uvedeného porovnání je zřejmé, že se subrutiny odlišují pouze v jediné instrukci, která byla přidána do subrutiny pro tisk inverzního znaku. A takový problém umíme na osmibitových mikropočítačích s mikroprocesory bez ochrany paměti snadno řešit, a to konkrétně použitím samomodifikujícího se kódu. Jedná se o takový kód, jehož instrukce se mění v čase běhu programu (tedy v runtime) cílenou změnou operačních kódů instrukcí a/nebo dalších bajtů, v nichž jsou uloženy adresy nebo jiné operandy instrukcí.

Aby bylo možné tuto modifikaci provést, musíme znát (= umět najít) operační kódy instrukcí, protože mnoho assemblerů neumožňuje použít mnemotechnický název instrukce jako operand jiné instrukce (pro assembler tvoří mnemotechnické kódy instrukcí samostatný jmenný prostor). To však nevadí, protože si mapování mezi mnemotechnickým názvem instrukce a číselným kódem instrukce můžeme do programu přidat sami formou pojmenovaných symbolů.

Z tabulky operačních kódů instrukcí tedy můžeme zjistit, jakou hodnotu máme zapsat, ovšem ještě je nutné zajistit, aby se tato hodnota zapsala na správné místo. To je v assembleru až směšně jednoduché (a prakticky nemožné dosáhnout ve vyšších programovacích jazycích), protože před každou instrukci můžeme vložit návěští (label). Měněný program může vypadat takto:

        ld a, 10
menena_instrukce:
        inc a
        ld b, a

Po vykonání této části kódu by měla být v registrech A a B zapsána hodnota 11. Pokud ovšem spustíme tuto část kódu:

        ld hl, menena_instrukce
        ld (hl), 0

popř. můžeme použít čitelnější podobu se symbolem:

NOP_INSTRUCTION equ 0
 
        ld hl, menena_instrukce
        ld (hl), NOP_INSTRUCTION

Změní se původní program (v runtime) de facto takto:

        ld a, 10
menena_instrukce:
        nop
        ld b, a

Proč tomu tak je? Hodnota 0, kterou jsme na místo instrukce zapsali, odpovídá kódu instrukce NOP, což snadno přečteme z již výše linkované tabulky.

Podobným způsobem například můžeme nahradit programovou smyčku, v níž se postupně zvyšuje hodnota počitadla instrukcí inc za smyčku, kde se hodnota naopak snižuje atd. atd. – podobných triků existují stovky a možná i tisíce.

11. Úprava subrutiny pro vykreslení znaku

Z předchozího textu již víme, jakým způsobem je možné modifikovat část programového kódu, takže se vraťme k řešení našeho problému – jak zajistit vytištění běžné ASCII tabulky a též tabulky, ovšem s inverzními znaky? Použijeme samomodifikující se kód, přičemž rutinu pro vykreslení jediného znaku nepatrně změníme. Ona změna spočívá v přidání návěští inv_instruction_adr před instrukci, kterou budeme chtít modifikovat. Jedná se o instrukci pro negaci obsahu akumulátoru A, což je instrukce CPL s délkou jednoho bajtu (to je důležité):

draw_char:
        ; Vytištění jednoho inverzního 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
inv_instruction_adr:
        cpl                      ; negace hodnoty v akumulátoru
        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

12. Řízená modifikace předchozí subrutiny v čase běhu programu

Subrutina popsaná v předchozí kapitole má před instrukcí CPL definováno návěští inv_instruction_adr. Díky tomuto návěští víme, do jakého paměťového místa zapisovat. Kód instrukce CPL můžeme kdykoli v runtime nahradit za instrukci NOP s kódem 0×00 nebo se později vrátit zpět k CPL zápisem kódu 0×2f (popř. použít libovolnou další instrukci o délce jednoho bajtu).

Nejdříve si zadefinujeme symbol pro instrukci NOP, aby byl výsledný program čitelnější:

NOP_INSTRUCTION equ 0

Vykreslení inverzní ASCII tabulky (původní kód obsahuje instrukci CPL):

        call draw_ascii_table     ; vykreslení 96 znaků

Vykreslení neinverzní ASCII tabulky modifikací programového kódu:

        ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat
        ld (hl), NOP_INSTRUCTION  ; zápis instrukce NOP namísto instrukce CPL
 
        call draw_ascii_table     ; vykreslení 96 znaků

Opětovná modifikace též instrukce povede k návratu k vykreslování inverzní ASCII tabulky:

        ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat
        ld (hl), CPL_INSTRUCTION  ; zápis instrukce NOP namísto instrukce CPL
 
        call draw_ascii_table     ; vykreslení 96 znaků

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

Úplný zdrojový kód dnešního v pořadí již čtvrtého demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/59-configurable-ascii-table.asm a vypadá následovně:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
NOP_INSTRUCTION equ 0
 
 
        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ů
        call draw_ascii_table     ; vykreslení 96 znaků
 
        ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat
        ld (hl), NOP_INSTRUCTION  ; zápis instrukce NOP namísto instrukce CPL
 
        call draw_ascii_table     ; vykreslení 96 znaků
        call draw_ascii_table     ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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 inverzního 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
inv_instruction_adr:
        cpl                      ; negace hodnoty v akumulátoru
        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
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
NOP_INSTRUCTION EQU 0000
                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:CD1A80     CALL 801A
800B:CD1A80     CALL 801A
800E:213980     LD HL, 8039
8011:3600       LD (HL), 00
8013:CD1A80     CALL 801A
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:           label inv_instruction_adr
8039:2F         CPL
803A:12         LD (DE), A
803B:2C         INC L
803C:14         INC D
803D:10F9       DJNZ 8038
803F:1C         INC E
8040:C8         RET Z
8041:51         LD D, C
8042:C9         RET
8043:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8042

14. Vykreslení pixelu metodou PLOT OVER (inverze)

V závěrečné části dnešního článku si ukážeme způsob vykreslení jediného pixelu metodou PLOT OVER. Jedná se o operaci, která změní (flip) hodnotu jediného bitu v obrazové paměti. Implementace je vlastně velmi jednoduchá, protože budeme (stále) aplikovat masku se sedmi bity nulovými a jediným bitem nastaveným na jedničku. A tato maska bude aplikována operací XOR (nonekvivalence), která onen jeden bit zneguje. Implementace je snadná:

plot_over:
        ; varianta podprogramu pro vykreslení pixelu inverzní barvou
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        xor d                    ; použít vypočtenou masku pro vynulování jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu
Poznámka: povšimněte si, že i tuto subrutinu bude možné (auto)modifikovat tak, aby prováděla buď operaci PLOT či PLOT OVER.

15. Inverze celé obrazovky tím nejpomalejším způsobem – pixel po pixelu

Podprogram plot_over ukázaný v předchozí kapitole dokáže invertovat barvu libovolného pixelu na obrazovce. Vyzkoušejme si tedy inverzi celé obrazovky pixel po pixelu (což je pochopitelně ten nejpomalejší možný způsob :-). Tato operace bude tak pomalá, že její provádění bude viditelné i bez použití zpožďovací smyčky:

Obrázek 10: Původní obsah obrazovky.

Obrázek 11: Postupná inverze obrazovky pixel po pixelu.

Obrázek 12: Postupná inverze obrazovky pixel po pixelu.

Obrázek 13: Postupná inverze obrazovky pixel po pixelu.

Obrázek 14: Postupná inverze obrazovky pixel po pixelu.

Způsob realizace formou vnější a vnitřní programové smyčky:

        ; Vstupní bod celého programu
start:
        call fill_in_screen      ; vyplnění obrazovky ASCII tabulkami
 
        ld b, 0                  ; x-ová souřadnice vykreslovaného pixelu
        ld c, 0                  ; y-ová souřadnice vykreslovaného pixelu
loop:
        call plot_over           ; vykreslení pixelu
        inc b                    ; posun na další x-ovou souřadnici
        jr nz, loop              ; vykreslit celou vodorovnou úsečku
        inc c                    ; posun na další y-ovou souřadnici
        ld  a, c
        cp  192                  ; test na ukončení smyčky
        jr nz, loop              ; opakovat, dokud není překreslena celá obrazovka pixel po pixelu
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
Poznámka: povšimněte si, že obě smyčky doskakují na stejné návěští loop. Taktéž stojí za povšimnutí fakt, že ve vnitřní smyčce kontrolujeme příznak zero, jenž je automaticky nastavován instrukcí inc.

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

Úplný zdrojový kód dnešního v pořadí již pátého a současně i posledního demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/60-plot-over.asm a vypadá následovně:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
NOP_INSTRUCTION equ 0
 
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        call fill_in_screen      ; vyplnění obrazovky ASCII tabulkami
 
        ld b, 0                  ; x-ová souřadnice vykreslovaného pixelu
        ld c, 0                  ; y-ová souřadnice vykreslovaného pixelu
loop:
        call plot_over           ; vykreslení pixelu
        inc b                    ; posun na další x-ovou souřadnici
        jr nz, loop              ; vykreslit celou vodorovnou úsečku
        inc c                    ; posun na další y-ovou souřadnici
        ld  a, c
        cp  192                  ; test na ukončení smyčky
        jr nz, loop              ; opakovat, dokud není překreslena celá obrazovka pixel po pixelu
finito:
        jr finito                ; ukončit program nekonečnou smyčkou
 
 
plot_over:
        ; varianta podprogramu pro vykreslení pixelu inverzní barvou
        ;
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        call calc_pixel_address  ; výpočet adresy pixelu
        call calc_pixel_value    ; výpočet ukládané hodnoty
        ld d, (hl)               ; přečíst původní hodnotu osmice pixelů
        xor d                    ; použít vypočtenou masku pro vynulování jediného bitu
        ld (hl), a               ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění)
        ret                      ; návrat z podprogramu
 
 
calc_pixel_value:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; A - hodnota pixelu
        push bc                  ; zapamatovat si hodnotu v registru B
        ld   a, b                ; A: X7 X6 X5 X4 X3 X2 X1 X0 
        and  %00000111           ; A: 0  0  0  0  0  X2 X1 X0
        ld b, a                  ; počitadlo smyčky (neměníme příznaky)
        ld a, %10000000          ; výchozí maska (neměníme příznaky)
        jr z, end_calc           ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec
 
next_shift:
        srl a                    ; posunout masku doprava
        djnz next_shift          ; 1x až 7x
end_calc:
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
 
 
calc_pixel_address:
        ; parametry:
        ; B - x-ová souřadnice (v pixelech)
        ; C - y-ová souřadnice (v pixelech)
        ;
        ; návratové hodnoty:
        ; HL - adresa pro zápis pixelu
        ;
        ; pozměněné registry:
        ; A
        ;
        ; vzor adresy:
        ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0)
                              ; A: 0 0 0 0 0 Y2 Y1 Y0
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  h, a              ; část horního bajtu adresy je vypočtena
                              ; H: 0 1 0 0 0 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3
        and %00011000         ; zamaskovat
                              ; A: 0  0  0 Y7 Y6  0  0  0
        or  h                 ; a přidat k vypočtenému mezivýsledku
        ld  h, a              ; H: 0  1  0 Y7 Y6 Y2 Y1 Y0
 
        ld  a, c              ; všech osm bitů Y-ové souřadnice
        rla
        rla                   ; A:  Y5 Y4 Y3 Y2 Y1 Y0 xx xx
        and %11100000         ; A:  Y5 Y4 Y3 0  0  0  0  0
        ld  l, a              ; část spodního bajtu adresy je vypočtena
 
        ld  a, b              ; všech osm bitů X-ové souřadnice
        rra
        rra
        rra                   ; rotace doprava -> 0  0  0  X7 X6 X5 X4
        and %00011111         ; A: 0  0  0  X7 X6 X5 X4 X3
        or  l                 ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3
        ld  l, a              ; spodní bajt adresy je vypočten
 
        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ů
        call draw_ascii_table     ; vykreslení 96 znaků
 
        ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat
        ld (hl), NOP_INSTRUCTION  ; zápis instrukce NOP namísto instrukce CPL
 
        call draw_ascii_table     ; vykreslení 96 znaků
        call draw_ascii_table     ; vykreslení 96 znaků
        ret                       ; návrat z podprogramu
 
 
draw_ascii_table:
        ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak)
        ;       
        ; 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 inverzního 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
 
loop2:
        ld   a,(hl)              ; načtení jednoho bajtu z masky
inv_instruction_adr:
        cpl                      ; negace hodnoty v akumulátoru
        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 loop2               ; 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 (mimochodem – délka je rovna 148 bajtům; žádný příklad se tedy prozatím ani nepřiblížil jednomu kilobajtu):

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
NOP_INSTRUCTION EQU 0000
                ORG 8000
8000:           label start
8000:CD5780     CALL 8057
8003:0600       LD B, 00
8005:0E00       LD C, 00
8007:           label loop
8007:CD2180     CALL 8021
800A:04         INC B
800B:20FA       JR NZ, 8007
800D:0C         INC C
800E:79         LD A, C
800F:FEC0       CP C0
8011:20F4       JR NZ, 8007
8013:           label finito
8013:18FE       JR 8013
8015:           label delay
8015:C5         PUSH BC
8016:0614       LD B, 14
8018:           label outer_loop
8018:0E00       LD C, 00
801A:           label inner_loop
801A:0D         DEC C
801B:20FD       JR NZ, 801A
801D:10F9       DJNZ 8018
801F:C1         POP BC
8020:C9         RET
8021:           label plot_over
8021:CD3A80     CALL 803A
8024:CD2B80     CALL 802B
8027:56         LD D, (HL)
8028:AA         XOR D
8029:77         LD (HL), A
802A:C9         RET
802B:           label calc_pixel_value
802B:C5         PUSH BC
802C:78         LD A, B
802D:E607       AND 07
802F:47         LD B, A
8030:3E80       LD A, 80
8032:2804       JR Z, 8038
8034:           label next_shift
8034:CB3F       SRL A
8036:10FC       DJNZ 8034
8038:           label end_calc
8038:C1         POP BC
8039:C9         RET
803A:           label calc_pixel_address
803A:79         LD A, C
803B:E607       AND 07
803D:F640       OR 40
803F:67         LD H, A
8040:79         LD A, C
8041:1F         RRA
8042:1F         RRA
8043:1F         RRA
8044:E618       AND 18
8046:B4         OR H
8047:67         LD H, A
8048:79         LD A, C
8049:17         RLA
804A:17         RLA
804B:E6E0       AND E0
804D:6F         LD L, A
804E:78         LD A, B
804F:1F         RRA
8050:1F         RRA
8051:1F         RRA
8052:E61F       AND 1F
8054:B5         OR L
8055:6F         LD L, A
8056:C9         RET
8057:           label fill_in_screen
8057:110040     LD DE, 4000
805A:CD6C80     CALL 806C
805D:CD6C80     CALL 806C
8060:218B80     LD HL, 808B
8063:3600       LD (HL), 00
8065:CD6C80     CALL 806C
8068:CD6C80     CALL 806C
806B:C9         RET
806C:           label draw_ascii_table
806C:3E20       LD A, 20
806E:           label next_char
806E:F5         PUSH AF
806F:CD7E80     CALL 807E
8072:3E20       LD A, 20
8074:CD7E80     CALL 807E
8077:F1         POP AF
8078:3C         INC A
8079:FE80       CP 80
807B:20F1       JR NZ, 806E
807D:C9         RET
807E:           label draw_char
807E:01003C     LD BC, 3C00
8081:61         LD H, C
8082:6F         LD L, A
8083:29         ADD HL, HL
8084:29         ADD HL, HL
8085:29         ADD HL, HL
8086:09         ADD HL, BC
8087:0608       LD B, 08
8089:4A         LD C, D
808A:           label loop2
808A:7E         LD A, (HL)
808B:           label inv_instruction_adr
808B:2F         CPL
808C:12         LD (DE), A
808D:2C         INC L
808E:14         INC D
808F:10F9       DJNZ 808A
8091:1C         INC E
8092:C8         RET Z
8093:51         LD D, C
8094:C9         RET
8095:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8094

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

V navazujícím článku se podrobněji zmíníme o problematice příznaků (flags), které jsou nastavovány některými instrukcemi a které lze využít jak v podmíněných skocích, tak i v dalších typech strojových instrukcí (rotacích atd). Prozatím jsme totiž používali pouze dva příznaky, a to konkrétně carry (příznak přenosu) a zero (příznak nulovosti), ovšem mikroprocesor Zilog Z80 nabízí i další příznaky, které lze využít jak ve výpočtech s hodnotami se znaménkem, tak i při realizaci některých optimalizací.

Taktéž si stručně přiblížíme FP formát, který byl použit v „kalkulačce“ umístěné v ROM a využívané zejména Sinclair BASICem. Operace s touto kalkulačkou byly postavené na zásobníku (stack) – viz též postarší seriál o programovacím jazyku Forth.

Cloud23

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 šesti článcích [1] [2], [3], [4], [5], [6], 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
 
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

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 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