Hlavní navigace

Vývoj pro ZX Spectrum: vlastní vykreslovací subrutiny potřetí

21. 3. 2023
Doba čtení: 50 minut

Sdílet

 Autor: Depositphotos
Dnes si ukážeme, jak v assembleru mikroprocesoru Zilog Z80 realizovat operaci typu PLOT. Jedná se o standardní příkaz Sinclair BASICu pro vykreslení jediného pixelu na obrazovku. Není to ovšem zcela triviální operace.

Obsah

1. Vývoj her a grafických i zvukových dem pro ZX Spectrum: vlastní vykreslovací subrutiny (3)

2. Vykreslení několika sad ASCII tabulek na obrazovku ZX Spectra

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

4. První varianta podprogramu typu PLOT pro vykreslení pixelu na obrazovku

5. Výpočet adresy bajtu s obsahem pixelu pro zadané souřadnice

6. Realizace výpočtu adresy

7. Pomalé vykreslení úsečky složené z „pixelů“

8. Kód pro vykreslení šikmé úsečky z jednotlivých blokových „pixelů“

9. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „blokových pixelů“

10. Výpočet masky pixelu

11. Realizace výpočtu masky pixelu

12. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „pravých pixelů“

13. Zjednodušení výpočtu masky pixelu

14. Úplný zdrojový kód upraveného demonstračního příkladu

15. Vykreslení pixelů na libovolném pozadí

16. Korektní zápis masky pixelu tak, aby nedošlo ke smazání pozadí

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

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. Vývoj her a grafických i zvukových dem pro ZX Spectrum: vlastní vykreslovací subrutiny (3)

V pořadí již šesté části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum se vrátíme k tématu, kterému jsme se začali věnovat již minule, a to konkrétně v patnácté kapitole. Řekli jsme si, že již relativně dobře umíme na obrazovku ZX Spectra vypsat znak či celý řetězec. Dokonce máme k dispozici několik metod, jak toho dosáhnout. Zejména můžeme využít subrutinu zapsanou přímo v ROM ZX Spectra (ta umí kromě dalších věcí rozeznávat řídicí kódy) nebo můžeme zavolat naši subrutinu, která je sice původně navržena pro tisk znaků v masce 8×8 znaků, ale relativně snadno ji lze upravit na různé výšky znaků (složitější bude úprava pro různé šířky).

Ovšem zajímavější (i když možná méně praktické) bude zjistit, jakým způsobem je možné realizovat operaci typu PLOT. Jedná se o standardní příkaz Sinclair BASICu sloužící pro vykreslení jediného pixelu (a navíc pro zapamatování souřadnice vykreslení pixelu pro další operace, například pr vykreslení úsečky). Aby byla situace nepatrně zajímavější, existuje tento příkaz v několika variantách:

Příkaz Stručný popis příkazu
PLOT x,y vykreslení pixelu na souřadnice [x,y] barvou inkoustu (ink)
PLOT INVERSE 1; x, y vykreslení pixelu barvou papíru (paper)
PLOT OVER 1; x, y negace barvy pixelu
PLOT INVERSE 1;OVER 1; pouze přesune grafický „kurzor“ na nové souřadnice

Při realizaci naší vlastní varianty příkazu PLOT budeme pochopitelně postupovat krok za krokem. Konkrétně si dnes ukážeme několik demonstračních příkladů, které budou postupně realizovat následující funkce (realizované formou podprogramů – subrutin):

  1. Subrutina pro vykreslení ASCII tabulky, kterou použijeme později pro vyplnění pozadí obrazovky, aby bylo jasně patrné, jak pracuje námi realizovaný příkaz PLOT.
  2. Výpočet adresy bajtu v obrazové paměti, kam se má pixel vykreslit.
  3. Vykreslení „širokého“ pixelu o šířce osmi obrazových pixelů.
  4. Výpočet bitové masky pixelu, protože každý pixel je zapsán jako jediný bit uložený společně s dalšími sedmi pixely v bajtu.
  5. Vykreslení „úzkého“ pixelu na korektní místo na obrazovce.
  6. Zajištění, že se nesmaže pozadí celé osmice pixelů, na tom místě na obrazovce, kam se provádí zápis.

Obrázek 1: Obchodní část hry Elite přepsaná do Sinclair BASICu. O vykreslení celé obrazovky se z velké části starají standardní grafické příkazy Sinclair BASICu.

2. Vykreslení několika sad ASCII tabulek na obrazovku ZX Spectra

Ještě předtím, než se budeme zabývat subrutinou pro vykreslení pixelu, si ukážeme program, který po svém spuštění vykreslí na obrazovku několik ASCII tabulek. Tento program budeme potřebovat proto, abychom zaplnili plochu pozadí nějakým vzorkem, na němž bude patrné, zda vykreslovací rutina PLOT pracuje korektně či nikoli. Výsledná obrazovka by měla vypadat následovně:

Obrázek 2: Několik ASCII tabulek, které vyplňují

Činnost celého programu je založena na podprogramu, který volá nám již známý podprogram draw_char, který vytiskne jeden znak a následně posune hodnotu uloženou v registrovém páru DE na adresu v obrazové paměti, kam se má vykreslit další znak. V jedné iteraci vykreslíme dvojici znaků – nějaký znak s kódem uloženým v počitadlu smyčky a mezeru:

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
Poznámka: povšimněte si, že hodnotu registru A, který obsahuje ASCII kód vykreslovaného znaku, musíme (společně s příznakovým registrem F) uložit na zásobník, protože obsah tohoto registru podprogram draw_char ničí.

Vykreslení několika ASCII tabulek přes celou obrazovku je již snadné, protože nám postačuje zavolat výše popsanou subrutinu draw_ascii_table čtyřikrát za sebou, přičemž nebudeme zasahovat do adresy uložené v registrovém páru DE:

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

Samotné tělo programu se tedy zkrátí na zavolání subrutiny fill_in_screen následované vstupem do nekonečné smyčky (aby se řízení nevrátilo zpět do BASICu):

        ; 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

3. Úplný zdrojový kód demonstračního příkladu pro tisk 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/50-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    ; vykreslení 96 znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        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
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
loop:
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld   (de),a              ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc  e
        ret  z                   ; D+=8,E=E+1=0
        ld   d, c
        ret                      ; D=D,E=E+1
 
end ENTRY_POINT

Podívejme se ještě na způsob překladu tohoto příkladu do strojového kódu. Jeho celková délka dosahuje šedesáti bajtů:

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
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
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:12         LD (DE), A
8035:2C         INC L
8036:14         INC D
8037:10FA       DJNZ 8033
8039:1C         INC E
803A:C8         RET Z
803B:51         LD D, C
803C:C9         RET
803D:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 803C

4. První varianta podprogramu typu PLOT pro vykreslení pixelu na obrazovku

Vraťme se k ústřednímu tématu dnešního článku – k implementaci podprogramu, který by měl být obdobou příkazu PLOT, tj. který by měl provést vykreslení jednoho pixelu na obrazovku. Připomeňme si, že rozlišení obrazovky ZX Spectra je 256×192 pixelů, což znamená, že x-ová i y-ová souřadnice pixelu musí být reprezentována osmibitovým číslem (což je oproti počítačům s rozlišením 320×xxx pixelů značné zjednodušení). X-ovou souřadnici budeme do postupně vznikajícího podprogramu předávat v registru B, y-ovou souřadnici pak v registru C.

Nejprimitivnější podoba podprogramu PLOT na základě obsahu pracovních registrů B a C (souřadnic pixelu) vypočítá adresu pixelu (resp. celé osmice pixelů) v obrazové paměti a následně na tuto adresu uloží konstantu 0×ff. To vlastně znamená, že nevykreslíme jediný pixel, ale osmici sousedních pixelů. Nějak však začít musíme :)

Podprogram bude vypadat následovně:

plot:
        ; první 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
        ld (hl), 0xff            ; zápis "něčeho" na adresu pixelu
        ret                      ; návrat z podprogramu

5. Výpočet adresy bajtu s obsahem pixelu pro zadané souřadnice

Výpočet adresy v obrazové paměti, na níž má být zápis proveden, je relativně komplikované, ovšem na druhou stranu se vyhneme nutnosti násobení atd. (popř. náhradě násobení za posuny a součty). Ve třetí části tohoto seriálu jsme si popsali organizaci obrazové paměti. Připomeňme si, že adresa každého bajtu obrazové paměti se skládá z několika bitových polí v tomto formátu (jedná se o 16bitovou adresu zapsanou formou jednotlivých bitů):

010 BB SSS RRR CCCCC
 
BB:    číslo bloku 0,1,2 (v bloku číslo 3 je atributová paměť)
SSS:   číslo řádky v jednom znaku, který je vysoký osm obrazových řádků
RRR:   pozice textového řádku v bloku. Každý blok je vysoký 64 obrazových řádků, což odpovídá osmi řádkům textovým
CCCCC: index sloupce bajtu v rozmezí 0..31, kde je uložena osmice sousedních pixelů

My ovšem nyní nepracujeme se znaky, ale s pixely. Schéma adresy se ovšem změní jen nepatrně (opět je adresa uvedena po jednotlivých bitech):

010 Y7 Y6 Y2 Y1 Y0 | Y5 Y3 Y3 X7 X6 X5 X4 X3

Povšimněte si, že nyní nejvyšší dva bity y-ové souřadnice uvádí číslo bloku. Naopak se v adrese vůbec nevyskytují bity X2, X1 a X0, což je logické, protože tyto tři bity specifikují index bitu v zapisovaném bajtu (viz navazující kapitoly). Našim úkolem je tedy realizovat tento algoritmus:

  1. Prvním vstupem jsou hodnoty X7 X6 X5 X4 X3 X3 X1 X0 uložené v registru B
  2. Druhým vstupem jsou hodnoty Y7 Y6 Y5 Y4 Y3 Y3 Y1 Y0 uložené v registru C
  3. Výstupem je 16bitová adresa 010 Y7 Y6 Y2 Y1 Y0 Y5 Y3 Y3 X7 X6 X5 X4 X3
  4. Vyšší bajt adresy 010 Y7 Y6 Y2 Y1 Y0 bude uložen do registru H
  5. Nižší bajt adresy Y5 Y3 Y3 X7 X6 X5 X4 X3 bude uložen do registru L

6. Realizace výpočtu adresy

Výše uvedený algoritmus je možné realizovat bitovými operacemi (and, or) a bitovými posuny (rra). Realizace celého algoritmu se „vejde“ do 29 bajtů (a to jsme neprováděli žádné optimalizace):

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

7. Pomalé vykreslení úsečky složené z „pixelů“

Aby bylo patrné, jak dobře či špatně se nám daří vypočítat adresu vykreslovaných pixelů, pokusíme se na obrazovku vykreslit šikmou úsečku (pod úhlem 45°). Úsečku přitom vykreslíme zpomaleně – mezi vykreslení každého dalšího pixelu bude vložena zpožďovací rutina. Vzhledem k relativně vysoké rychlosti mikroprocesoru Zilog Z80 využívá tato rutina dvojici vnořených programových smyček, každou s osmibitovým čítačem (jedna smyčka s osmibitovým čítačem by nestačila). Jedna z možných variant této zpožďovací smyčky může vypadat takto:

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
Poznámka: povšimněte si, že tato rutina na konci obnoví všechny použité registry, takže ji je možné volat kdekoli v kódu.

8. Kód pro vykreslení šikmé úsečky z jednotlivých blokových „pixelů“

Samotné vykreslení šikmé úsečky je již jednoduché, protože nám postačuje postupně modifikovat hodnoty pracovních registrů B a C, které jsou použity při volání podprogramu PLOT a které obsahují x-ovou a y-ovou souřadnici pixelu. Jak již víme, budeme vkládat mezi jednotlivá volání subrutiny plot zpožďovací smyčky:

        ; Vstupní bod celého programu
start:
        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

Výsledek bude vypadat zhruba následovně:

Obrázek 3: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 4: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 5: Postupné vykreslování úsečky z „blokových pixelů“.

Obrázek 6: Postupné vykreslování úsečky z „blokových pixelů“.

9. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „blokových pixelů“

Úplný zdrojový kód demonstračního příkladu popsaného v předchozích kapitolách je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/51-plot-block.asm a vypadá takto:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        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:
        ; první 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
        ld (hl), 0xff            ; zápis "něčeho" na adresu pixelu
        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
 
 
end ENTRY_POINT

Po překladu do strojového kódu získáme sekvenci instrukcí s délkou pouhých 65 bajtů:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:0600       LD B, 00
8002:0E00       LD C, 00
8004:           label loop
8004:CD1F80     CALL 801F
8007:CD1380     CALL 8013
800A:04         INC B
800B:0C         INC C
800C:78         LD A, B
800D:FEC0       CP C0
800F:20F3       JR NZ, 8004
8011:           label finito
8011:18FE       JR 8011
8013:           label delay
8013:C5         PUSH BC
8014:0614       LD B, 14
8016:           label outer_loop
8016:0E00       LD C, 00
8018:           label inner_loop
8018:0D         DEC C
8019:20FD       JR NZ, 8018
801B:10F9       DJNZ 8016
801D:C1         POP BC
801E:C9         RET
801F:           label plot
801F:CD2580     CALL 8025
8022:36FF       LD (HL), FF
8024:C9         RET
8025:           label calc_pixel_address
8025:79         LD A, C
8026:E607       AND 07
8028:F640       OR 40
802A:67         LD H, A
802B:79         LD A, C
802C:1F         RRA
802D:1F         RRA
802E:1F         RRA
802F:E618       AND 18
8031:B4         OR H
8032:67         LD H, A
8033:79         LD A, C
8034:17         RLA
8035:17         RLA
8036:E6E0       AND E0
8038:6F         LD L, A
8039:78         LD A, B
803A:1F         RRA
803B:1F         RRA
803C:1F         RRA
803D:E61F       AND 1F
803F:B5         OR L
8040:6F         LD L, A
8041:C9         RET
8042:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8041

10. Výpočet masky pixelu

Abychom nekreslili pouze „široké pixely“, ale dokázali skutečně vykreslit jednotlivý pixel, musíme nejdříve vypočítat masku pixelu. Jedná se o bajt, který obsahuje pouze jediný nastavený bit vypočtený na základě nejnižších tří bitů X-ové souřadnice (tyto tři bity jsme prozatím ignorovali). Výpočet je snadný za předpokladu, pokud si uvědomíme, jak má maska vypadat:

X2 X1 X0   Maska
0  0  0    10000000
0  0  1    01000000
0  1  0    00100000
0  1  1    00010000
1  0  0    00001000
1  0  1    00000100
1  1  0    00000010
1  1  1    00000001

Vidíme, že se vlastně jedná o posun bitové hodnoty 1000000 doprava o n bitů, přičemž n odpovídá spodním třem bitům x-ové souřadnice od 0 do 7.

Jakmile je maska vypočtena, můžeme ji (prozatím chybně) uložit do obrazové paměti takto:

plot:
        ; druhá 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 (hl), a               ; zápis hodnoty pixelu
        ret                      ; návrat z podprogramu
Poznámka: předpokládáme, že podprogram calc_pixel_value vrátí vypočtenou hodnotu v akumulátoru, tedy v registru A.

Výsledek bude vypadat zhruba následovně:

Obrázek 7: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 8: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 9: Postupné vykreslování úsečky z „úzkých pixelů“.

11. Realizace výpočtu masky pixelu

Podívejme se nyní na neoptimalizovaný program, který vypočte masku pixelu na základě plné osmibitové x-ové souřadnice, která je předána v registru B. Nejprve se z této souřadnice získají spodní tři bity a následně se použijí jako počitadlo smyčky, v níž je realizován posun bitové hodnoty 10000000 doprava:

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
        jr z, put_1              ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec
 
        ld b, a                  ; počitadlo smyčky
        ld a, %10000000          ; výchozí maska
next_shift:
        srl a                    ; posunout masku doprava
        djnz next_shift          ; 1x až 7x
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
put_1:
        ld a, %10000000          ; výchozí maska je současně i návratovou hodnotou
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
Poznámka: výsledný podprogram není nijak optimalizován a je otázkou, zda celý výpočet nenahradit za načtení příslušné hodnoty z tabulky – to si ostatně ukážeme později.

12. Úplný zdrojový kód demonstračního příkladu pro vykreslení úsečky z „pravých pixelů“

Úplný zdrojový kód demonstračního příkladu popsaného v předchozích dvou kapitolách je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/52-plot-pixel.asm a vypadá takto:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        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:
        ; druhá 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 (hl), a               ; zápis hodnoty pixelu
        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
        jr z, put_1              ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec
 
        ld b, a                  ; počitadlo smyčky
        ld a, %10000000          ; výchozí maska
next_shift:
        srl a                    ; posunout masku doprava
        djnz next_shift          ; 1x až 7x
        pop bc                   ; obnovit hodnotu v registru B
        ret                      ; návrat z podprogramu
put_1:
        ld a, %10000000          ; výchozí maska je současně i návratovou hodnotou
        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
 
 
end ENTRY_POINT

A pro úplnost si ukažme způsob překladu do strojového kódu, přičemž výsledek bude mít stále přijatelných 86 bajtů:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:0600       LD B, 00
8002:0E00       LD C, 00
8004:           label loop
8004:CD1F80     CALL 801F
8007:CD1380     CALL 8013
800A:04         INC B
800B:0C         INC C
800C:78         LD A, B
800D:FEC0       CP C0
800F:20F3       JR NZ, 8004
8011:           label finito
8011:18FE       JR 8011
8013:           label delay
8013:C5         PUSH BC
8014:0614       LD B, 14
8016:           label outer_loop
8016:0E00       LD C, 00
8018:           label inner_loop
8018:0D         DEC C
8019:20FD       JR NZ, 8018
801B:10F9       DJNZ 8016
801D:C1         POP BC
801E:C9         RET
801F:           label plot
801F:CD3A80     CALL 803A
8022:CD2780     CALL 8027
8025:77         LD (HL), A
8026:C9         RET
8027:           label calc_pixel_value
8027:C5         PUSH BC
8028:78         LD A, B
8029:E607       AND 07
802B:2809       JR Z, 8036
802D:47         LD B, A
802E:3E80       LD A, 80
8030:           label next_shift
8030:CB3F       SRL A
8032:10FC       DJNZ 8030
8034:C1         POP BC
8035:C9         RET
8036:           label put_1
8036:3E80       LD A, 80
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:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8056

13. Zjednodušení výpočtu masky pixelu

Způsob výpočtu masky pixelu, který jsme si ukázali v jedenácté kapitole, je možné různými způsoby zjednodušit. Jedna z možných variant zjednodušení spočívá v odstranění duplicitních instrukcí pop bc a ret a současně i naplnění masky hodnotou %10000000 pro nulové souřadnice X2, X1, X0. Postačuje nám nepatrně upravit programovou logiku, kdy počáteční hodnotu masky %10000000 vložíme do registru A a teprve poté testujeme, jestli nejsou hodnoty X2, X1 a X0 nulové – to nám Zilog Z80 skutečně umožňuje, protože instrukce ld nenastavují příznaky (na rozdíl od MOSu 6502, kde by tento postup nešel použít). Upravená subrutina pro výpočet masky pixelu vypadá následovně:

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

Výsledné chování programu se tím nijak nezmění:

Obrázek 10: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 11: Postupné vykreslování úsečky z „úzkých pixelů“.

Obrázek 12: Postupné vykreslování úsečky z „úzkých pixelů“.

14. Úplný zdrojový kód upraveného demonstračního příkladu

Úplný zdrojový kód upraveného demonstračního příkladu popsaného v předchozí kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/53-plot-pixel.asm a vypadá takto:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
        ; Vstupní bod celého programu
start:
        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:
        ; druhá 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 (hl), a               ; zápis hodnoty pixelu
        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
 
 
end ENTRY_POINT

Po překladu do strojového kódu snadno zjistíme, že jsme ušetřili čtyři bajty. To se sice na první pohled nezdá moc, ale jedná se o celá 4% celkové velikosti kódu:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:0600       LD B, 00
8002:0E00       LD C, 00
8004:           label loop
8004:CD1F80     CALL 801F
8007:CD1380     CALL 8013
800A:04         INC B
800B:0C         INC C
800C:78         LD A, B
800D:FEC0       CP C0
800F:20F3       JR NZ, 8004
8011:           label finito
8011:18FE       JR 8011
8013:           label delay
8013:C5         PUSH BC
8014:0614       LD B, 14
8016:           label outer_loop
8016:0E00       LD C, 00
8018:           label inner_loop
8018:0D         DEC C
8019:20FD       JR NZ, 8018
801B:10F9       DJNZ 8016
801D:C1         POP BC
801E:C9         RET
801F:           label plot
801F:CD3680     CALL 8036
8022:CD2780     CALL 8027
8025:77         LD (HL), A
8026:C9         RET
8027:           label calc_pixel_value
8027:C5         PUSH BC
8028:78         LD A, B
8029:E607       AND 07
802B:47         LD B, A
802C:3E80       LD A, 80
802E:2804       JR Z, 8034
8030:           label next_shift
8030:CB3F       SRL A
8032:10FC       DJNZ 8030
8034:           label end_calc
8034:C1         POP BC
8035:C9         RET
8036:           label calc_pixel_address
8036:79         LD A, C
8037:E607       AND 07
8039:F640       OR 40
803B:67         LD H, A
803C:79         LD A, C
803D:1F         RRA
803E:1F         RRA
803F:1F         RRA
8040:E618       AND 18
8042:B4         OR H
8043:67         LD H, A
8044:79         LD A, C
8045:17         RLA
8046:17         RLA
8047:E6E0       AND E0
8049:6F         LD L, A
804A:78         LD A, B
804B:1F         RRA
804C:1F         RRA
804D:1F         RRA
804E:E61F       AND 1F
8050:B5         OR L
8051:6F         LD L, A
8052:C9         RET
8053:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8052

15. Vykreslení pixelů na libovolném pozadí

Nyní si předchozí demonstrační příklad, který již (alespoň zdánlivě) dokáže vykreslit pixely na obrazovku korektním způsobem, nepatrně upravíme. Použijeme totiž podprogram fill_in_screen, kterým jsme se zabývali v první části dnešního článku, tak, aby se ještě před vykreslením úsečky z pixelů zaplnilo celé pozadí ASCII znaky. Celý program je složen ze série podprogramů, takže jeho úprava bude snadná:

        ; 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

Výsledek však ukáže, že jsme ještě stále nedošli ke kýženému výsledku, protože se vždy smaže překreslí celý blok osmi pixelů. Ve výsledku se tak jakoby smažou všechny znaky ležící na diagonále:

Obrázek 13: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 14: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 15: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Obrázek 16: Zápis jediného pixelu vede k překreslení osmice sousedících pixelů.

Pro úplnost si ukažme celý zdrojový kód takto upraveného demonstračního příkladu:

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:
        ; druhá 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 (hl), a               ; zápis hodnoty pixelu
        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ů
        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
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
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
 
end ENTRY_POINT

16. Korektní zápis masky pixelu tak, aby nedošlo ke smazání pozadí

Původní algoritmus příkazu PLOT nejprve zjistil adresu, na kterou se má zapsat bitová maska a následně tuto bitovou masku vypočítal. Následně se celá bitová maska uložila na vypočtenou adresu, což ovšem znamenalo, že se vždy nastavil (na barvu inkoustu) jediný pixel z celé osmice, zatímco ostatních sedm pixelů bylo smazáno na barvu papíru. Toto „vykreslení+smazání“ je realizováno instrukcí ld (hl), a:

plot:
        ; druhá 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 (hl), a               ; zápis hodnoty pixelu
        ret                      ; návrat z podprogramu

My ovšem musíme provést odlišnou operaci – vykreslení jediného pixelu, tj. zápis (resp. modifikaci) jediného bitu z celé osmice. Tuto operaci lze realizovat relativně snadno:

  1. Načtení původní hodnoty všech osmi bitů z obrazové paměti
  2. Aplikace vypočtené bitové masky operací or (ta v našem případě nastaví jediný bit)
  3. Uložení vypočtené nové hodnoty všech osmi bitů do obrazové paměti

Upravený kód bude vypadat takto:

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ýsledky již mluví za vše:

Obrázek 17: Nyní se pixely správně překreslují přes pozadí.

Obrázek 18: Nyní se pixely správně překreslují přes pozadí.

Obrázek 19: Nyní se pixely správně překreslují přes pozadí.

Obrázek 20: Nyní se pixely správně překreslují přes pozadí.

Poznámka: ve skutečnosti ještě budeme muset zajistit vykreslení pixelu barvou pozadí (papíru).

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

Úplný zdrojový kód opraveného demonstračního příkladu s úpravou popsanou v předchozí kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/55-plot-pixel.asm a vypadá takto:

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    ; vykreslení 96 znaků
        call draw_ascii_table    ; vykreslení 96 znaků
        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
        ;       
        ; vstupy:
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ld a, ' '                ; kód vykreslovaného znaku
next_char:
        push af                  ; uschovat akumulátor na zásobník
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, ' '                ; vykreslit za znakem mezeru
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        pop af                   ; obnovit akumulátor ze zásobníku
        inc a                    ; ASCII kód dalšího znaku
        cp  ' ' + 96             ; jsme již na konci ASCII tabulky?
        jr nz, next_char         ; ne? potom pokračujeme
        ret                      ; návrat z podprogramu
 
 
draw_char:
        ; Vytištění jednoho znaku na obrazovku
        ;
        ; vstupy:
        ; A - kód znaku pro vykreslení
        ; DE - adresa v obrazové paměti pro vykreslení znaku
        ;
        ; výstupy:
        ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
        ;
        ; změněné registry:
        ; všechny
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
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
 
end ENTRY_POINT

Ani takto rozsáhlý příklad nebude po překladu do strojového kódu nijak závratně objemný, protože bude mít délku pouhých 143 bajtů:

root_podpora

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
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
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:12         LD (DE), A
8088:2C         INC L
8089:14         INC D
808A:10FA       DJNZ 8086
808C:1C         INC E
808D:C8         RET Z
808E:51         LD D, C
808F:C9         RET
8090:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 808F

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 pěti článcích [1] [2], [3], [4], [5], 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
 
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

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

Byl pro vás článek přínosný?