Vývoj her a dem pro ZX Spectrum: vlastní vykreslovací subrutiny podruhé

9. 3. 2023
Doba čtení: 41 minut

Sdílet

 Autor: Depositphotos
Nejdříve dokončíme problematiku tisku znaků na obrazovku. Poté si ukážeme, jak je možné vytisknout celé řetězce a následně se zaměříme na emulaci BASICovského příkazu PLOT.

Obsah

1. Instrukce pro bitové posuny a rotace

2. Náhrada čtveřice operací rra za trojici operací rrca

3. Vylepšený výpočet adresy masky znaku uloženého v paměti ROM

4. Rutina pro vykreslení znaku, která vrátí adresu pro zápis dalšího znaku

5. Vykreslení znaku na libovolné místo na obrazovce

6. Rozbalení programových smyček

7. Realizace rozbalení programové smyčky

8. První pokus o vytištění celého řetězce

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

10. Kde vlastně došlo k chybě?

11. Uložení hodnot na zásobník; obnova hodnot ze zásobníku

12. Oprava podprogramu pro tisk řetězce

13. Jedná se o korektní opravu?

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

15. Realizace operace typu PLOT – vykreslení pixelu

16. Bitové operace prováděné na mikroprocesoru Zilog Z80

17. Operace pro otestování, nastavení a vynulování konkrétního bitu

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

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

20. Odkazy na Internetu

1. Instrukce pro bitové posuny a rotace

Instrukce mikroprocesoru Zilog Z80, které jsme si popsali v předchozích čtyřech článcích [1] [2], [3], [4], nám již dnes nebudou postačovat, neboť začneme při tisku do obrazové paměti provádět posuny a rotace dat. Na rozdíl od minimalisticky pojatého čipu MOS 6502 nabízí Z80 programátorům celou řadu instrukcí pro rotaci (přes osm bitů nebo přes devět bitů s carry) i pro aritmetický a bitový posun doleva a doprava. V tabulce pro úplnost uvádím i instrukci SLL, kterou lze použít, i když nebyla oficiálně zdokumentovaná:

Instrukce Popis prováděné operace
RLCA rotace akumulátoru doleva, sedmý bit do carry
RLA rotace akumulátoru i s carry doleva
RRCA rotace akumulátoru doleva, nultý bit do carry
RRA rotace akumulátoru i s carry doprava
   
RLC rotace registru doleva, sedmý bit do carry
RL rotace registru i s carry doleva
RRC rotace registru doleva, nultý bit do carry
RR rotace registru i s carry doprava
   
SLA aritmetický posun doleva (násobení dvěma), sedmý bit do carry
SLL* dtto, ovšem do nultého bitu se nasune jednička (nedokumentovaná instrukce)
SRA aritmetický posun doprava (dělení dvěma), nultý bit do carry
SRL logický posun doprava (do sedmého bitu se nasune nula)

Obrázek 1: Obrazovka vyplněná barevným textem vykresleným příkladem (ROM rutina si navíc vynutí pokračování vykreslování po stisku klávesy).

2. Náhrada čtveřice operací rra za trojici operací rrca

Připomeňme si nejdříve, jak vlastně vypadal program, který dokázal na určené místo na obrazovce v rastru 32×24 znaků vykreslit blok o velikosti 8×8 pixelů. Tento program nejdříve vypočítal adresu prvního zapisovaného bajtu s tím, že adresa bajtu na dalším obrazovém řádku je díky „podivné“ struktuře obrazové paměti ZX Spectra vlastně umístěna na adrese o 256 bajtů vyšší. A přechod o 256 bajtů výše se provádí až triviálně snadno – pouhou inkrementací hodnoty vyššího bajtu adresy (což je rychlá osmibitová operace).

Zdrojový kód tohoto programu vypadá následovně:

SCREEN_ADR      equ $4000
ENTRY_POINT     equ $8000
 
PATTERN         equ $ff
 
 
        org ENTRY_POINT
 
start:
 
        ld b, 0                 ; x-ová souřadnice
        ld c, 0                 ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 15                ; x-ová souřadnice
        ld c, 12                ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 2                 ; x-ová souřadnice
        ld c, 2                 ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 31                ; x-ová souřadnice
        ld c, 23                ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
finish:
        jr finish               ; žádný návrat do systému
 
 
calc_block_address:
        ; parametry:
        ; B - x-ová souřadnice (ve znacích, ne pixelech)
        ; C - y-ová souřadnice (ve znacích, ne pixelech)
        ;
        ; návratové hodnoty:
        ; HL - adresa pro zápis bloku
        ;
        ; vzor adresy:
        ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
        ld  a, c
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
        rra
        rra
        rra
        rra                   ; nyní jsou čísla řádků v horních třech bitech
        or  b                 ; připočítat x-ovou souřadnici
        ld  l, a              ; máme spodní bajt adresy
                              ; Y2 Y1 Y0 X4 X3 X2 X1 X0
 
        ld  a, c              ; y-ová souřadnice
        and %00011000         ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  h, a              ; máme horní bajt adresy
                              ; 0 1 0 Y5 Y4 0 0 0
        ret                   ; návrat z podprogramu
 
 
fill_block:
        ; parametry:
        ; A - pattern
        ; HL - adresa vykreslení bloku
        ld b, 8               ; počitadlo zapsaných bajtů
loop:
        ld (hl), PATTERN      ; zápis hodnoty na adresu (HL)
        inc h                 ; posun na definici dalšího obrazového řádku
        djnz loop             ; vnitřní smyčka: blok s osmi zápisy
        ret                   ; návrat z podprogramu
 
 
end ENTRY_POINT

Obrázek 2: Bloky vykreslené popisovaným demonstračním příkladem.

Zajímat nás nyní bude především sekvence instrukcí, která vysune tři bity y-ové souřadnice (tedy vlastně číslo obrazového řádku v rámci znaku vysokého osm pixelů) do nejvyšších třech bitů. Tuto operaci jsme realizovali maskou a následným čtyřnásobným posunem doprava přes carry (popř. pětinásobným posunem doleva):

ld  a, c
and %00000111         ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
rra
rra
rra
rra                   ; nyní jsou čísla řádků v horních třech bitech
                      ; Y2 Y1 Y0 0 0 0 0 0

Ve skutečnosti je možné tento bitový posun (což je vlastně rotace) o jednu instrukci zkrátit, když nebudeme rotovat přes carry, ale pouze v rámci jednoho bajtu (carry se sice nastavuje, ale to nás nemusí trápit):

ld  a, c
and %00000111         ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
rrca
rrca
rrca                  ; nyní jsou čísla řádků v horních třech bitech
                      ; Y2 Y1 Y0 0 0 0 0 0
Poznámka: díky čtenáři _dw za upozornění!

Výsledný program bude vypadat takto:

SCREEN_ADR      equ $4000
ENTRY_POINT     equ $8000
 
PATTERN         equ $ff
 
 
        org ENTRY_POINT
 
start:
 
        ld b, 0                 ; x-ová souřadnice
        ld c, 0                 ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 15                ; x-ová souřadnice
        ld c, 12                ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 2                 ; x-ová souřadnice
        ld c, 2                 ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
        ld b, 31                ; x-ová souřadnice
        ld c, 23                ; y-ová souřadnice
        call calc_block_address ; výpočet adresy
        ld a, PATTERN
        call fill_block         ; vykreslit blok
 
finish:
        jr finish               ; žádný návrat do systému
 
 
calc_block_address:
        ; parametry:
        ; B - x-ová souřadnice (ve znacích, ne pixelech)
        ; C - y-ová souřadnice (ve znacích, ne pixelech)
        ;
        ; návratové hodnoty:
        ; HL - adresa pro zápis bloku
        ;
        ; vzor adresy:
        ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
        ld  a, c
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
        rrca
        rrca
        rrca                  ; nyní jsou čísla řádků v horních třech bitech
        or  b                 ; připočítat x-ovou souřadnici
        ld  l, a              ; máme spodní bajt adresy
                              ; Y2 Y1 Y0 X4 X3 X2 X1 X0
 
        ld  a, c              ; y-ová souřadnice
        and %00011000         ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  h, a              ; máme horní bajt adresy
                              ; 0 1 0 Y5 Y4 0 0 0
        ret                   ; návrat z podprogramu
 
 
fill_block:
        ; parametry:
        ; A - pattern
        ; HL - adresa vykreslení bloku
        ld b, 8               ; počitadlo zapsaných bajtů
loop:
        ld (hl), PATTERN      ; zápis hodnoty na adresu (HL)
        inc h                 ; posun na definici dalšího obrazového řádku
        djnz loop             ; vnitřní smyčka: blok s osmi zápisy
        ret                   ; návrat z podprogramu
 
 
end ENTRY_POINT

Výsledek by měl být přitom stejný, jako tomu bylo u originálního příkladu:

Obrázek 3: Bloky vykreslené upraveným demonstračním příkladem.

Pro úplnost se podívejme na způsob překladu do strojového kódu:

SCREEN_ADR      EQU 4000
ENTRY_POINT     EQU 8000
PATTERN         EQU 00FF
                ORG 8000
8000:           label start
8000:0600       LD B, 00
8002:0E00       LD C, 00
8004:CD3280     CALL 8032
8007:3EFF       LD A, FF
8009:CD4180     CALL 8041
800C:060F       LD B, 0F
800E:0E0C       LD C, 0C
8010:CD3280     CALL 8032
8013:3EFF       LD A, FF
8015:CD4180     CALL 8041
8018:0602       LD B, 02
801A:0E02       LD C, 02
801C:CD3280     CALL 8032
801F:3EFF       LD A, FF
8021:CD4180     CALL 8041
8024:061F       LD B, 1F
8026:0E17       LD C, 17
8028:CD3280     CALL 8032
802B:3EFF       LD A, FF
802D:CD4180     CALL 8041
8030:           label finish
8030:18FE       JR 8030
8032:           label calc_block_address
8032:79         LD A, C
8033:E607       AND 07
8035:0F         RRCA
8036:0F         RRCA
8037:0F         RRCA
8038:B0         OR B
8039:6F         LD L, A
803A:79         LD A, C
803B:E618       AND 18
803D:F640       OR 40
803F:67         LD H, A
8040:C9         RET
8041:           label fill_block
8041:0608       LD B, 08
8043:           label loop
8043:36FF       LD (HL), FF
8045:24         INC H
8046:10FB       DJNZ 8043
8048:C9         RET
8049:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8048

3. Vylepšený výpočet adresy masky znaku uloženého v paměti ROM

Dalším kódem, který je možné optimalizovat, je kód pro výpočet adresy masky znaku uloženého v paměti ROM. Původní tvar výpočtu pro kód znaku zapsaného do akumulátoru A vypadal následovně:

draw_char:
        ld hl, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld b, 0
        ld c, a                  ; kód znaku je nyní ve dvojici BC
        sla c
        rl b
        sla c
        rl b
        sla c
        rl b                     ; vynásobení BC osmi
        add hl, bc               ; přičíst adresu k offsetu masky znaku

Povšimněte si, že jsme kód znaku nejdříve uložili do dvojice registrů BC a poté bitovými posuny a rotacemi vynásobili tuto hodnotu osmi. Výsledek byl přičten k bázové adrese masek znaků, která byla uložena do dvojice registrů HL.

I tento kód můžeme zkrátit a urychlit, a to tak, že prohodíme význam dvojic BC s HL. Bázová adresa bude nyní uložena ve dvojici BC, zatímco HL bude použit pro výpočet offsetu. Proč zrovna HL? Protože je možné bitové rotace a posuny nahradit šestnáctibitovým součtem (16bitové operace nejsou ortogonální, tj. nejsou dostupné pro všechny dvojice registrů):

draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
Poznámka: opět díky čtenáři _dw za upozornění!

Obrázek 4: Tisk znaků upraveným demonstračním příkladem.

Upravený zdrojový kód demonstračního příkladu vypadá následovně:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
start:
        ld de, SCREEN_ADR        ; adresa pro zápis
        ld a, 'A'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
        ld de, SCREEN_ADR+1      ; adresa pro zápis
        ld a, 'B'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
        ld de, SCREEN_ADR+128+31 ; adresa pro zápis
        ld a, '?'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
finish:
        ret                      ; ukončit program
 
draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
 
loop:
        ld    a,(hl)             ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        ret                      ; návrat z podprogramu
 
end ENTRY_POINT

Překlad do objektového kódu:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:110040     LD DE, 4000
8003:3E41       LD A, 41
8005:CD1980     CALL 8019
8008:110140     LD DE, 4001
800B:3E42       LD A, 42
800D:CD1980     CALL 8019
8010:119F40     LD DE, 409F
8013:3E3F       LD A, 3F
8015:CD1980     CALL 8019
8018:           label finish
8018:C9         RET
8019:           label draw_char
8019:01003C     LD BC, 3C00
801C:61         LD H, C
801D:6F         LD L, A
801E:29         ADD HL, HL
801F:29         ADD HL, HL
8020:29         ADD HL, HL
8021:09         ADD HL, BC
8022:0608       LD B, 08
8024:           label loop
8024:7E         LD A, (HL)
8025:12         LD (DE), A
8026:2C         INC L
8027:14         INC D
8028:10FA       DJNZ 8024
802A:C9         RET
802B:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 802A

4. Rutina pro vykreslení znaku, která vrátí adresu pro zápis dalšího znaku

Podprogram draw_char určený pro tisk jednoho znaku očekává, že v registrovém páru DE je uložena adresa do obrazové paměti, kam se má uložit první mikrořádek znaku (tedy prvních osm pixelů). Většinou budeme chtít tisknout více znaků za sebou, takže by bylo vhodné, aby rutina automaticky zvýšila adresu DE tak, aby bylo možné ihned začít tisknout další znak. Původní tvar podprogramu ovšem tento výpočet nedělal, protože se sice zvyšovala hodnota v registru D, ale takovým způsobem, že by se další znak vytisknul pod znak předchozí (navíc ne vždy, protože obrazovka je rozdělena do třech oblastí):

draw_char:
        ...
        ...
        ...
 
loop:
        ...
        ...
        ...
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        ret                      ; návrat z podprogramu

Můžeme ovšem provést relativně malé množství úprav, které změnu adresy DE na adresu dalšího znaku zajistí. Musíme si uvědomit, že další znak na stejném řádku začíná na adrese DE+1, takže by se smyčka dala upravit takto:

draw_char:
        ...
        ...
        ...
        ld c, d
 
loop:
        ...
        ...
        ...
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ld    d, c
        ret                      ; E=E+1

Musíme ovšem počítat i s tím, že na konci celého bloku je nutné se přesunout na blok další, tj. že hodnota D vypočtená uvnitř smyčky je vlastně korektní a pouze zvýšíme hodnotu v registru E (navíc nemusíme řešit přetečení do D, protože víme, že E je na konci bloku nulové). Úprava bude nyní následující:

draw_char:
        ...
        ...
        ...
        ld c, d
 
loop:
        ...
        ...
        ...
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ret   z                  ; D+=8,E=E+1=0
        ld    d, c
        ret                      ; D=D,E=E+1

Obrázek 5: Tisk tří znaků za sebou upraveným demonstračním příkladem.

Upravená varianta demonstračního příkladu vypadá následovně. Povšimněte si, že znaky A, B a ? vytiskneme za sebou bez úpravy adresy:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
start:
        ld de, SCREEN_ADR        ; adresa pro zápis
        ld a, 'A'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
        ld a, 'B'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
        ld a, '?'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
finish:
        ret                      ; ukončit program
 
draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
loop:
        ld    a,(hl)             ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ret   z                  ; D+=8,E=E+1=0
        ld    d, c
        ret                      ; D=D,E=E+1
 
end ENTRY_POINT

Způsob překladu z assembleru do strojového kódu:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:110040     LD DE, 4000
8003:3E41       LD A, 41
8005:CD1380     CALL 8013
8008:3E42       LD A, 42
800A:CD1380     CALL 8013
800D:3E3F       LD A, 3F
800F:CD1380     CALL 8013
8012:           label finish
8012:C9         RET
8013:           label draw_char
8013:01003C     LD BC, 3C00
8016:61         LD H, C
8017:6F         LD L, A
8018:29         ADD HL, HL
8019:29         ADD HL, HL
801A:29         ADD HL, HL
801B:09         ADD HL, BC
801C:0608       LD B, 08
801E:4A         LD C, D
801F:           label loop
801F:7E         LD A, (HL)
8020:12         LD (DE), A
8021:2C         INC L
8022:14         INC D
8023:10FA       DJNZ 801F
8025:1C         INC E
8026:C8         RET Z
8027:51         LD D, C
8028:C9         RET
8029:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8028

5. Vykreslení znaku na libovolné místo na obrazovce

V této chvíli již máme k dispozici programový kód pro umístění a vykreslení bloku kamkoli na obrazovku a současně umíme vykreslit znak, jehož maska je přečtena z ROM. Tyto dvě znalosti je nutné zkombinovat a vytvořit rutinu pro tisk znaků popř. (později) řetězců kamkoli na obrazovku. Postup je vlastně triviální, protože potřebujeme znát a využít pouze trojici hodnot: kód znaku, x-ovou souřadnici 0..31 a y-ovou souřadnici 0..23. Ze souřadnic vypočteme adresu znaku na obrazovce a poté znak vykreslíme:

ld b, 15                 ; x-ová souřadnice
ld c, 12                 ; y-ová souřadnice
call calc_char_address   ; výpočet adresy
ld a, 'A'                ; kód vykreslovaného znaku
call draw_char           ; zavolat subrutinu pro vykreslení znaku

Obrázek 6: Trojice znaků vykreslená na různá místa na obrazovce.

Upravený zdrojový kód demonstračního příkladu bude nyní vypadat následovně:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
start:
        ld b, 15                 ; x-ová souřadnice
        ld c, 12                 ; y-ová souřadnice
        call calc_char_address   ; výpočet adresy
        ld a, 'A'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
        ld a, 'B'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
        ld b, 31                 ; x-ová souřadnice
        ld c, 23                 ; y-ová souřadnice
        call calc_char_address   ; výpočet adresy
        ld a, '?'                ; kód vykreslovaného znaku
        call draw_char           ; zavolat subrutinu pro vykreslení znaku
 
finish:
        jr finish               ; žádný návrat do systému
 
 
calc_char_address:
        ; parametry:
        ; B - x-ová souřadnice (ve znacích, ne pixelech)
        ; C - y-ová souřadnice (ve znacích, ne pixelech)
        ;
        ; návratové hodnoty:
        ; DE - adresa pro zápis bloku
        ;
        ; vzor adresy:
        ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
        ld  a, c
        and %00000111         ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
        rrca
        rrca
        rrca                  ; nyní jsou čísla řádků v horních třech bitech
        or  b                 ; připočítat x-ovou souřadnici
        ld  e, a              ; máme spodní bajt adresy
                              ; Y2 Y1 Y0 X4 X3 X2 X1 X0
 
        ld  a, c              ; y-ová souřadnice
        and %00011000         ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
        or  %01000000         ; "posun" do obrazové paměti (na 0x4000)
        ld  d, a              ; máme horní bajt adresy
                              ; 0 1 0 Y5 Y4 0 0 0
        ret                   ; návrat z podprogramu
 
 
draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
loop:
        ld    a,(hl)             ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ret   z                  ; D+=8,E=E+1=0
        ld    d, c
        ret                      ; D=D,E=E+1
 
end ENTRY_POINT

Po překladu získáme strojový kód o velikosti celých 67 bajtů:

SCREEN_ADR      EQU 4000
CHAR_ADR        EQU 3C00
ENTRY_POINT     EQU 8000
                ORG 8000
8000:           label start
8000:060F       LD B, 0F
8002:0E0C       LD C, 0C
8004:CD1F80     CALL 801F
8007:3E41       LD A, 41
8009:CD2E80     CALL 802E
800C:3E42       LD A, 42
800E:CD2E80     CALL 802E
8011:061F       LD B, 1F
8013:0E17       LD C, 17
8015:CD1F80     CALL 801F
8018:3E3F       LD A, 3F
801A:CD2E80     CALL 802E
801D:           label finish
801D:18FE       JR 801D
801F:           label calc_char_address
801F:79         LD A, C
8020:E607       AND 07
8022:0F         RRCA
8023:0F         RRCA
8024:0F         RRCA
8025:B0         OR B
8026:5F         LD E, A
8027:79         LD A, C
8028:E618       AND 18
802A:F640       OR 40
802C:57         LD D, A
802D:C9         RET
802E:           label draw_char
802E:01003C     LD BC, 3C00
8031:61         LD H, C
8032:6F         LD L, A
8033:29         ADD HL, HL
8034:29         ADD HL, HL
8035:29         ADD HL, HL
8036:09         ADD HL, BC
8037:0608       LD B, 08
8039:4A         LD C, D
803A:           label loop
803A:7E         LD A, (HL)
803B:12         LD (DE), A
803C:2C         INC L
803D:14         INC D
803E:10FA       DJNZ 803A
8040:1C         INC E
8041:C8         RET Z
8042:51         LD D, C
8043:C9         RET
8044:           END 8000
Emiting TAP basic loader
Emiting TAP from 8000 to 8043

6. Rozbalení programových smyček

Ještě jednou se podívejme na tu část vykreslovací rutiny, která postupně načte osm masek znaku a vykreslí je, tj. přesune je do obrazové paměti:

        ld b, 8                  ; počitadlo zapsaných bajtů
 
loop:
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy

Můžeme zde vidět, že se jedná o programovou smyčku, která se bude opakovat osmkrát. Samotná obsluha smyčky (tj. instrukce, které jsou nutné pro její provedení) je realizována jednou osmibitovou instrukcí ld a osmkrát volanou instrukcí djnz. Celkem tedy obsluha smyčky vyžaduje relativně velký počet strojových cyklů:

Instrukce Opakování Počet cyklů na instrukci Celkem cyklů
ld b, 8 7 7
djnz loop pro b>1 13 91
djnz loop pro b==1 8 8

Celkem tedy i prázdná programová smyčka opakovaná osmkrát vyžaduje 7+91+8=106 strojových cyklů!

7. Realizace rozbalení programové smyčky

Za cenu poněkud větší spotřeby operační paměti můžeme smyčku ručně (nebo s využitím makra) rozbalit, a to tak, že jednoduše sekvenci instrukcí osmkrát zopakujeme. Dosáhneme tak vyšší rychlosti vykreslování (konkrétně ušetříme 106 strojových cyklů), ovšem za cenu větší spotřeby RAM, což je pro osmibitové stroje zcela typické dilema, které je nutné řešit prakticky neustále:

        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
 
        ld   a,(hl)              ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  d                   ; posun na definici dalšího obrazového řádku

Všechny výše uvedené instrukce jsou jednobajtové, takže rozbalení smyčky zabere 4×7+3=31 bajtů. To může být relativně dobrá cena, kterou zaplatíme za urychlení vykreslování.

Obrázek 7: Trojice znaků vykreslená na různá místa na obrazovce rozbalenou smyčkou.

8. První pokus o vytištění celého řetězce

Ve druhé části dnešního článku si ukážeme, jak můžeme upravený podprogram pro vytištění znaku použít pro vytištění celého řetězce. Využijeme přitom toho, že podprogram správně spočítá adresu dalšího znaku, takže teoreticky by mohlo být vytištění řetězce (speciálně ASCII řetězce ukončeného nulou) realizováno velmi jednoduchým způsobem:

print_string:
        ld   a, (hl)             ; načíst kód znaku z řetězce
        and  a                   ; test na kód znak s kódem 0
        ret  Z                   ; ukončit podprogram na konci řetězce
        call draw_char           ; tisk jednoho znaku
        inc  hl                  ; přechod na další znak
        jr   print_string        ; na další znak

Tento podprogram vyžaduje, aby při jeho volání byla v dvojici registrů HL uložena adresa řetězce, který budeme chtít vytisknout. Dále podprogram vyžaduje uložení adresy prvního znaku v obrazové paměti v registrovém páru DE (tuto hodnotu nám připraví podprogram calc_char_address.

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

Podívejme se nyní na úplný zdrojový kód takto upraveného a rozšířeného demonstračního příkladu. Jedná se sice o poněkud delší kód, který by však stále měl být relativně snadno pochopitelný:

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
start:
        ld b, 15                 ; x-ová souřadnice
        ld c, 12                 ; y-ová souřadnice
        call calc_char_address   ; výpočet adresy
        ld   hl, TEXT            ; adresa prvního znaku v řetězci
        call print_string        ; tisk celého řetězce
 
finish:
        jr finish                ; žádný návrat do systému
 
 
calc_char_address:
        ; parametry:
        ; B - x-ová souřadnice (ve znacích, ne pixelech)
        ; C - y-ová souřadnice (ve znacích, ne pixelech)
        ;
        ; návratové hodnoty:
        ; DE - adresa pro zápis bloku
        ;
        ; vzor adresy:
        ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
        ld  a, c
        and %00000111            ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
        rrca
        rrca
        rrca                     ; nyní jsou čísla řádků v horních třech bitech
        or  b                    ; připočítat x-ovou souřadnici
        ld  e, a                 ; máme spodní bajt adresy
                                 ; Y2 Y1 Y0 X4 X3 X2 X1 X0
 
        ld  a, c                 ; y-ová souřadnice
        and %00011000            ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
        or  %01000000            ; "posun" do obrazové paměti (na 0x4000)
        ld  d, a                 ; máme horní bajt adresy
                                 ; 0 1 0 Y5 Y4 0 0 0
        ret                      ; návrat z podprogramu
 
 
print_string:
        ld   a, (hl)             ; načíst kód znaku z řetězce
        and  a                   ; test na kód znak s kódem 0
        ret  Z                   ; ukončit podprogram na konci řetězce
        call draw_char           ; tisk jednoho znaku
        inc  hl                  ; přechod na další znak
        jr   print_string        ; na další znak
 
draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
loop:
        ld    a,(hl)             ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ret   z                  ; D+=8,E=E+1=0
        ld    d, c
        ret                      ; D=D,E=E+1
 
; nulou ukončený řetězec
TEXT:   db "Hello, Speccy!", 0
 
end ENTRY_POINT

Po jeho překladu a spuštění se však namísto očekávané zprávy „Hello, Speccy!“ objeví chybný text:

Obrázek 8: Nekorektní text zobrazený tímto demonstračním příkladem.

Poznámka: velká výhoda ZX Spectra spočívá ve faktu, že prakticky všichni by měli vidět stejné chyby!

10. Kde vlastně došlo k chybě?

Program uvedený v předchozí kapitole sice vykreslil špatný řetězec, ovšem aspoň skončil :-), vytiskl neplatný řetězec na správné místo na obrazovce a navíc je první znak korektní („H“). To může znamenat, že chyba se nachází v té části programu, který počítá adresy znaků v řetězci. Adresa znaku se nachází ve dvojici registrů HL, jejíž hodnota se vy smyčce postupně zvyšuje (a to korektně):

print_string:
        ld   a, (hl)             ; načíst kód znaku z řetězce
        and  a                   ; test na kód znak s kódem 0
        ret  Z                   ; ukončit podprogram na konci řetězce
        call draw_char           ; tisk jednoho znaku
        inc  hl                  ; přechod na další znak
        jr   print_string        ; na další znak

Problém tedy musíme hledat přímo v podprogramu draw_char. A skutečně – zde se s HL skutečně manipuluje:

draw_char:
        ...
        ...
        ...
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
        ...
        ...
        ...

Chybu jsme tedy našli – volaný podprogram nám přepisuje obsah registrů. Nyní se ji pokusíme nějakým způsobem opravit.

11. Uložení hodnot na zásobník; obnova hodnot ze zásobníku

Ještě předtím, než si ukážeme, jakým způsobem je možné program z předchozí kapitoly opravit, si popíšeme strojové instrukce sloužící pro uložení hodnot na zásobník a instrukce pro následnou obnovu hodnot ze zásobníku. Zásobníkem je v kontextu mikroprocesoru Zilog Z80 myšlena oblast operační paměti, do níž se data (většinou) ukládají pod adresu uloženou ve speciálním 16bitovém registru SP, přičemž při uložení hodnot se adresa v SP sníží a při obnově hodnot (načtení ze zásobníku) naopak zvýší. Zásobník tedy roste směrem dolů. Zajímavostí mikroprocesorů Z80 (a 8080) je to, že se na zásobník vždy ukládají dvě osmibitové hodnoty, což je buď návratová adresa nebo dvojice osmibitových registrů. K dispozici jsou následující instrukce, které nějakým způsobem manipulují s obsahem zásobníku:

Instrukce Délka Stručný popis
push BC 1 uložení registrového páru BC na zásobník
push DE 1 uložení registrového páru DE na zásobník
push HL 1 uložení registrového páru HL na zásobník
push AF 1 uložení akumulátoru a příznakového registru na zásobník
push IX 2 uložení index registru IX na zásobník
push IY 2 uložení index registru IY na zásobník
   
pop BC 1 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru BC
pop DE 1 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru DE
pop HL 1 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do registrového páru HL
pop AF 1 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do akumulátoru a příznakového registru
pop IX 2 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do index registru IX
pop IY 2 vyjmutí 16bitové hodnoty ze zásobníku s jejím uložením do index registru IY
   
call adresa 3 volání podprogramu (uložení návratové adresy na zásobník)
call příznak, adresa 3 volání podprogramu při splnění podmínky (uložení návratové adresy na zásobník)
rst x 1 volání podprogramu, zkrácená verze pro vybrané adresy (uložení návratové adresy na zásobník)
ret 1 návrat z podprogramu (přečtení návratové adresy ze zásobníku)
ret příznak 1 návrat z podprogramu při splnění podmínky (přečtení návratové adresy ze zásobníku)
retn 2 návrat z nemaskovatelného přerušení (přečtení návratové adresy ze zásobníku)
reti 2 návrat z maskovatelného přerušení (přečtení návratové adresy ze zásobníku)
Poznámka: povšimněte si, že instrukce pracující s index registry IX a IY mají délku dvou bajtů, protože před operačním kódem instrukce je uveden jednobajtový prefix.

12. Oprava podprogramu pro tisk řetězce

Podprogram pro tisk řetězce můžeme opravit velmi snadno. Víme totiž, že se v podprogramu draw_char, který se postupně volá pro každý tisknutý znak, mj. „ničí“ obsah registrového páru HL, který ovšem potřebujeme, protože obsahuje adresu právě zpracovávaného znaku. Celá oprava tedy bude spočívat v tom, že obsah tohoto registrového páru před voláním draw_char uložíme na zásobník a ihned po návratu z podprogramu obsah registrového páru obnovíme, takže bude použitelný pro načtení dalšího znaku v řetězci, který se má vytisknout. Oprava může vypadat následovně:

print_string:
        ld   a, (hl)             ; načíst kód znaku z řetězce
        and  a                   ; test na kód znak s kódem 0
        ret  Z                   ; ukončit podprogram na konci řetězce
        push hl                  ; uschovat HL na zásobník
        call draw_char           ; tisk jednoho znaku
        pop  hl                  ; obnovit obsah HL ze zásobníku
        inc  hl                  ; přechod na další znak
        jr   print_string        ; na další znak

S aplikací této opravy by měl výpis řetězce dopadnout následovně:

Obrázek 9: Výpis řetězce na obrazovku – nyní je vše korektní.

13. Jedná se o korektní opravu?

Na tomto místě je vhodné si položit otázku, jestli je oprava korektní, a to jak z hlediska činnosti programu, tak i z hlediska jeho čitelnosti resp. „očekávatelnosti“ chování. Program jsme evidentně opravili, protože tiskne správný řetězec. Ovšem při volání podprogramu draw_char si neustále musíme dávat pozor na uložení a opětovné obnovení obsahu HL. Z tohoto pohledu by mohlo být lepší, aby se instrukce push hl a pop hl vložily přímo do podprogramu draw_char (popř. by se mohl podprogram upravit do takové míry, aby nemodifikoval žádné registry, v nichž nebudou předány návratové hodnoty).

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

Pro úplnost se nyní podívejme na to, jak vlastně vypadá opravená varianta demonstračního příkladu pro výpis řetězce na libovolné místo na obrazovce (opět počítáno v „textovém“ rastru 32×24 znaků):

SCREEN_ADR      equ $4000
CHAR_ADR        equ $3c00
ENTRY_POINT     equ $8000
 
        org ENTRY_POINT
 
start:
        ld b, 15                 ; x-ová souřadnice
        ld c, 12                 ; y-ová souřadnice
        call calc_char_address   ; výpočet adresy
        ld   hl, TEXT            ; adresa prvního znaku v řetězci
        call print_string        ; tisk celého řetězce
 
finish:
        jr finish                ; žádný návrat do systému
 
 
calc_char_address:
        ; parametry:
        ; B - x-ová souřadnice (ve znacích, ne pixelech)
        ; C - y-ová souřadnice (ve znacích, ne pixelech)
        ;
        ; návratové hodnoty:
        ; DE - adresa pro zápis bloku
        ;
        ; vzor adresy:
        ; 0 1 0 Y4 Y3 0 0 0 | Y2 Y1 Y0 X4 X3 X2 X1 X0
        ld  a, c
        and %00000111            ; pouze spodní tři bity y-ové souřadnice (řádky 0..7)
        rrca
        rrca
        rrca                     ; nyní jsou čísla řádků v horních třech bitech
        or  b                    ; připočítat x-ovou souřadnici
        ld  e, a                 ; máme spodní bajt adresy
                                 ; Y2 Y1 Y0 X4 X3 X2 X1 X0
 
        ld  a, c                 ; y-ová souřadnice
        and %00011000            ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány)
        or  %01000000            ; "posun" do obrazové paměti (na 0x4000)
        ld  d, a                 ; máme horní bajt adresy
                                 ; 0 1 0 Y5 Y4 0 0 0
        ret                      ; návrat z podprogramu
 
 
print_string:
        ld   a, (hl)             ; načíst kód znaku z řetězce
        and  a                   ; test na kód znak s kódem 0
        ret  Z                   ; ukončit podprogram na konci řetězce
        push hl                  ; uschovat HL na zásobník
        call draw_char           ; tisk jednoho znaku
        pop  hl                  ; obnovit obsah HL ze zásobníku
        inc  hl                  ; přechod na další znak
        jr   print_string        ; na další znak
 
draw_char:
        ld bc, CHAR_ADR          ; adresa, od níž začínají masky znaků
        ld h, c                  ; C je nulové, protože CHAR_ADR=0x3c00
        ld l, a                  ; kód znaku je nyní ve dvojici HL
 
        add  hl, hl              ; 2x
        add  hl, hl              ; 4x
        add  hl, hl              ; 8x
        add  hl, bc              ; přičíst bázovou adresu masek znaků
 
        ld b, 8                  ; počitadlo zapsaných bajtů
        ld c, d
 
loop:
        ld    a,(hl)             ; načtení jednoho bajtu z masky
        ld  (de),a               ; zápis hodnoty na adresu (DE)
        inc  l                   ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
        inc  d                   ; posun na definici dalšího obrazového řádku
        djnz loop                ; vnitřní smyčka: blok s osmi zápisy
        inc   e
        ret   z                  ; D+=8,E=E+1=0
        ld    d, c
        ret                      ; D=D,E=E+1
 
; nulou ukončený řetězec
TEXT:   db "Hello, Speccy!", 0
 
end ENTRY_POINT

15. Realizace operace typu PLOT – vykreslení pixelu

Nyní již relativně dobře umíme na obrazovku ZX Spectra vypsat řetězec. Dokonce máme k dispozici několik metod. Můžeme využít subrutinu zapsanou přímo v ROM ZX Spectra (ta umí kromě dalších věcí rozeznávat řídicí kódy) nebo můžeme zavolat naši subrutinu, která je sice původně navržena pro tisk znaků v masce 8×8 znaků, ale relativně snadno ji lze upravit na různé výšky znaků. Ovšem zajímavější (i když možná méně praktické) bude zjistit, jakým způsobem je možné realizovat operaci typu PLOT. Jedná se o standardní příklad Sinclair BASICu sloužící pro vykreslení jediného pixelu. Aby byla situace nepatrně zajímavější, existuje tento příkaz v několika variantách:

Příkaz Stručný popis příkazu
PLOT x,y vykreslení pixelu na souřadnice [x,y] barvou inkoustu
PLOT INVERSE 1; x, y vykreslení pixelu barvou papíru
PLOT OVER 1; x, y negace harvy pixelu
PLOT INVERSE 1;OVER 1; pouze přesune grafický „kurzor“ na nové souřadnice
Poznámka: počátek souřadnicového systému je v levém dolním rohu, přesněji řečeno na začátku řádku 175, protože spodní dva textové řádky (2×8 obrazových řádků) jsou vyhrazeny pro BASIC. Naše programy budou mít naproti tomu počátek v levém horním rohu.

Obrázek 10: Slavná hra Elite používá velké množství grafických entit: body, úsečky, kružnice, vyplněné bloky a znaky.

16. Bitové operace prováděné na mikroprocesoru Zilog Z80

Vykreslování na úrovni jednotlivých pixelů bude vzhledem ke struktuře obrazové paměti ZX Spectra vyžadovat provádění bitových operací. Jaké operace ovšem vůbec máme k dispozici? To nám napoví následující tabulka, v níž jsou příslušné instrukce vypsány:

Instrukce Stručný popis instrukce
AND r bitový součin akumulátoru s osmibitovým registrem
AND n bitový součin akumulátoru s osmibitovou konstantou
AND (HL) bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese HL
AND (IX+d) bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d
AND (IY+d) bitový součin akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d
   
OR r bitový součet akumulátoru s osmibitovým registrem
OR n bitový součet akumulátoru s osmibitovou konstantou
OR (HL) bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese HL
OR (IX+d) bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d
OR (IY+d) bitový součet akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d
   
XOR r bitová nonekvivalence akumulátoru s osmibitovým registrem
XOR n bitová nonekvivalence akumulátoru s osmibitovou konstantou
XOR (HL) bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese HL
XOR (IX+d) bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese IX+d
XOR (IY+d) bitová nonekvivalence akumulátoru s osmibitovou hodnotou uloženou na adrese IY+d
   
CPL bitová negace všech bitů v akumulátoru

17. Operace pro otestování, nastavení a vynulování konkrétního bitu

Instrukční soubor osmibitového mikroprocesoru Zilog Z80 obsahuje přibližně 1200 operačních kódů instrukcí, přičemž velkou část z tohoto množství zaberou instrukce BIT, SET a RES. Tyto instrukce slouží pro test jednoho bitu (výsledek je ukládán do příznaku zero), nastavení konkrétního bitu na jedničku nebo vynulování zvoleného bitu. Důvodem, proč tyto instrukce zabírají velké množství operačních kódů je fakt, že v operačním kódu instrukce je zakódován i index testovaného, nastavovaného či nulovaného bitu, tj. každá instrukce vlastně existuje v osmi variantách, což je dále násobeno několika adresovacími režimy (může se použít libovolný registr, adresa v HL, navíc je někdy nutné uložit i offset d atd.):

Instrukce Stručný popis instrukce
BIT b,r test bitu b uloženého v některém osmibitovém registru
BIT b,(HL) test bitu b uloženého na adrese HL
BIT b,(IX+d) test bitu b uloženého na adrese IX+d
BIT b,(IY+d) test bitu b uloženého na adrese IY+d
   
SET b,r nastavení bitu b uloženého v některém osmibitovém registru
SET b,(HL) nastavení bitu b uloženého na adrese HL
SET b,(IX+d) nastavení bitu b uloženého na adrese IX+d
SET b,(IY+d) nastavení bitu b uloženého na adrese IY+d
   
RES b,r vynulování bitu b uloženého v některém osmibitovém registru
RES b,(HL) vynulování bitu b uloženého na adrese HL
RES b,(IX+d) vynulování bitu b uloženého na adrese IX+d
RES b,(IY+d) vynulování bitu b uloženého na adrese IY+d
Poznámka: povšimněte si určité ortogonality instrukční sady – adresovací režimy odpovídají výše uvedeným logickým operacím.

Tyto instrukce mají většinou prefix 0×CB, 0×DD 0×CB nebo 0×FD 0×CB. Jejich délka je dva či čtyři bajty.

ict ve školství 24

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

Výše uvedené demonstrační příklady i příklady, které již byly popsány v předchozích čtyřech článcích [1] [2], [3], [4], je možné přeložit s využitím souboru Makefile, jehož aktuální verze vypadá následovně (pro překlad a slinkování je použit assembler Pasmo):

ASSEMBLER := pasmo
 
all: 01.tap 02.tap 03.tap 04.tap 05.tap 06.tap 07.tap 08.tap 09.tap 10.tap \
    11.tap 12.tap 13.tap 14.tap 15.tap 16.tap 17.tap 18.tap 19.tap 20.tap \
    21.tap 22.tap 23.tap 24.tap 25.tap 26.tap 27.tap 28.tap 29.tap 30.tap \
    31.tap 32.tap 33.tap 34.tap 35.tap 36.tap 37.tap 38.tap 39.tap 40.tap \
    41.tap 42.tap 43.tap 44.tap 45.tap 46.tap 47.tap 48.tap 49.tap
 
clean:
        rm -f *.tap
 
.PHONY: all clean
 
 
01.tap: 01-color-attribute.asm
        $(ASSEMBLER) -v -d --tap $< $@ > 01-color-attribute.lst
 
02.tap: 02-blinking-attribute.asm
        $(ASSEMBLER) -v -d --tap $< $@ > 02-blinking-attribute.lst
 
03.tap: 03-symbolic-names.asm
        $(ASSEMBLER) -v -d --tap $< $@ > 03-symbolic-names.lst
 
04.tap: 04-operators.asm
        $(ASSEMBLER) -v -d --tap $< $@ > 04-operators.lst
 
05.tap: 05-better-symbols.asm
        $(ASSEMBLER) -v -d --tap $< $@ > 05-better-symbols.lst
 
06.tap: 06-tapbas-v1.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 06-tapbas-v1.lst
 
07.tap: 07-tapbas-v2.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 07-tapbas-v2.lst
 
08.tap: 08-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 08-loop.lst
 
09.tap: 09-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 09-loop.lst
 
10.tap: 10-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 10-loop.lst
 
11.tap: 11-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 11-loop.lst
 
12.tap: 12-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 12-loop.lst
 
13.tap: 13-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 13-loop.lst
 
14.tap: 14-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 14-loop.lst
 
15.tap: 15-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 15-loop.lst
 
16.tap: 16-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 16-loop.lst
 
17.tap: 17-loop.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 17-loop.lst
 
18.tap: 18-cls.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 18-cls.lst
 
19.tap: 19-print-char-call.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 19-print-char-call.lst
 
20.tap: 20-print-char-rst.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 20-print-char-rst.lst
 
21.tap: 21-print-char.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 21-print-char.lst
 
22.tap: 22-print-all-chars.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 22-print-all-chars.lst
 
23.tap: 23-print-all-chars.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 23-print-all-chars.lst
 
24.tap: 24-change-color.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 24-change-color.lst
 
25.tap: 25-change-flash.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 25-change-flash.lst
 
26.tap: 26-print-at.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 26-print-at.lst
 
27.tap: 27-print-string.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 27-print-string.lst
 
28.tap: 28-print-string.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 28-print-string.lst
 
29.tap: 29-print-colorized-string.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 29-print-colorized-string.lst
 
30.tap: 30-print-string-ROM.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 30-print-string-ROM.lst
 
31.tap: 31-attributes.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 31-attributes.lst
 
32.tap: 32-fill-in-vram.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 32-fill-in-vram.lst
 
33.tap: 33-fill-in-vram-no-ret.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 33-fill-in-vram-no-ret.lst
 
34.tap: 34-fill-in-vram-pattern.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 34-fill-in-vram-pattern.lst
 
35.tap: 35-slow-fill-in-vram.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 35-slow-fill-in-vram.lst
 
36.tap: 36-slow-fill-in-vram-no-ret.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 36-slow-fill-in-vram-no-ret.lst
 
37.tap: 37-fill-block.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 37-fill-block.lst
 
38.tap: 38-fill-block-with-pattern.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 38-fill-block-with-pattern.lst
 
39.tap: 39-fill-block-optimized.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 39-fill-block-optimized.lst
 
40.tap: 40-draw-char.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 40-draw-char.lst
 
41.tap: 41-draw-any-char.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 41-draw-any-char.lst
 
42.tap: 42-block-anywhere.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 42-block-anywhere.lst
 
43.tap: 43-block-anywhere-rrca.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 43-block-anywhere-rrca.lst
 
44.tap: 44-better-draw-char.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 44-better-draw-char.lst
 
45.tap: 45-even-better-draw-char.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 45-even-better-draw-char.lst
 
46.tap: 46-draw-char-at.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 46-draw-char-at.lst
 
47.tap: 47-draw-char-at-unrolled.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 47-draw-char-at-unrolled.lst
 
48.tap: 48-incorrect-print-string.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 48-incorrect-print-string.lst
 
49.tap: 49-correct-print-string.asm
        $(ASSEMBLER) -v -d --tapbas $< $@ > 49-correct-print-string.lst

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

V tabulce zobrazené pod tímto odstavcem jsou uvedeny odkazy na všechny prozatím popsané demonstrační příklady určené pro překlad a spuštění na osmibitovém domácím mikropočítači ZX Spectrum (libovolný model či jeho klon), které jsou psány v assembleru mikroprocesoru Zilog Z80. Pro překlad těchto demonstračních příkladů je možné použít například assembler Pasmo (viz též úvodní článek):

# Soubor Stručný popis Adresa
1 01-color-attribute.asm modifikace jednoho barvového atributu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/01-color-attribute.asm
2 02-blinking-attribute.asm barvový atribut s nastavením bitů pro blikání a vyšší intenzitu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/02-blinking-attribute.asm
3 03-symbolic-names.asm symbolická jména v assembleru https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/03-symbolic-names.asm
4 04-operators.asm operátory a operace se symbolickými hodnotami https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/04-operators.asm
5 05-better-symbols.asm tradičnější symbolická jména https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/05-better-symbols.asm
6 06-tapbas-v1.asm vygenerování BASICovského loaderu (neúplný příklad) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/06-tapbas-v1.asm
7 07-tapbas-v2.asm vygenerování BASICovského loaderu (úplný příklad) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/07-tapbas-v2.asm
8 08-loop.asm jednoduchá počítaná programová smyčka: naivní varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/08-loop.asm
9 09-loop.asm programová smyčka: zkrácení kódu pro vynulování použitých pracovních registrů https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/09-loop.asm
10 10-loop.asm programová smyčka: optimalizace skoku na konci smyčky (instrukce DJNZ) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/10-loop.asm
11 11-loop.asm programová smyčka: optimalizace využití pracovních registrů https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/11-loop.asm
12 12-loop.asm programová smyčka: použití pracovního registru IX https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/12-loop.asm
13 13-loop.asm programová smyčka: použití pracovního registru IY https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/13-loop.asm
14 14-loop.asm programová smyčka se šestnáctibitovým počitadlem, základní varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/14-loop.asm
15 15-loop.asm programová smyčka se šestnáctibitovým počitadlem, vylepšená varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/15-loop.asm
16 16-loop.asm použití relativního skoku a nikoli skoku absolutního https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/16-loop.asm
17 17-loop.asm programová smyčka: inc l namísto inc hl https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/17-loop.asm
       
18 18-cls.asm smazání obrazovky a otevření kanálu číslo 2 (screen) přes funkci v ROM https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/18-cls.asm
19 19-print-char-call.asm smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce CALL) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/19-print-char-call.asm
20 20-print-char-rst.asm smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce RST) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/20-print-char-rst.asm
21 21-print-char.asm pouze výpis jednoho znaku na obrazovku bez jejího smazání https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/21-print-char.asm
22 22-print-all-chars.asm výpis znakové sady znak po znaku (nekorektní verze příkladu) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/22-print-all-chars.asm
23 23-print-all-chars.asm výpis znakové sady znak po znaku (korektní verze příkladu) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/23-print-all-chars.asm
24 24-change-color.asm změna barvových atributů (popředí a pozadí) vypisovaných znaků https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/24-change-color.asm
25 25-change-flash.asm povolení či zákaz blikání vypisovaných znaků https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/25-change-flash.asm
26 26-print-at.asm výpis znaku či znaků na určené místo na obrazovce https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/26-print-at.asm
27 27-print-string.asm výpis celého řetězce explicitně zapsanou programovou smyčkou (základní varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/27-print-string.asm
28 28-print-string.asm výpis celého řetězce explicitně zapsanou programovou smyčkou (vylepšená varianta) https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/28-print-string.asm
29 29-print-colorized-string.asm výpis řetězce, který obsahuje i řídicí znaky pro změnu barvy atd. https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/29-print-colorized-string.asm
30 30-print-string-ROM.asm výpis řetězce s využitím služby/subrutiny uložené v ROM ZX Spectra https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/30-print-string-ROM.asm
       
31 31-attributes.asm modifikace atributů pro tisk řetězce subrutinou uloženou v ROM https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/31-attributes.asm
32 32-fill-in-vram.asm vyplnění celé bitmapy barvou popředí, návrat do systému https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/32-fill-in-vram.asm
33 33-fill-in-vram-no-ret.asm vyplnění celé bitmapy barvou popředí, bez návratu do systému https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/33-fill-in-vram-no-ret.asm
34 34-fill-in-vram-pattern.asm vyplnění celé bitmapy zvoleným vzorkem https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/34-fill-in-vram-pattern.asm
35 35-slow-fill-in-vram.asm pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/35-slow-fill-in-vram.asm
36 36-slow-fill-in-vram-no-ret.asm pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy, bez návratu do systému https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/36-slow-fill-in-vram-no-ret.asm
37 37-fill-block.asm vykreslení bloku 8×8 pixelů https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/37-fill-block.asm
38 38-fill-block-with-pattern.asm vykreslení bloku 8×8 pixelů zvoleným vzorkem https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/38-fill-block-with-pattern.asm
39 39-fill-block-optimized.asm optimalizace předchozího příkladu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/39-fill-block-optimized.asm
40 40-draw-char.asm vykreslení znaku do levého horního rohu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/40-draw-char.asm
41 41-draw-any-char.asm podprogram pro vykreslení libovolně zvoleného znaku do levého horního rohu https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/41-draw-any-char.asm
42 42-block-anywhere.asm podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/42-block-anywhere.asm
       
43 43-block-anywhere-rrca.asm podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku, vylepšená varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/43-block-anywhere-rrca.asm
44 44-better-draw-char.asm vykreslení znaku v masce 8×8 pixelů, vylepšená varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/44-better-draw-char.asm
45 45-even-better-draw-char.asm posun offsetu pro vykreslení dalšího znaku https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/45-even-better-draw-char.asm
46 46-draw-char-at.asm vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/46-draw-char-at.asm
47 47-draw-char-at-unrolled.asm vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/47-draw-char-at-unrolled.asm
48 48-incorrect-print-string.asm tisk řetězce, nekorektní varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/48-incorrect-print-string.asm
49 49-correct-print-string.asm tisk řetězce, korektní varianta https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/49-correct-print-string.asm
       
50 Makefile Makefile pro překlad a slinkování všech demonstračních příkladů do podoby obrazu magnetické pásky https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/Makefile

20. Odkazy na Internetu

  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

Autor článku

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