Obsah
1. Dokončení realizace příkazu PLOT
2. Vyplnění obrazovky inverzními znaky z ASCII tabulky
3. Úplný zdrojový kód demonstračního příkladu pro tisk inverzních ASCII tabulek
4. Původní rutina pro vykreslení pixelu použitá společně s inverzní ASCII tabulkou
5. Úplný zdrojový kód dnešního druhého demonstračního příkladu
6. Vykreslení jediného pixelu barvou papíru
7. Úprava vykreslovací rutiny PLOT pro korektní kreslení barvou papíru
8. Úplný zdrojový kód dnešního třetího demonstračního příkladu
9. Jak vytvořit subrutinu pro tisk normální i inverzní ASCII tabulky?
10. Samomodifikující se kód na mikroprocesoru Zilog Z80
11. Úprava subrutiny pro vykreslení znaku
12. Řízená modifikace předchozí subrutiny v čase běhu programu
13. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu
14. Vykreslení pixelu metodou PLOT OVER (inverze)
15. Inverze celé obrazovky tím nejpomalejším způsobem – pixel po pixelu
16. Úplný zdrojový kód dnešního posledního demonstračního příkladu
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
19. Repositář s demonstračními příklady
1. Dokončení realizace příkazu PLOT
V pořadí již sedmé části seriálu o vývoji programů pro legendární osmibitový domácí mikropočítač ZX Spectrum dokončíme až kupodivu velmi rozsáhlé a netriviální téma, kterému jsme se věnovali předminule i minule. Jedná se o vlastní implementaci podprogramu (neboli subrutiny) naprogramované v assembleru osmibitového mikroprocesoru Zilog Z80, přičemž tato subrutina bude napodobovat příkaz PLOT ze Sinclair BASICu. Tento příkaz slouží pro vykreslení jediného pixelu na obrazovku; programátor přitom může určit, zda se vykreslení provede barvou inkoustu (v BASICu se používá jméno ink), barvou papíru (paper) nebo zda se má již nakreslený pixel invertovat (z barvy inkoustu na barvu papíru či naopak).

Obrázek 1: První verze programu realizujícího rutinu PLOT: postupné vykreslování úsečky z „blokových pixelů“.
V závěru předchozího článku, konkrétně v sedmnácté kapitole, je uveden výpis demonstračního příkladu, jenž dokáže vykreslit pixel (resp. pixely) barvou inkoustu a přitom nepřemazat pixely v okolí. Úspěšně jsme tedy realizovali první krok k implementaci plnohodnotného příkazu PLOT. Dnes si ukážeme další dvě možné varianty příkazu PLOT, a to konkrétně vykreslení pixelu barvou papíru a inverzi pixelu.

Obrázek 2: Druhá verze programu realizujícího rutinu PLOT: postupné vykreslování úsečky z „úzkých pixelů“, ovšem s přepisem pozadí (ostatních sedmi pixelů v bloku).
Budeme se zabývat i tvorbou takzvaného samomodifikujícího se kódu. Jedná se o zajímavou techniku, v níž běžící program přepíše část sebe samého takovým způsobem, aby se dosáhlo kýženého efektu. Tato technika kromě optimalizace na velikost programu, popř. na rychlost jeho běhu navíc umožňuje, aby se minimalizoval počet parametrů vstupujících do složitěji strukturovaného kódu, protože obecně platí, že předávání většího množství parametrů je na (vlastně všech) typech mikroprocesorů relativně problematické.

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

Obrázek 4: Inverzní znaky z ASCII tabulky.
Úprava podprogramu pro tisk znaků tak, aby se znaky vytiskly inverzně, je velmi jednoduchá, protože postačuje přidat strojovou instrukci pro negaci celého bajtu, který je zapisován na obrazovku (jak uvidíme dále, je tato operace pro znaky jednodušší, než pro jednotlivé pixely). Původní podprogram vypadal takto:
draw_char: ; Vytištění jednoho znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
Po výše zmíněné úpravě získáme následující subrutinu (viz podtržená část kódu, která byla modifikována):
draw_char_inv: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
3. Úplný zdrojový kód demonstračního příkladu pro tisk inverzních ASCII tabulek
Úplný zdrojový kód demonstračního příkladu popsaného ve druhé kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/56-inverse-ascii-table.asm:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami finito: jr finito ; ukončit program nekonečnou smyčkou fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků ret ; návrat z podprogramu draw_ascii_table_inv: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char_inv ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char_inv ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu draw_char_inv: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Výsledek překladu tohoto programu do strojového kódu vypadá takto:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:CD0580 CALL 8005 8003: label finito 8003:18FE JR 8003 8005: label fill_in_screen 8005:110040 LD DE, 4000 8008:CD1580 CALL 8015 800B:CD1580 CALL 8015 800E:CD1580 CALL 8015 8011:CD1580 CALL 8015 8014:C9 RET 8015: label draw_ascii_table_inv 8015:3E20 LD A, 20 8017: label next_char 8017:F5 PUSH AF 8018:CD2780 CALL 8027 801B:3E20 LD A, 20 801D:CD2780 CALL 8027 8020:F1 POP AF 8021:3C INC A 8022:FE80 CP 80 8024:20F1 JR NZ, 8017 8026:C9 RET 8027: label draw_char_inv 8027:01003C LD BC, 3C00 802A:61 LD H, C 802B:6F LD L, A 802C:29 ADD HL, HL 802D:29 ADD HL, HL 802E:29 ADD HL, HL 802F:09 ADD HL, BC 8030:0608 LD B, 08 8032:4A LD C, D 8033: label loop 8033:7E LD A, (HL) 8034:2F CPL 8035:12 LD (DE), A 8036:2C INC L 8037:14 INC D 8038:10F9 DJNZ 8033 803A:1C INC E 803B:C8 RET Z 803C:51 LD D, C 803D:C9 RET 803E: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 803D
4. Původní rutina pro vykreslení pixelu použitá společně s inverzní ASCII tabulkou
Jen pro úplnost si ukažme, jak bude vypadat obrazovka ZX Spectra, pokud na ní nejdříve vykreslíme sadu inverzních ASCII tabulek a následně do této scény vykreslíme diagonální úsečku s černými pixely, resp. přesněji řečeno s pixely vykreslenými inkoustem (ink). Výsledná úsečka nebude (zcela podle očekávání) příliš viditelná:

Obrázek 5: Úsečka vytvořená černými pixely vykreslenými nad (převážně) tmavým pozadím (logicky není moc viditelná).
Samotné tělo programu se nebude příliš lišit od příkladů ukázaných v předchozím článku. Jeho základní kostra (main) zůstane zcela zachována:
start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou
Rozdíl spočívá v poněkud odlišně realizované rutině fill_in_screen, která bude vykreslovat inverzní ASCII tabulky:
fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků ret ; návrat z podprogramu
5. Úplný zdrojový kód dnešního druhého demonstračního příkladu
Úplný zdrojový kód dnešního druhého demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/57-plot-pixel-on-inverse-background.asm a vypadá následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot ; vykreslení pixelu call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot: ; třetí varianta podprogramu pro vykreslení pixelu ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů or d ; použít vypočtenou masku pro nastavení jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu calc_pixel_value: ; parametry: ; B - x-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; A - hodnota pixelu push bc ; zapamatovat si hodnotu v registru B ld a, b ; A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; A: 0 0 0 0 0 X2 X1 X0 ld b, a ; počitadlo smyčky (neměníme příznaky) ld a, %10000000 ; výchozí maska (neměníme příznaky) jr z, end_calc ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x end_calc: pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu calc_pixel_address: ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis pixelu ; ; pozměněné registry: ; A ; ; vzor adresy: ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0 ld a, c ; všech osm bitů Y-ové souřadnice and %00000111 ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0) ; A: 0 0 0 0 0 Y2 Y1 Y0 or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; část horního bajtu adresy je vypočtena ; H: 0 1 0 0 0 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rra rra rra ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3 and %00011000 ; zamaskovat ; A: 0 0 0 Y7 Y6 0 0 0 or h ; a přidat k vypočtenému mezivýsledku ld h, a ; H: 0 1 0 Y7 Y6 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rla rla ; A: Y5 Y4 Y3 Y2 Y1 Y0 xx xx and %11100000 ; A: Y5 Y4 Y3 0 0 0 0 0 ld l, a ; část spodního bajtu adresy je vypočtena ld a, b ; všech osm bitů X-ové souřadnice rra rra rra ; rotace doprava -> 0 0 0 X7 X6 X5 X4 and %00011111 ; A: 0 0 0 X7 X6 X5 X4 X3 or l ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3 ld l, a ; spodní bajt adresy je vypočten ret ; návrat z podprogramu fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků ret ; návrat z podprogramu draw_ascii_table_inv: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char_inv ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char_inv ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu draw_char_inv: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Pochopitelně si opět pro úplnost ukážeme, jakým způsobem byl proveden překlad do strojového kódu:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:CD5880 CALL 8058 8003:0600 LD B, 00 8005:0E00 LD C, 00 8007: label loop 8007:CD2280 CALL 8022 800A:CD1680 CALL 8016 800D:04 INC B 800E:0C INC C 800F:78 LD A, B 8010:FEC0 CP C0 8012:20F3 JR NZ, 8007 8014: label finito 8014:18FE JR 8014 8016: label delay 8016:C5 PUSH BC 8017:0614 LD B, 14 8019: label outer_loop 8019:0E00 LD C, 00 801B: label inner_loop 801B:0D DEC C 801C:20FD JR NZ, 801B 801E:10F9 DJNZ 8019 8020:C1 POP BC 8021:C9 RET 8022: label plot 8022:CD3B80 CALL 803B 8025:CD2C80 CALL 802C 8028:56 LD D, (HL) 8029:B2 OR D 802A:77 LD (HL), A 802B:C9 RET 802C: label calc_pixel_value 802C:C5 PUSH BC 802D:78 LD A, B 802E:E607 AND 07 8030:47 LD B, A 8031:3E80 LD A, 80 8033:2804 JR Z, 8039 8035: label next_shift 8035:CB3F SRL A 8037:10FC DJNZ 8035 8039: label end_calc 8039:C1 POP BC 803A:C9 RET 803B: label calc_pixel_address 803B:79 LD A, C 803C:E607 AND 07 803E:F640 OR 40 8040:67 LD H, A 8041:79 LD A, C 8042:1F RRA 8043:1F RRA 8044:1F RRA 8045:E618 AND 18 8047:B4 OR H 8048:67 LD H, A 8049:79 LD A, C 804A:17 RLA 804B:17 RLA 804C:E6E0 AND E0 804E:6F LD L, A 804F:78 LD A, B 8050:1F RRA 8051:1F RRA 8052:1F RRA 8053:E61F AND 1F 8055:B5 OR L 8056:6F LD L, A 8057:C9 RET 8058: label fill_in_screen 8058:110040 LD DE, 4000 805B:CD6880 CALL 8068 805E:CD6880 CALL 8068 8061:CD6880 CALL 8068 8064:CD6880 CALL 8068 8067:C9 RET 8068: label draw_ascii_table_inv 8068:3E20 LD A, 20 806A: label next_char 806A:F5 PUSH AF 806B:CD7A80 CALL 807A 806E:3E20 LD A, 20 8070:CD7A80 CALL 807A 8073:F1 POP AF 8074:3C INC A 8075:FE80 CP 80 8077:20F1 JR NZ, 806A 8079:C9 RET 807A: label draw_char_inv 807A:01003C LD BC, 3C00 807D:61 LD H, C 807E:6F LD L, A 807F:29 ADD HL, HL 8080:29 ADD HL, HL 8081:29 ADD HL, HL 8082:09 ADD HL, BC 8083:0608 LD B, 08 8085:4A LD C, D 8086: label loop2 8086:7E LD A, (HL) 8087:2F CPL 8088:12 LD (DE), A 8089:2C INC L 808A:14 INC D 808B:10F9 DJNZ 8086 808D:1C INC E 808E:C8 RET Z 808F:51 LD D, C 8090:C9 RET 8091: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8090
6. Vykreslení jediného pixelu barvou papíru
Nyní se již konečně můžeme zabývat problémem, který budeme chtít v dnešním článku vyřešit – jakým způsobem se zajistí realizace příkazu PLOT INVERSE, tedy vykreslení jediného pixelu barvou papíru (paper)? Teoreticky můžeme postupovat stejným způsobem, jako při tisku pixelu barvou inkoustu:
- Výpočet adresy v obrazové paměti, kde je uložen vykreslovaný pixel i sedm dalších sousedních pixelů
- Výpočet masky, tj. osmibitové hodnoty, která se bude aplikovat na původní bajt přečtený z obrazové paměti
- Aplikace masky, tj. kombinace původní hodnoty přečtené z obrazové paměti s vypočtenou maskou
- Zápis výsledku na (stejnou) adresu v obrazové paměti
Obecný postup tedy již známe a máme ho realizován pro pixel v barvě inkoustu. Jak se však tento postup bude konkrétně implementovat v našem „inverzním“ případě? Evidentně nebudeme měnit první bod, protože adresa pro čtení/zápis bajtu z/do obrazové paměti zůstane stejná. Podobně nebudeme měnit ani bod poslední, protože i ten musí zůstat zachován. Změnit tedy budeme muset výpočet masky a aplikaci této masky.
Pro vykreslení pixelu barvou inkoustu je nutné nastavit vybraný bit na jedničku. K tomu slouží instrukce or a maska, která má jeden bit nastavený na jedničku a ostatní bity nulové:
původní hodnota x x x x x x x x maska 0 0 0 1 0 0 0 0 ================================= hodnota OR maska x x x 1 x x x x
Naproti tomu pro vykreslení pixelu barvou papíru je nutné vynulovat vybraný bit. K tomu slouží instrukce and a maska, která má jeden bit nastavený na nulu o ostatní bity jedničkové:
původní hodnota x x x x x x x x maska 1 1 1 0 1 1 1 1 ================================= hodnota AND maska x x x 0 x x x x
7. Úprava vykreslovací rutiny PLOT pro korektní kreslení barvou papíru
Pro vykreslení pixelu barvou inkoustu vypadala realizace výše uvedeného postupu následovně:
plot: ; třetí varianta podprogramu pro vykreslení pixelu ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů or d ; použít vypočtenou masku pro nastavení jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu
V upravené variantě jsme přidali instrukci cpl určenou pro negaci obsahu akumulátoru (complement, tím je myšlen jedničkový doplněk) a instrukce or d byla nahrazena za instrukci and d:
plot_inverse: ; varianta podprogramu pro vykreslení pixelu barvou papíru ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů cpl ; inverze masky and d ; použít vypočtenou masku pro vynulování jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu

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

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

Obrázek 8: Úsečka vykreslená z pixelů s barvou papíru, nikoli inkoustu.
8. Úplný zdrojový kód dnešního třetího demonstračního příkladu
Úplný zdrojový kód dnešního v pořadí již třetího demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/58-plot-inverse-pixel-on-inverse-background.asm a vypadá následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot_inverse ; vykreslení pixelu barvou papíru call delay inc b ; posun na další souřadnici inc c ld a, b cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není vykreslena celá šikmá "úsečka" finito: jr finito ; ukončit program nekonečnou smyčkou delay: ; zpožďovací rutina ; nemění žádné registry push bc ; uschovat hodnoty registrů, které se používají ve smyčkách ld b, 20 ; počitadlo vnější zpožďovací smyčky outer_loop: ld c, 0 ; počitadlo vnitřní zpožďovací smyčky inner_loop: dec c ; snížení hodnoty počitadla (v první iteraci 256->255) jr NZ, inner_loop ; opakovat, dokud není dosaženo nuly djnz outer_loop ; opakovat vnější smyčku, nyní s počitadlem v B pop bc ; obnovit hodnoty registrů změněných smyčkami ret ; návrat z podprogramu plot_inverse: ; varianta podprogramu pro vykreslení pixelu barvou papíru ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů cpl ; inverze masky and d ; použít vypočtenou masku pro vynulování jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu calc_pixel_value: ; parametry: ; B - x-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; A - hodnota pixelu push bc ; zapamatovat si hodnotu v registru B ld a, b ; A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; A: 0 0 0 0 0 X2 X1 X0 ld b, a ; počitadlo smyčky (neměníme příznaky) ld a, %10000000 ; výchozí maska (neměníme příznaky) jr z, end_calc ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x end_calc: pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu calc_pixel_address: ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis pixelu ; ; pozměněné registry: ; A ; ; vzor adresy: ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0 ld a, c ; všech osm bitů Y-ové souřadnice and %00000111 ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0) ; A: 0 0 0 0 0 Y2 Y1 Y0 or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; část horního bajtu adresy je vypočtena ; H: 0 1 0 0 0 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rra rra rra ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3 and %00011000 ; zamaskovat ; A: 0 0 0 Y7 Y6 0 0 0 or h ; a přidat k vypočtenému mezivýsledku ld h, a ; H: 0 1 0 Y7 Y6 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rla rla ; A: Y5 Y4 Y3 Y2 Y1 Y0 xx xx and %11100000 ; A: Y5 Y4 Y3 0 0 0 0 0 ld l, a ; část spodního bajtu adresy je vypočtena ld a, b ; všech osm bitů X-ové souřadnice rra rra rra ; rotace doprava -> 0 0 0 X7 X6 X5 X4 and %00011111 ; A: 0 0 0 X7 X6 X5 X4 X3 or l ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3 ld l, a ; spodní bajt adresy je vypočten ret ; návrat z podprogramu fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků call draw_ascii_table_inv ; vykreslení 96 znaků ret ; návrat z podprogramu draw_ascii_table_inv: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char_inv ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char_inv ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu draw_char_inv: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Způsob překladu tohoto příkladu do strojového kódu je následující:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 ORG 8000 8000: label start 8000:CD5980 CALL 8059 8003:0600 LD B, 00 8005:0E00 LD C, 00 8007: label loop 8007:CD2280 CALL 8022 800A:CD1680 CALL 8016 800D:04 INC B 800E:0C INC C 800F:78 LD A, B 8010:FEC0 CP C0 8012:20F3 JR NZ, 8007 8014: label finito 8014:18FE JR 8014 8016: label delay 8016:C5 PUSH BC 8017:0614 LD B, 14 8019: label outer_loop 8019:0E00 LD C, 00 801B: label inner_loop 801B:0D DEC C 801C:20FD JR NZ, 801B 801E:10F9 DJNZ 8019 8020:C1 POP BC 8021:C9 RET 8022: label plot_inverse 8022:CD3C80 CALL 803C 8025:CD2D80 CALL 802D 8028:56 LD D, (HL) 8029:2F CPL 802A:A2 AND D 802B:77 LD (HL), A 802C:C9 RET 802D: label calc_pixel_value 802D:C5 PUSH BC 802E:78 LD A, B 802F:E607 AND 07 8031:47 LD B, A 8032:3E80 LD A, 80 8034:2804 JR Z, 803A 8036: label next_shift 8036:CB3F SRL A 8038:10FC DJNZ 8036 803A: label end_calc 803A:C1 POP BC 803B:C9 RET 803C: label calc_pixel_address 803C:79 LD A, C 803D:E607 AND 07 803F:F640 OR 40 8041:67 LD H, A 8042:79 LD A, C 8043:1F RRA 8044:1F RRA 8045:1F RRA 8046:E618 AND 18 8048:B4 OR H 8049:67 LD H, A 804A:79 LD A, C 804B:17 RLA 804C:17 RLA 804D:E6E0 AND E0 804F:6F LD L, A 8050:78 LD A, B 8051:1F RRA 8052:1F RRA 8053:1F RRA 8054:E61F AND 1F 8056:B5 OR L 8057:6F LD L, A 8058:C9 RET 8059: label fill_in_screen 8059:110040 LD DE, 4000 805C:CD6980 CALL 8069 805F:CD6980 CALL 8069 8062:CD6980 CALL 8069 8065:CD6980 CALL 8069 8068:C9 RET 8069: label draw_ascii_table_inv 8069:3E20 LD A, 20 806B: label next_char 806B:F5 PUSH AF 806C:CD7B80 CALL 807B 806F:3E20 LD A, 20 8071:CD7B80 CALL 807B 8074:F1 POP AF 8075:3C INC A 8076:FE80 CP 80 8078:20F1 JR NZ, 806B 807A:C9 RET 807B: label draw_char_inv 807B:01003C LD BC, 3C00 807E:61 LD H, C 807F:6F LD L, A 8080:29 ADD HL, HL 8081:29 ADD HL, HL 8082:29 ADD HL, HL 8083:09 ADD HL, BC 8084:0608 LD B, 08 8086:4A LD C, D 8087: label loop2 8087:7E LD A, (HL) 8088:2F CPL 8089:12 LD (DE), A 808A:2C INC L 808B:14 INC D 808C:10F9 DJNZ 8087 808E:1C INC E 808F:C8 RET Z 8090:51 LD D, C 8091:C9 RET 8092: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8091
9. Jak vytvořit subrutinu pro tisk normální i inverzní ASCII tabulky?
V dalších kapitolách si mj. ukážeme způsob vykreslování „inverzních pixelů“, což je vlastně další varianta BASICovského příkazu PLOT. Pixel, který měl původně barvu inkoustu se změní na barvu pozadí a naopak. Aby byla tato operace dobře viditelná, pozměníme nejprve nepatrně pozadí celé scény zobrazené na obrazovce. Opět vykreslíme několik sad ASCII znaků, tentokrát však bude první polovina obrazovky vyplněna inverzními ASCII tabulky a druhá polovina původními (neinverzními) tabulkami:

Obrázek 9: Obrazovka ZX Spectra vykreslená jak neinverzní ASCII tabulkou, tak i její inverzní variantou.
Jak lze tohoto efektu dosáhnout? Nejprimitivnější by pochopitelně bylo ponechat v programovém kódu obě subrutiny pro tisk ASCII tabulek, tedy jak subrutinu pro tisk neinverzní tabulky, tak i tabulky inverzní:
draw_ascii_table: ; Vytištění ASCII tabulky ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu
a:
draw_ascii_table_inv: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char_inv ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char_inv ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu
Jenže když se podíváte na obě subrutiny, je ihned patrné, že se neliší jejich „vlastní“ instrukce (to by nebyl největší problém, protože se jedná pouze o několik instrukcí), ale že se volají i odlišné subrutiny pro tisk znaků, resp. inverzních znaků. A tyto subrutiny jsou již dosti rozsáhlé a asi není nejlepší nápad zaplňovat omezený prostor RAM (48 kB včetně obrazové paměti) několika kopiemi prakticky totožného programového kódu:
Samozřejmě by bylo možné nějakým způsobem do těchto subrutin předat další parametr typu „vykresluj znaky normálně“ nebo „vykresluj znaky inverzně“. Problém spočívá v omezeném počtu volných registrů (takové registry neexistují) a taktéž v tom, že by se uvnitř subrutin musel tento parametr neustále testovat a tím pádem by došlo ke zpomalení celého kódu.
draw_char: ; Vytištění jednoho znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
a:
draw_char_inv: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
10. Samomodifikující se kód na mikroprocesoru Zilog Z80
Zkusme namísto snahy o předání dalšího parametru subrutině draw_char použít zcela odlišný přístup. Nejprve si obě subrutiny porovnejme instrukci po instrukci:
label draw_char label draw_char_inv 01003C LD BC, 3C00 01003C LD BC, 3C00 61 LD H, C 61 LD H, C 6F LD L, A 6F LD L, A 29 ADD HL, HL 29 ADD HL, HL 29 ADD HL, HL 29 ADD HL, HL 29 ADD HL, HL 29 ADD HL, HL 09 ADD HL, BC 09 ADD HL, BC 0608 LD B, 08 0608 LD B, 08 4A LD C, D 4A LD C, D label loop label loop 7E LD A, (HL) 7E LD A, (HL) 2F CPL 12 LD (DE), A 12 LD (DE), A 2C INC L 2C INC L 14 INC D 14 INC D 10FA DJNZ 8033 10F9 DJNZ 8033 1C INC E 1C INC E C8 RET Z C8 RET Z 51 LD D, C 51 LD D, C C9 RET C9 RET
Z výše uvedeného porovnání je zřejmé, že se subrutiny odlišují pouze v jediné instrukci, která byla přidána do subrutiny pro tisk inverzního znaku. A takový problém umíme na osmibitových mikropočítačích s mikroprocesory bez ochrany paměti snadno řešit, a to konkrétně použitím samomodifikujícího se kódu. Jedná se o takový kód, jehož instrukce se mění v čase běhu programu (tedy v runtime) cílenou změnou operačních kódů instrukcí a/nebo dalších bajtů, v nichž jsou uloženy adresy nebo jiné operandy instrukcí.
Aby bylo možné tuto modifikaci provést, musíme znát (= umět najít) operační kódy instrukcí, protože mnoho assemblerů neumožňuje použít mnemotechnický název instrukce jako operand jiné instrukce (pro assembler tvoří mnemotechnické kódy instrukcí samostatný jmenný prostor). To však nevadí, protože si mapování mezi mnemotechnickým názvem instrukce a číselným kódem instrukce můžeme do programu přidat sami formou pojmenovaných symbolů.
Z tabulky operačních kódů instrukcí tedy můžeme zjistit, jakou hodnotu máme zapsat, ovšem ještě je nutné zajistit, aby se tato hodnota zapsala na správné místo. To je v assembleru až směšně jednoduché (a prakticky nemožné dosáhnout ve vyšších programovacích jazycích), protože před každou instrukci můžeme vložit návěští (label). Měněný program může vypadat takto:
ld a, 10 menena_instrukce: inc a ld b, a
Po vykonání této části kódu by měla být v registrech A a B zapsána hodnota 11. Pokud ovšem spustíme tuto část kódu:
ld hl, menena_instrukce ld (hl), 0
popř. můžeme použít čitelnější podobu se symbolem:
NOP_INSTRUCTION equ 0 ld hl, menena_instrukce ld (hl), NOP_INSTRUCTION
Změní se původní program (v runtime) de facto takto:
ld a, 10 menena_instrukce: nop ld b, a
Proč tomu tak je? Hodnota 0, kterou jsme na místo instrukce zapsali, odpovídá kódu instrukce NOP, což snadno přečteme z již výše linkované tabulky.
Podobným způsobem například můžeme nahradit programovou smyčku, v níž se postupně zvyšuje hodnota počitadla instrukcí inc za smyčku, kde se hodnota naopak snižuje atd. atd. – podobných triků existují stovky a možná i tisíce.
11. Úprava subrutiny pro vykreslení znaku
Z předchozího textu již víme, jakým způsobem je možné modifikovat část programového kódu, takže se vraťme k řešení našeho problému – jak zajistit vytištění běžné ASCII tabulky a též tabulky, ovšem s inverzními znaky? Použijeme samomodifikující se kód, přičemž rutinu pro vykreslení jediného znaku nepatrně změníme. Ona změna spočívá v přidání návěští inv_instruction_adr před instrukci, kterou budeme chtít modifikovat. Jedná se o instrukci pro negaci obsahu akumulátoru A, což je instrukce CPL s délkou jednoho bajtu (to je důležité):
draw_char: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky inv_instruction_adr: cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1
12. Řízená modifikace předchozí subrutiny v čase běhu programu
Subrutina popsaná v předchozí kapitole má před instrukcí CPL definováno návěští inv_instruction_adr. Díky tomuto návěští víme, do jakého paměťového místa zapisovat. Kód instrukce CPL můžeme kdykoli v runtime nahradit za instrukci NOP s kódem 0×00 nebo se později vrátit zpět k CPL zápisem kódu 0×2f (popř. použít libovolnou další instrukci o délce jednoho bajtu).
Nejdříve si zadefinujeme symbol pro instrukci NOP, aby byl výsledný program čitelnější:
NOP_INSTRUCTION equ 0
Vykreslení inverzní ASCII tabulky (původní kód obsahuje instrukci CPL):
call draw_ascii_table ; vykreslení 96 znaků
Vykreslení neinverzní ASCII tabulky modifikací programového kódu:
ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat ld (hl), NOP_INSTRUCTION ; zápis instrukce NOP namísto instrukce CPL call draw_ascii_table ; vykreslení 96 znaků
Opětovná modifikace též instrukce povede k návratu k vykreslování inverzní ASCII tabulky:
ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat ld (hl), CPL_INSTRUCTION ; zápis instrukce NOP namísto instrukce CPL call draw_ascii_table ; vykreslení 96 znaků
13. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu
Úplný zdrojový kód dnešního v pořadí již čtvrtého demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/59-configurable-ascii-table.asm a vypadá následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 NOP_INSTRUCTION equ 0 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami finito: jr finito ; ukončit program nekonečnou smyčkou fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat ld (hl), NOP_INSTRUCTION ; zápis instrukce NOP namísto instrukce CPL call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu draw_ascii_table: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu draw_char : ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop: ld a,(hl) ; načtení jednoho bajtu z masky inv_instruction_adr: cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Překlad do strojového kódu:
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 NOP_INSTRUCTION EQU 0000 ORG 8000 8000: label start 8000:CD0580 CALL 8005 8003: label finito 8003:18FE JR 8003 8005: label fill_in_screen 8005:110040 LD DE, 4000 8008:CD1A80 CALL 801A 800B:CD1A80 CALL 801A 800E:213980 LD HL, 8039 8011:3600 LD (HL), 00 8013:CD1A80 CALL 801A 8016:CD1A80 CALL 801A 8019:C9 RET 801A: label draw_ascii_table 801A:3E20 LD A, 20 801C: label next_char 801C:F5 PUSH AF 801D:CD2C80 CALL 802C 8020:3E20 LD A, 20 8022:CD2C80 CALL 802C 8025:F1 POP AF 8026:3C INC A 8027:FE80 CP 80 8029:20F1 JR NZ, 801C 802B:C9 RET 802C: label draw_char 802C:01003C LD BC, 3C00 802F:61 LD H, C 8030:6F LD L, A 8031:29 ADD HL, HL 8032:29 ADD HL, HL 8033:29 ADD HL, HL 8034:09 ADD HL, BC 8035:0608 LD B, 08 8037:4A LD C, D 8038: label loop 8038:7E LD A, (HL) 8039: label inv_instruction_adr 8039:2F CPL 803A:12 LD (DE), A 803B:2C INC L 803C:14 INC D 803D:10F9 DJNZ 8038 803F:1C INC E 8040:C8 RET Z 8041:51 LD D, C 8042:C9 RET 8043: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8042
14. Vykreslení pixelu metodou PLOT OVER (inverze)
V závěrečné části dnešního článku si ukážeme způsob vykreslení jediného pixelu metodou PLOT OVER. Jedná se o operaci, která změní (flip) hodnotu jediného bitu v obrazové paměti. Implementace je vlastně velmi jednoduchá, protože budeme (stále) aplikovat masku se sedmi bity nulovými a jediným bitem nastaveným na jedničku. A tato maska bude aplikována operací XOR (nonekvivalence), která onen jeden bit zneguje. Implementace je snadná:
plot_over: ; varianta podprogramu pro vykreslení pixelu inverzní barvou ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů xor d ; použít vypočtenou masku pro vynulování jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu
15. Inverze celé obrazovky tím nejpomalejším způsobem – pixel po pixelu
Podprogram plot_over ukázaný v předchozí kapitole dokáže invertovat barvu libovolného pixelu na obrazovce. Vyzkoušejme si tedy inverzi celé obrazovky pixel po pixelu (což je pochopitelně ten nejpomalejší možný způsob :-). Tato operace bude tak pomalá, že její provádění bude viditelné i bez použití zpožďovací smyčky:

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

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

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

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

Obrázek 14: Postupná inverze obrazovky pixel po pixelu.
Způsob realizace formou vnější a vnitřní programové smyčky:
; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot_over ; vykreslení pixelu inc b ; posun na další x-ovou souřadnici jr nz, loop ; vykreslit celou vodorovnou úsečku inc c ; posun na další y-ovou souřadnici ld a, c cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není překreslena celá obrazovka pixel po pixelu finito: jr finito ; ukončit program nekonečnou smyčkou
16. Úplný zdrojový kód dnešního posledního demonstračního příkladu
Úplný zdrojový kód dnešního v pořadí již pátého a současně i posledního demonstračního příkladu je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/60-plot-over.asm a vypadá následovně:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 NOP_INSTRUCTION equ 0 org ENTRY_POINT ; Vstupní bod celého programu start: call fill_in_screen ; vyplnění obrazovky ASCII tabulkami ld b, 0 ; x-ová souřadnice vykreslovaného pixelu ld c, 0 ; y-ová souřadnice vykreslovaného pixelu loop: call plot_over ; vykreslení pixelu inc b ; posun na další x-ovou souřadnici jr nz, loop ; vykreslit celou vodorovnou úsečku inc c ; posun na další y-ovou souřadnici ld a, c cp 192 ; test na ukončení smyčky jr nz, loop ; opakovat, dokud není překreslena celá obrazovka pixel po pixelu finito: jr finito ; ukončit program nekonečnou smyčkou plot_over: ; varianta podprogramu pro vykreslení pixelu inverzní barvou ; ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) call calc_pixel_address ; výpočet adresy pixelu call calc_pixel_value ; výpočet ukládané hodnoty ld d, (hl) ; přečíst původní hodnotu osmice pixelů xor d ; použít vypočtenou masku pro vynulování jediného bitu ld (hl), a ; zápis hodnoty pixelu (ostatních sedm pixelů se nezmění) ret ; návrat z podprogramu calc_pixel_value: ; parametry: ; B - x-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; A - hodnota pixelu push bc ; zapamatovat si hodnotu v registru B ld a, b ; A: X7 X6 X5 X4 X3 X2 X1 X0 and %00000111 ; A: 0 0 0 0 0 X2 X1 X0 ld b, a ; počitadlo smyčky (neměníme příznaky) ld a, %10000000 ; výchozí maska (neměníme příznaky) jr z, end_calc ; pokud je nyní souřadnice nulová, zapíšeme výchozí masku + konec next_shift: srl a ; posunout masku doprava djnz next_shift ; 1x až 7x end_calc: pop bc ; obnovit hodnotu v registru B ret ; návrat z podprogramu calc_pixel_address: ; parametry: ; B - x-ová souřadnice (v pixelech) ; C - y-ová souřadnice (v pixelech) ; ; návratové hodnoty: ; HL - adresa pro zápis pixelu ; ; pozměněné registry: ; A ; ; vzor adresy: ; 0 1 0 Y7 Y6 Y2 Y1 Y0 | Y5 Y4 Y3 X4 X3 X2 X1 X0 ld a, c ; všech osm bitů Y-ové souřadnice and %00000111 ; pouze spodní tři bity y-ové souřadnice (Y2 Y1 Y0) ; A: 0 0 0 0 0 Y2 Y1 Y0 or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld h, a ; část horního bajtu adresy je vypočtena ; H: 0 1 0 0 0 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rra rra rra ; rotace doprava -> Y1 Y0 xx Y7 Y6 Y5 Y4 Y3 and %00011000 ; zamaskovat ; A: 0 0 0 Y7 Y6 0 0 0 or h ; a přidat k vypočtenému mezivýsledku ld h, a ; H: 0 1 0 Y7 Y6 Y2 Y1 Y0 ld a, c ; všech osm bitů Y-ové souřadnice rla rla ; A: Y5 Y4 Y3 Y2 Y1 Y0 xx xx and %11100000 ; A: Y5 Y4 Y3 0 0 0 0 0 ld l, a ; část spodního bajtu adresy je vypočtena ld a, b ; všech osm bitů X-ové souřadnice rra rra rra ; rotace doprava -> 0 0 0 X7 X6 X5 X4 and %00011111 ; A: 0 0 0 X7 X6 X5 X4 X3 or l ; A: Y5 Y3 Y3 X7 X6 X5 X4 X3 ld l, a ; spodní bajt adresy je vypočten ret ; návrat z podprogramu fill_in_screen: ; Vyplnění obrazovky snadno rozpoznatelným vzorkem - ASCII tabulkami ; ; vstupy: ; žádné ld de, SCREEN_ADR ; adresa pro vykreslení prvního bloku znaků call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ld hl, inv_instruction_adr; adresa bajtu v paměti, který budeme modifikovat ld (hl), NOP_INSTRUCTION ; zápis instrukce NOP namísto instrukce CPL call draw_ascii_table ; vykreslení 96 znaků call draw_ascii_table ; vykreslení 96 znaků ret ; návrat z podprogramu draw_ascii_table: ; Vytištění ASCII tabulky inverzně (barva inkoustu je barvou pozadí a naopak) ; ; vstupy: ; DE - adresa v obrazové paměti pro vykreslení znaku ld a, ' ' ; kód vykreslovaného znaku next_char: push af ; uschovat akumulátor na zásobník call draw_char ; zavolat subrutinu pro vykreslení znaku ld a, ' ' ; vykreslit za znakem mezeru call draw_char ; zavolat subrutinu pro vykreslení znaku pop af ; obnovit akumulátor ze zásobníku inc a ; ASCII kód dalšího znaku cp ' ' + 96 ; jsme již na konci ASCII tabulky? jr nz, next_char ; ne? potom pokračujeme ret ; návrat z podprogramu draw_char: ; Vytištění jednoho inverzního znaku na obrazovku ; ; vstupy: ; A - kód znaku pro vykreslení ; DE - adresa v obrazové paměti pro vykreslení znaku ; ; výstupy: ; DE - adresa v obrazové paměti pro vykreslení dalšího znaku ; ; změněné registry: ; všechny ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků ld h, c ; C je nulové, protože CHAR_ADR=0x3c00 ld l, a ; kód znaku je nyní ve dvojici HL add hl, hl ; 2x add hl, hl ; 4x add hl, hl ; 8x add hl, bc ; přičíst bázovou adresu masek znaků ld b, 8 ; počitadlo zapsaných bajtů ld c, d loop2: ld a,(hl) ; načtení jednoho bajtu z masky inv_instruction_adr: cpl ; negace hodnoty v akumulátoru ld (de),a ; zápis hodnoty na adresu (DE) inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu) inc d ; posun na definici dalšího obrazového řádku djnz loop2 ; vnitřní smyčka: blok s osmi zápisy inc e ret z ; D+=8,E=E+1=0 ld d, c ret ; D=D,E=E+1 end ENTRY_POINT
Překlad tohoto demonstračního příkladu do strojového kódu (mimochodem – délka je rovna 148 bajtům; žádný příklad se tedy prozatím ani nepřiblížil jednomu kilobajtu):
SCREEN_ADR EQU 4000 CHAR_ADR EQU 3C00 ENTRY_POINT EQU 8000 NOP_INSTRUCTION EQU 0000 ORG 8000 8000: label start 8000:CD5780 CALL 8057 8003:0600 LD B, 00 8005:0E00 LD C, 00 8007: label loop 8007:CD2180 CALL 8021 800A:04 INC B 800B:20FA JR NZ, 8007 800D:0C INC C 800E:79 LD A, C 800F:FEC0 CP C0 8011:20F4 JR NZ, 8007 8013: label finito 8013:18FE JR 8013 8015: label delay 8015:C5 PUSH BC 8016:0614 LD B, 14 8018: label outer_loop 8018:0E00 LD C, 00 801A: label inner_loop 801A:0D DEC C 801B:20FD JR NZ, 801A 801D:10F9 DJNZ 8018 801F:C1 POP BC 8020:C9 RET 8021: label plot_over 8021:CD3A80 CALL 803A 8024:CD2B80 CALL 802B 8027:56 LD D, (HL) 8028:AA XOR D 8029:77 LD (HL), A 802A:C9 RET 802B: label calc_pixel_value 802B:C5 PUSH BC 802C:78 LD A, B 802D:E607 AND 07 802F:47 LD B, A 8030:3E80 LD A, 80 8032:2804 JR Z, 8038 8034: label next_shift 8034:CB3F SRL A 8036:10FC DJNZ 8034 8038: label end_calc 8038:C1 POP BC 8039:C9 RET 803A: label calc_pixel_address 803A:79 LD A, C 803B:E607 AND 07 803D:F640 OR 40 803F:67 LD H, A 8040:79 LD A, C 8041:1F RRA 8042:1F RRA 8043:1F RRA 8044:E618 AND 18 8046:B4 OR H 8047:67 LD H, A 8048:79 LD A, C 8049:17 RLA 804A:17 RLA 804B:E6E0 AND E0 804D:6F LD L, A 804E:78 LD A, B 804F:1F RRA 8050:1F RRA 8051:1F RRA 8052:E61F AND 1F 8054:B5 OR L 8055:6F LD L, A 8056:C9 RET 8057: label fill_in_screen 8057:110040 LD DE, 4000 805A:CD6C80 CALL 806C 805D:CD6C80 CALL 806C 8060:218B80 LD HL, 808B 8063:3600 LD (HL), 00 8065:CD6C80 CALL 806C 8068:CD6C80 CALL 806C 806B:C9 RET 806C: label draw_ascii_table 806C:3E20 LD A, 20 806E: label next_char 806E:F5 PUSH AF 806F:CD7E80 CALL 807E 8072:3E20 LD A, 20 8074:CD7E80 CALL 807E 8077:F1 POP AF 8078:3C INC A 8079:FE80 CP 80 807B:20F1 JR NZ, 806E 807D:C9 RET 807E: label draw_char 807E:01003C LD BC, 3C00 8081:61 LD H, C 8082:6F LD L, A 8083:29 ADD HL, HL 8084:29 ADD HL, HL 8085:29 ADD HL, HL 8086:09 ADD HL, BC 8087:0608 LD B, 08 8089:4A LD C, D 808A: label loop2 808A:7E LD A, (HL) 808B: label inv_instruction_adr 808B:2F CPL 808C:12 LD (DE), A 808D:2C INC L 808E:14 INC D 808F:10F9 DJNZ 808A 8091:1C INC E 8092:C8 RET Z 8093:51 LD D, C 8094:C9 RET 8095: END 8000 Emiting TAP basic loader Emiting TAP from 8000 to 8094
17. Obsah navazujícího článku
V navazujícím článku se podrobněji zmíníme o problematice příznaků (flags), které jsou nastavovány některými instrukcemi a které lze využít jak v podmíněných skocích, tak i v dalších typech strojových instrukcí (rotacích atd). Prozatím jsme totiž používali pouze dva příznaky, a to konkrétně carry (příznak přenosu) a zero (příznak nulovosti), ovšem mikroprocesor Zilog Z80 nabízí i další příznaky, které lze využít jak ve výpočtech s hodnotami se znaménkem, tak i při realizaci některých optimalizací.
Taktéž si stručně přiblížíme FP formát, který byl použit v „kalkulačce“ umístěné v ROM a využívané zejména Sinclair BASICem. Operace s touto kalkulačkou byly postavené na zásobníku (stack) – viz též postarší seriál o programovacím jazyku Forth.
18. Příloha: upravený soubor Makefile pro překlad demonstračních příkladů
Výše uvedené demonstrační příklady i příklady, které již byly popsány v předchozích šesti článcích [1] [2], [3], [4], [5], [6], je možné přeložit s využitím souboru Makefile, jehož aktuální verze vypadá následovně (pro překlad a slinkování je použit assembler Pasmo):
ASSEMBLER := pasmo all: 01.tap 02.tap 03.tap 04.tap 05.tap 06.tap 07.tap 08.tap 09.tap 10.tap \ 11.tap 12.tap 13.tap 14.tap 15.tap 16.tap 17.tap 18.tap 19.tap 20.tap \ 21.tap 22.tap 23.tap 24.tap 25.tap 26.tap 27.tap 28.tap 29.tap 30.tap \ 31.tap 32.tap 33.tap 34.tap 35.tap 36.tap 37.tap 38.tap 39.tap 40.tap \ 41.tap 42.tap 43.tap 44.tap 45.tap 46.tap 47.tap 48.tap 49.tap 50.tap \ 51.tap 52.tap 53.tap 54.tap 55.tap 56.tap 57.tap 58.tap 59.tap 60.tap clean: rm -f *.tap .PHONY: all clean 01.tap: 01-color-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 01-color-attribute.lst 02.tap: 02-blinking-attribute.asm $(ASSEMBLER) -v -d --tap $< $@ > 02-blinking-attribute.lst 03.tap: 03-symbolic-names.asm $(ASSEMBLER) -v -d --tap $< $@ > 03-symbolic-names.lst 04.tap: 04-operators.asm $(ASSEMBLER) -v -d --tap $< $@ > 04-operators.lst 05.tap: 05-better-symbols.asm $(ASSEMBLER) -v -d --tap $< $@ > 05-better-symbols.lst 06.tap: 06-tapbas-v1.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 06-tapbas-v1.lst 07.tap: 07-tapbas-v2.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 07-tapbas-v2.lst 08.tap: 08-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 08-loop.lst 09.tap: 09-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 09-loop.lst 10.tap: 10-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 10-loop.lst 11.tap: 11-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 11-loop.lst 12.tap: 12-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 12-loop.lst 13.tap: 13-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 13-loop.lst 14.tap: 14-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 14-loop.lst 15.tap: 15-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 15-loop.lst 16.tap: 16-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 16-loop.lst 17.tap: 17-loop.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 17-loop.lst 18.tap: 18-cls.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 18-cls.lst 19.tap: 19-print-char-call.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 19-print-char-call.lst 20.tap: 20-print-char-rst.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 20-print-char-rst.lst 21.tap: 21-print-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 21-print-char.lst 22.tap: 22-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 22-print-all-chars.lst 23.tap: 23-print-all-chars.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 23-print-all-chars.lst 24.tap: 24-change-color.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 24-change-color.lst 25.tap: 25-change-flash.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 25-change-flash.lst 26.tap: 26-print-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 26-print-at.lst 27.tap: 27-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 27-print-string.lst 28.tap: 28-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 28-print-string.lst 29.tap: 29-print-colorized-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 29-print-colorized-string.lst 30.tap: 30-print-string-ROM.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 30-print-string-ROM.lst 31.tap: 31-attributes.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 31-attributes.lst 32.tap: 32-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 32-fill-in-vram.lst 33.tap: 33-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 33-fill-in-vram-no-ret.lst 34.tap: 34-fill-in-vram-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 34-fill-in-vram-pattern.lst 35.tap: 35-slow-fill-in-vram.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 35-slow-fill-in-vram.lst 36.tap: 36-slow-fill-in-vram-no-ret.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 36-slow-fill-in-vram-no-ret.lst 37.tap: 37-fill-block.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 37-fill-block.lst 38.tap: 38-fill-block-with-pattern.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 38-fill-block-with-pattern.lst 39.tap: 39-fill-block-optimized.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 39-fill-block-optimized.lst 40.tap: 40-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 40-draw-char.lst 41.tap: 41-draw-any-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 41-draw-any-char.lst 42.tap: 42-block-anywhere.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 42-block-anywhere.lst 43.tap: 43-block-anywhere-rrca.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 43-block-anywhere-rrca.lst 44.tap: 44-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 44-better-draw-char.lst 45.tap: 45-even-better-draw-char.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 45-even-better-draw-char.lst 46.tap: 46-draw-char-at.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 46-draw-char-at.lst 47.tap: 47-draw-char-at-unrolled.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 47-draw-char-at-unrolled.lst 48.tap: 48-incorrect-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 48-incorrect-print-string.lst 49.tap: 49-correct-print-string.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 49-correct-print-string.lst 50.tap: 50-ascii-table.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 50-ascii-table.lst 51.tap: 51-plot-block.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 51-plot-block.lst 52.tap: 52-plot-pixel.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 52-plot-pixel.lst 53.tap: 53-plot-pixel.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 53-plot-pixel.lst 54.tap: 54-plot-pixel-on-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 54-plot-pixel-on-background.lst 55.tap: 55-plot-pixel-on-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 55-plot-pixel-on-background.lst 56.tap: 56-inverse-ascii-table.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 56-inverse-ascii-table.lst 57.tap: 57-plot-pixel-on-inverse-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 57-plot-pixel-on-inverse-background.lst 58.tap: 58-plot-inverse-pixel-on-inverse-background.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 58-plot-inverse-pixel-on-inverse-background.lst 59.tap: 59-configurable-ascii-table.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 59-configurable-ascii-table.lst 60.tap: 60-plot-over.asm $(ASSEMBLER) -v -d --tapbas $< $@ > 60-plot-over.lst
19. Repositář s demonstračními příklady
V tabulce zobrazené pod tímto odstavcem jsou uvedeny odkazy na všechny prozatím popsané demonstrační příklady určené pro překlad a spuštění na osmibitovém domácím mikropočítači ZX Spectrum (libovolný model či jeho klon), které jsou psány v assembleru mikroprocesoru Zilog Z80. Pro překlad těchto demonstračních příkladů je možné použít například assembler Pasmo (viz též úvodní článek):
# | Soubor | Stručný popis | Adresa |
---|---|---|---|
1 | 01-color-attribute.asm | modifikace jednoho barvového atributu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/01-color-attribute.asm |
2 | 02-blinking-attribute.asm | barvový atribut s nastavením bitů pro blikání a vyšší intenzitu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/02-blinking-attribute.asm |
3 | 03-symbolic-names.asm | symbolická jména v assembleru | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/03-symbolic-names.asm |
4 | 04-operators.asm | operátory a operace se symbolickými hodnotami | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/04-operators.asm |
5 | 05-better-symbols.asm | tradičnější symbolická jména | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/05-better-symbols.asm |
6 | 06-tapbas-v1.asm | vygenerování BASICovského loaderu (neúplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/06-tapbas-v1.asm |
7 | 07-tapbas-v2.asm | vygenerování BASICovského loaderu (úplný příklad) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/07-tapbas-v2.asm |
8 | 08-loop.asm | jednoduchá počítaná programová smyčka: naivní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/08-loop.asm |
9 | 09-loop.asm | programová smyčka: zkrácení kódu pro vynulování použitých pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/09-loop.asm |
10 | 10-loop.asm | programová smyčka: optimalizace skoku na konci smyčky (instrukce DJNZ) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/10-loop.asm |
11 | 11-loop.asm | programová smyčka: optimalizace využití pracovních registrů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/11-loop.asm |
12 | 12-loop.asm | programová smyčka: použití pracovního registru IX | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/12-loop.asm |
13 | 13-loop.asm | programová smyčka: použití pracovního registru IY | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/13-loop.asm |
14 | 14-loop.asm | programová smyčka se šestnáctibitovým počitadlem, základní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/14-loop.asm |
15 | 15-loop.asm | programová smyčka se šestnáctibitovým počitadlem, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/15-loop.asm |
16 | 16-loop.asm | použití relativního skoku a nikoli skoku absolutního | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/16-loop.asm |
17 | 17-loop.asm | programová smyčka: inc l namísto inc hl | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/17-loop.asm |
18 | 18-cls.asm | smazání obrazovky a otevření kanálu číslo 2 (screen) přes funkci v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/18-cls.asm |
19 | 19-print-char-call.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce CALL) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/19-print-char-call.asm |
20 | 20-print-char-rst.asm | smazání obrazovky a výpis jednoho znaku na obrazovku přes funkci v ROM (použití instrukce RST) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/20-print-char-rst.asm |
21 | 21-print-char.asm | pouze výpis jednoho znaku na obrazovku bez jejího smazání | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/21-print-char.asm |
22 | 22-print-all-chars.asm | výpis znakové sady znak po znaku (nekorektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/22-print-all-chars.asm |
23 | 23-print-all-chars.asm | výpis znakové sady znak po znaku (korektní verze příkladu) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/23-print-all-chars.asm |
24 | 24-change-color.asm | změna barvových atributů (popředí a pozadí) vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/24-change-color.asm |
25 | 25-change-flash.asm | povolení či zákaz blikání vypisovaných znaků | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/25-change-flash.asm |
26 | 26-print-at.asm | výpis znaku či znaků na určené místo na obrazovce | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/26-print-at.asm |
27 | 27-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (základní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/27-print-string.asm |
28 | 28-print-string.asm | výpis celého řetězce explicitně zapsanou programovou smyčkou (vylepšená varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/28-print-string.asm |
29 | 29-print-colorized-string.asm | výpis řetězce, který obsahuje i řídicí znaky pro změnu barvy atd. | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/29-print-colorized-string.asm |
30 | 30-print-string-ROM.asm | výpis řetězce s využitím služby/subrutiny uložené v ROM ZX Spectra | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/30-print-string-ROM.asm |
31 | 31-attributes.asm | modifikace atributů pro tisk řetězce subrutinou uloženou v ROM | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/31-attributes.asm |
32 | 32-fill-in-vram.asm | vyplnění celé bitmapy barvou popředí, návrat do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/32-fill-in-vram.asm |
33 | 33-fill-in-vram-no-ret.asm | vyplnění celé bitmapy barvou popředí, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/33-fill-in-vram-no-ret.asm |
34 | 34-fill-in-vram-pattern.asm | vyplnění celé bitmapy zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/34-fill-in-vram-pattern.asm |
35 | 35-slow-fill-in-vram.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/35-slow-fill-in-vram.asm |
36 | 36-slow-fill-in-vram-no-ret.asm | pomalé vyplnění celé bitmapy, vizualizace struktury bitmapy, bez návratu do systému | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/36-slow-fill-in-vram-no-ret.asm |
37 | 37-fill-block.asm | vykreslení bloku 8×8 pixelů | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/37-fill-block.asm |
38 | 38-fill-block-with-pattern.asm | vykreslení bloku 8×8 pixelů zvoleným vzorkem | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/38-fill-block-with-pattern.asm |
39 | 39-fill-block-optimized.asm | optimalizace předchozího příkladu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/39-fill-block-optimized.asm |
40 | 40-draw-char.asm | vykreslení znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/40-draw-char.asm |
41 | 41-draw-any-char.asm | podprogram pro vykreslení libovolně zvoleného znaku do levého horního rohu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/41-draw-any-char.asm |
42 | 42-block-anywhere.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/42-block-anywhere.asm |
43 | 43-block-anywhere-rrca.asm | podprogramy pro vykreslení bloku 8×8 pixelů kamkoli na obrazovku, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/43-block-anywhere-rrca.asm |
44 | 44-better-draw-char.asm | vykreslení znaku v masce 8×8 pixelů, vylepšená varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/44-better-draw-char.asm |
45 | 45-even-better-draw-char.asm | posun offsetu pro vykreslení dalšího znaku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/45-even-better-draw-char.asm |
46 | 46-draw-char-at.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/46-draw-char-at.asm |
47 | 47-draw-char-at-unrolled.asm | vykreslení znaku v masce 8×8 pixelů kamkoli na obrazovku | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/47-draw-char-at-unrolled.asm |
48 | 48-incorrect-print-string.asm | tisk řetězce, nekorektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/48-incorrect-print-string.asm |
49 | 49-correct-print-string.asm | tisk řetězce, korektní varianta | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/49-correct-print-string.asm |
50 | 50-ascii-table.asm | tisk několika bloků ASCII tabulky | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/50-ascii-table.asm |
51 | 51-plot-block.asm | vykreslení pixelu verze 1: zápis celého bajtu na pozici pixelu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/51-plot-block.asm |
52 | 52-plot-pixel.asm | vykreslení pixelu verze 2: korektní vykreslení jednoho pixelu, ovšem překreslení celého bajtu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/52-plot-pixel.asm |
53 | 53-plot-pixel.asm | vykreslení pixelu verze 3: vylepšená verze předchozího demonstračního příkladu | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/53-plot-pixel.asm |
54 | 54-plot-pixel-on-background.asm | vykreslení pixelu vůči pozadí (nekorektní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/54-plot-pixel-on-background.asm |
55 | 55-plot-pixel-on-background.asm | vykreslení pixelu vůči pozadí (korektní varianta) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/55-plot-pixel-on-background.asm |
56 | 56-inverse-ascii-table.asm | vykreslení ASCII tabulky inverzní barvou (inkoust vs. papír) | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/56-inverse-ascii-table.asm |
57 | 57-plot-pixel-on-inverse-background.asm | vykreslení pixelů barvou papíru proti inverzní ASCII tabulce | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/57-plot-pixel-on-inverse-background.asm |
58 | 58-plot-inverse-pixel-on-inverse-background.asm | vykreslení pixelů inverzní barvou proti inverzní ASCII tabulce | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm58-plot-inverse-pixel-on-inverse-background.asm/ |
59 | 59-configurable-ascii-table.asm | vykreslení ASCII tabulky buď přímo inkoustem nebo inverzně | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/59-configurable-ascii-table.asm |
60 | 60-plot-over.asm | přibližná implementace příkazu PLOT OVER | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/60-plot-over.asm |
61 | Makefile | Makefile pro překlad a slinkování všech demonstračních příkladů do podoby obrazu magnetické pásky | https://github.com/tisnik/8bit-fame/blob/master/Speccy-asm/Makefile |
20. Odkazy na Internetu
- z80 standalone assembler
https://www.asm80.com/onepage/asmz80.html - The ZX BASIC Compiler
https://www.boriel.com/pages/the-zx-basic-compiler.html - Z80 Assembly programming for the ZX Spectrum
https://www.chibiakumas.com/z80/ZXSpectrum.php - 8-BIT SMACKDOWN! 65C02 vs. Z80: slithy VLOGS #6
https://www.youtube.com/watch?v=P1paVoFEvyc - Instrukce mikroprocesoru Z80
https://clrhome.org/table/ - Z80 instructions: adresní režimy atd.
https://jnz.dk/z80/instructions.html - Z80 Instruction Groups
https://jnz.dk/z80/instgroups.html - Elena, New programming language for the ZX Spectrum Next
https://vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next/ - Sinclair BASIC
https://worldofspectrum.net/legacy-info/sinclair-basic/ - Grafika na osmibitových počítačích firmy Sinclair
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair/ - Grafika na osmibitových počítačích firmy Sinclair II
https://www.root.cz/clanky/grafika-na-osmibitovych-pocitacich-firmy-sinclair-ii/ - HiSoft BASIC
https://worldofspectrum.net/infoseekid.cgi?id=0008249 - YS MegaBasic
https://worldofspectrum.net/infoseekid.cgi?id=0008997 - Beta Basic
https://worldofspectrum.net/infoseekid.cgi?id=0007956 - BASIC+
https://worldofspectrum.net/infoseekid.php?id=0014277 - Spectrum ROM Memory Map
https://skoolkit.ca/disassemblies/rom/maps/all.html - Goto subroutine
https://skoolkit.ca/disassemblies/rom/asm/7783.html - Spectrum Next: The Evolution of the Speccy
https://www.specnext.com/about/ - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/ - Programovací jazyk BASIC na osmibitových mikropočítačích
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich/ - Programovací jazyk BASIC na osmibitových mikropočítačích (2)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-2/#k06 - Programovací jazyk BASIC na osmibitových mikropočítačích (3)
https://www.root.cz/clanky/programovaci-jazyk-basic-na-osmibitovych-mikropocitacich-3/ - Sinclair BASIC (Wikipedia CZ)
http://cs.wikipedia.org/wiki/Sinclair_BASIC - Assembly Language: Still Relevant Today
http://wilsonminesco.com/AssyDefense/ - Programovani v assembleru na OS Linux
http://www.cs.vsb.cz/grygarek/asm/asmlinux.html - Why Assembly Language Programming? (Why Learning Assembly Language Is Still a Good Idea)
https://wdc65×x.com/markets/education/why-assembly-language-programming/ - Low Fat Computing
http://www.ultratechnology.com/lowfat.htm - Assembly Language
https://www.cleverism.com/skills-and-tools/assembly-language/ - Why do we need assembly language?
https://cs.stackexchange.com/questions/13287/why-do-we-need-assembly-language - Assembly language (Wikipedia)
https://en.wikipedia.org/wiki/Assembly_language#Historical_perspective - Assembly languages
https://curlie.org/Computers/Programming/Languages/Assembly/ - vasm
http://sun.hasenbraten.de/vasm/ - B-ELITE
https://jsj.itch.io/b-elite - ZX-Spectrum Child
http://www.dotkam.com/2008/11/19/zx-spectrum-child/ - Speccy.cz
http://www.speccy.cz/ - Planet Sinclair
http://www.nvg.ntnu.no/sinclair/ - World of Spectrum
http://www.worldofspectrum.org/ - The system variables
https://worldofspectrum.org/ZXBasicManual/zxmanchap25.html - ZX Spectrum manual: chapter #17 Graphics
https://worldofspectrum.org/ZXBasicManual/zxmanchap17.html - Why does Sinclair BASIC have two formats for storing numbers in the same structure?
https://retrocomputing.stackexchange.com/questions/8834/why-does-sinclair-basic-have-two-formats-for-storing-numbers-in-the-same-structu - Plovoucí řádová čárka na ZX Spectru
https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/#k05 - Norma IEEE 754 a příbuzní: formáty plovoucí řádové tečky
https://www.root.cz/clanky/norma-ieee-754-a-pribuzni-formaty-plovouci-radove-tecky/#k05