Jeste jsem to nedocetl do konce.
Budu uplne ignorovat pouzivani djnz pro pripady, kdy se to na nahradit jr, protoze se to pouzije 256x a uvnitr smycky je nejaky registr menen tak, ze nastavi zero flag prave a jedine ve stejnou dobu co by se ukoncila smycka.
Ale dovolil jsem si upravit tu rutinu pro 14. kapitolu. Z puvodnich 53 bajtu na 38 bajtu.
Jedna z podminek je bud pouzit nejaka makra a nebo slevit z prehlednosti a nepouzivat nejake globalni konstanty jako je adresa tech fontu. Protoze puvodni rutina uplne ignoruje ze je polozena v pameti na adresu delitelnou 256...
Pak jsem upravil tu funkci pro vypsani znaku tak, aby vracela v DE adresu nasledujiciho znaku (vcetne prelezeni pres tretiny obrazovky), prijde me to jako pouzitelnejsi navrh, nez muset po kazdem vypsanem znaku hledat kde je nasledujici adresa.
Puvodni:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld de, SCREEN_ADR ; 3:10 adresa pro zápis ld a, 'A' ; 2:7 kód vykreslovaného znaku call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku ld de, SCREEN_ADR+1 ; 3:10 adresa pro zápis ld a, 'B' ; 2:7 kód vykreslovaného znaku call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku ld de, SCREEN_ADR+128+31 ; 3:10 adresa pro zápis ld a, '?' ; 2:7 kód vykreslovaného znaku call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku finish: ret ; 1:10 ukončit program draw_char: ld hl, CHAR_ADR ; 3:10 adresa, od níž začínají masky znaků ld b, 0 ; 2:7 ld c, a ; 1:4 kód znaku je nyní ve dvojici BC sla c ; 2:8 rl b ; 2:8 2*bc sla c ; 2:8 rl b ; 2:8 4*bc sla c ; 2:8 rl b ; 2:8 vynásobení BC osmi add hl, bc ; 1:11 přičíst adresu k offsetu masky znaku ld b, 8 ; 2:7 počitadlo zapsaných bajtů loop: ld a, (hl) ; 1:7 načtení jednoho bajtu z masky ld (de), a ; 1:7 zápis hodnoty na adresu (DE) inc hl ; 1:6 posun na další bajt masky inc d ; 1:4 posun na definici dalšího obrazového řádku djnz loop ; 2:8/13 vnitřní smyčka: blok s osmi zápisy ret ; 1:10 end ENTRY_POINT
Nove:
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld DE, SCREEN_ADR ; 3:10 adresa pro zápis ld A, 'A' ; 2:7 kód vykreslovaného znaku call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku ld A, 'B' ; 2:7 kód vykreslovaného znaku call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku ld E, SCREEN_ADR+4*32+31 ; 2:7 adresa pro zápis (jsme stale v prvni tretine obrazovky) ld A, '?' ; 2:7 kód vykreslovaného znaku ; call draw_char ; 3:17 zavolat subrutinu pro vykreslení znaku finish: ; ret ; 1:10 ukončit program ; Input: DE = adresa znaku pro zapis, A = znak ; Output: DE = adresa nasledujiciho znaku (muze pretect do atributu) ; Pollutes: B = 0, C = hi(old_DE),HL draw_char: ld BC, 0x3C00 ; 3:10 adresa, od níž začínají masky znaků ld H, C ; 1:4 ld L, A ; 1:4 kód znaku je nyní ve dvojici HL add HL, HL ; 1:11 2x add HL, HL ; 1:11 4x add HL, HL ; 1:11 8x add HL, BC ; 1:11 ld B, 0x08 ; 2:7 počitadlo zapsaných bajtů ld C, D ; 1:4 loop: ld A,(HL) ; 1:7 načtení jednoho bajtu z masky ld (DE),A ; 1:7 zápis hodnoty na adresu (DE) inc L ; 1:4 posun na další bajt masky inc D ; 1:4 posun na definici dalšího obrazového řádku djnz loop ; 2:8/13 vnitřní smyčka: blok s osmi zápisy ld D, C ; 1:4 inc DE ; 1:6 ret ; 1:10 end ENTRY_POINT
PS: Jinak me uplne dostal ten vypis u jr/djnz, kdy jsem si myslel ze 0 ukazuje pred tu instrukci a ona ukazuje za ni. Uplne jsem si to pletl s $, ktera ukazuje prave pred (na prave provadenou/zapisovanou) instrukci. Takze $ je neco jako 0xFE.
Muzes pouzit cokoliv. Pisi to sem, protoze si myslim ze to jde udelat lepe, ne abych trolil. Jsou to jen kousky kodu, ktere uz urcite napsalo spoustu lidi prede mnou a netoci se tu zadne velke penize, takze o nic nejde. Z80 nikoho v podstate uz nezajima asi jako historicky serm. .)
A nektery kod je na prvni pohled podezrely, to citis hned, ze to pujde napsat lepe, na to by prisel kazdy. Staci aby tam byla nejaka instrukce, jako jsou dvoubajtove s osmi takty a hned to pouta pozornost.
U nekterych veci citim, ze je to spatne, ale nejde to dokazat. Jako napr.
; B - x-ová souřadnice (ve znacích, ne pixelech) ; C - y-ová souřadnice (ve znacích, ne pixelech)
By melo byt prohozene, X v C a Y v B.
Ted to vypada ze na tom nezalezi, ze je to jen skryta vada. Pokud by si chtel napsat rutinu, kde by vstup mohl mit X vic jak 31 a povazovalo by se to za X = X mod 32, Y = (Y + X /32) mod 24. Tak uz by to bylo poznat.
Je to stejna chyba (navrhu) jako ze DJNZ ma pocitadlo v B a ne v C. Tam to ale jde dokazat hned, ze je to spatne mit bity s mensi vahou ve vyssim registru.
PS: Pak jsem si nevsiml, ze bys/nekdo diskutoval nad tim, jakou strategii zvolit pro obsazeni registru, a ktere se budou ve funkcich uchovavat, zda vsechny a nebo jen nektere. Podle me je to dulezita vec mit jednotne pravidla, pokud pises neco co neni na par bajtu.
Ja jsem prvne ukladal vse na zasobnik a vstupy a vystupy mel v registrech jak se to zrovna hodilo... Variant je ale vic, jestli napriklad chces uchovavat i vstup, protoze se ta fce vola opakovane s nepatrne odlisnym vstupem.
Forth me ale naucil, ze to jde i uplne jinak, dokonce tak, ze proste i vis nejen co musis uchovat a co je volne, ale i kde mas parametry, bez ohledu na pocet a nemusis to pro kazdou fci hledat.
Na druhou stranu pokud pouzivas zasobnik "ciste" pro data, tak si komplikujes volani fci a ukladani pocitadel smycek.
Kdyz to michas tak, mas taky problemy a kdyz mas data na pevnych adresach tak taky.
Tohle uz nekdo musel pro Z80 resit. Nekdo musel prijit na to, ze:
- 1 bitovy vystup je nejlepsi mit v carry protoze... setris registr a muzes vysledek pouzit pro skoky, scitani s prenosem atd.?
- 8 bitovy vystup je nejlepsi mit v A protoze... je velka sance ze uz byl akumulator pro vypocet pouzit a nemusis ho teda uz nastavovat a ani ho nemusis uchovavat kdyz byl menen ale je to vystup?
- 16 bitovy vystup v HL protoze... viz 8 bitovy vstup.
Nebo je to nejak uplne jinak a ta funkce neni "nezavisla", ale se pouziva jen jako pomocna fce nejake jine a ta uz ma registry nastavene takhle a takhle takze...
Mozna staci jakakoliv pravidla, ale hlavne aby byla? I spatna jsou lepsi nez zadna?
Mozna by sla pouzit nejaka statistika verejnych zdrojaku? Urcite na to existuji nejake strategie. Jen maly navrh na tema clanku.
diky. pro jistotu se ptam, aby nebyl problem. Jinak jasne - spoustu tech veci zkusime optimalizovat, ale snazim se aspon prvni verzi udelat trosku citelne a snad i prehledne. Co jsem se dival na kod z nekterych her (i na 6502), tak to uz jsou nekdy brutalne optimalizovane veci, kde se bez popisu proc a jak se to dela, neni moc sance to pochopit.
(btw budu muset dohledat triky, okolo FP, tam toho bylo hodne a byly dost silene :)
Jak ctu kapitolu 15. tak jsem si uvedomil, ze jsem udelal chybu ve vypoctu prechodu adresy znaku mezi tretinama.
Nemohu komentovat ani opravit komentar co nebyl (jeste) schvalen takze pisi novy prispevek. Opravena verze je o bajt delsi a takt rychlejsi (zmena je jen pod djnz loop).
loop: ld A,(HL) ; 1:7 načtení jednoho bajtu z masky ld (DE),A ; 1:7 zápis hodnoty na adresu (DE) inc L ; 1:4 posun na další bajt masky inc D ; 1:4 posun na definici dalšího obrazového řádku djnz loop ; 2:8/13 vnitřní smyčka: blok s osmi zápisy inc E ; 1:4 ret z ; 1:5/11 D+=8,E=E+1=0 ld D, C ; 1:4 ret ; 1:10 D=D,E=E+1
V 15. kapitole rutina calc_block_address se ta taky vylepsit, kdyz misto rra se pouzije rrca.
Puvodni:
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 ; 1:4 and %00000111 ; 2:7 pouze spodní tři bity y-ové souřadnice (řádky 0..7) rra ; 1:4 rra ; 1:4 rra ; 1:4 rra ; 1:4 nyní jsou čísla řádků v horních třech bitech or B ; 1:4 připočítat x-ovou souřadnici ld L, A ; 1:4 máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld A, C ; 1:4 y-ová souřadnice and %00011000 ; 2:7 dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; 2:7 "posun" do obrazové paměti (na 0x4000) ld H, A ; 1:4 máme horní bajt adresy ; 00 01 00 Y4 Y3 00 00 00 ret ; 1:10 návrat z podprogramu
Nove:
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 ; 1:4 rrca ; 1:4 rrca ; 1:4 rrca ; 1:4 and %11100000 ; 2:7 nyní jsou čísla řádků v horních třech bitech or B ; 1:4 připočítat x-ovou souřadnici ld L, A ; 1:4 máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld A, C ; 1:4 y-ová souřadnice and %00011000 ; 2:7 dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; 2:7 "posun" do obrazové paměti (na 0x4000) ld H, A ; 1:4 máme horní bajt adresy ; 00 01 00 Y4 Y3 00 00 00 ret ; 1:10 návrat z podprogramu
V 16. kapitole se uvadi kod:
ld b, 15 ; 2:7 x-ová souřadnice ld c, 12 ; 2:7 y-ová souřadnice
Mene prehledne, ale efektivnejsi je:
ld bc, 256*15+12 ; 3:10 b=x-ová souřadnice,c=y-ová souřadnice
Nacitani konstant byva jedno z nejtezsich veci co lze na Z80 delat.
Staci treba chtit na zasobnik ulozit par bajtu a slozitost jak to napsat nejefektivneji zacne exponencionalne rust.
ld HL,xxxx
push HL
ld HL,xxxx
push HL
...
nebo
ld BC,xxxx
ld HL,xxxx
push HL
inc H
push HL
ld L,C
push HL
push BC
dec HL
push HL
...
kombinaci je prilis mnoho a jsou zavisle jakou posloupnost chceme ulozit.
Na ukazku ulozeni pouze 3 word slov na zasobnik Forthu (je to "normalni" zasobnik pres SP + predposledni hodnota je v DE + a posledni v HL)
Na zacatku ulozi prvni hodnotu a pak uz ma informaci co v tom registru je, takze to muze mozna vyuzit...
push DE ; 1:11 0x4532 0x3345 0x4433 ( -- 0x4532 0x3345 0x4433 ) # HL E HL D push HL ; 1:11 0x4532 0x3345 0x4433 ld HL, 0x4532 ; 3:10 0x4532 0x3345 0x4433 push HL ; 1:11 0x4532 0x3345 0x4433 ld E, H ; 1:4 0x4532 0x3345 0x4433 lo before E = H = 0x45 dec H ; 1:4 0x4532 0x3345 0x4433 inc L ; 1:4 0x4532 0x3345 0x4433 ld D, L ; 1:4 0x4532 0x3345 0x4433 hi after D = L = 0x33 ; seconds: 0 ;[10:59]
push DE ; 1:11 0x45FF 0xFF00 0x4600 ( -- 0x45FF 0xFF00 0x4600 ) # HL D HL E push HL ; 1:11 0x45FF 0xFF00 0x4600 ld HL, 0x45FF ; 3:10 0x45FF 0xFF00 0x4600 push HL ; 1:11 0x45FF 0xFF00 0x4600 ld D, L ; 1:4 0x45FF 0xFF00 0x4600 hi before D = L = 0xFF inc HL ; 1:6 0x45FF 0xFF00 0x4600 0x4600 = 0x45FF+1 ld E, L ; 1:4 0x45FF 0xFF00 0x4600 lo after E = L = 0x00 ; seconds: 1 ;[ 9:57]
Adresovací režimy jsou samozřejmě super, jak na 6502, tak na 6809. Obzvlášť na 6502 je zajímavé, s jakým málem (2 8-bitové registry) se dají vykouzlit opravdu zajímavé a silné adresovací režimy. Mě se filozofie 6502 líbí, jen mi prostě příjde, že původní verze je až moc očesaná a až právě 65c02 je dotažená.
Což je tedy asi jediný 8bitový procesor, který se podařilo opravdu vylepšit. Z osmibitových následovníku jak 68**, tak *80 jsem spíš rozpačitej.
2. 3. 2023, 18:59 editováno autorem komentáře
Elite neplní VRAM skrz PUSH, jen si tak čistí backbuffer. Samotné kopírování z backbufru do VRAM má na svědomí série instrukcí LDI. Stejným způsobem se to dělá už ve hře 3D Starstrike z roku 1984.
Asi první kdo píše do VRAM přímo skrz PUSH bude hra Ghosts&Goblins, co přesně se děje během vykreslování obrazovky je docela dobře rozebráno tady: http://www.emix8.org/ggdisasm/
Nicméně G&G používá specializované podprogramy pro každou šířku objektu, o rok mladší Cobra si už tu sekvenci PUSH instrukcí generuje pro každý frame znovu a znovu.
https://www.youtube.com/watch?v=T-NMhQc_v34&t=443s
Nektere nove hry ani nevypadaji ze jsou spectracke.
https://zxonline.net/game/nothing/
nebo
https://rgb.yandex.ru/2019#drift
Za ty roky to neni jen vyvoj v technice programovani, je to i vyvoj v tech samotnych hrach. Hraci zleniveli... .) Postavam ve hrach pribylo vic animaci pohybu atd.
Rád bych vyjádřil poděkování za tenhle seriál. Kéž bych něco takového měl k dispozici před třiceti lety, kdy jsem tohle objevoval. :)
Konečně jsem taky pochopil ono kouzlo překřížení tří adresních drátů video RAM. Slyšel jsem o tom legendu, že je to z důvodu zjednodušení logického obvodu uvnitř ULA, který generuje video. Ale tohle vysvětlení, že se díky tomu dají chytře využít rychlé instrukce pro inkrementaci vyššího bajtu adresy dává mnohem větší smysl.
Těším se na další díl!
Ve skutečnosti je ten formát částečně vynucený. ULA čte dvojice bajtů pixely a atributy v takzvaném page modu, na jednu adresu RAS se použijí dvě CAS adresy, je to rychlejší než dva plné cykly (2x(RAS+CAS)), takže Sinclair mohl použít pomalejší (= levnější) paměti. Nicméně to že jsou řádky pod sebou vzdálené právě o 256 bajtů je zřejmě úmysl, v případě 16KB pamětí šlo použít jakýkoliv násobek 128.
U Z80 mi vadí, že instrukce LDI má 2 bajty, jako jednobajtová mohla být rychlejší, a je to jedna ze zásadních instrukcí. Také nemusela snižovat registr BC, to je zbytečné, a nejspíš (nevím to jistě) také nějaký takt zbytečně ubere. Dovedl bych si naopak představit jednobajtové instrukce LDI, LDI2 a LDI4, pro přenos příslušného počtu bajtů, to by bylo úplně ideální, aby lidé nešaškovali do dnešního dne se stackem :-)