Prilis nerozumim u te kapitoly 5 co to ma delat jak jsou tam voleny konstanty pro SP. Je to fakt spatne citelne.
SCREEN_ADR+32*9 Proc???
SCREEN_ADR+32*9 =
SCREEN_ADR+256+32 =
SCREEN_ADR+druhy pixelovy radek + druhy znakovy radek (pokud pocitam od 1)
A ta posledni konstanta sp= SCREEN_ADR+SECOND_SCREEN_BLOCK+2048 je trosku nesmysl. Ve vysledku pak vyjde 0x9000 jak je videt v tom vypise.
Lepsi by bylo po DI si ulozit puvodni hodnotu SP a tu pak vratit tesne pred EI.
Pro ten presun bloku instrukci je dulezite si uvedomit par veci:
1. Zjistit si kolik bajtu najednou v registrech prenasim. A to je 2*8=16 bajtu. Polovina sirky obrazovky.
2. POP instrukce nacte nizsi bajt na ktery ukazuje SP a pak zvysi SP a nacte vyssi bajt a nakonec znovu zvysi SP (celkove o 2). Takze na zacatku staci dat SP=zdroj (vcetne) a po presunu jednoho bloku udelat SP=zdroj+16, potreti SP=zdroj+2*16 atd.
3. PUSH instrukce prvne snizi hodnotu SP, pote ulozi horni bajt a pak znovu snizi SP a ulozi nizsi bajt. Takze u cile musi ukazovat SP za konec bloku. SP=cil+16. Dalsi cilovy blok bude SP=cil+2*16, potreti to bude SP=cil+3*16 atd.
Pak ten presun muzeme chapat tak, ze jen u cile budeme ukazovat neustale o 16 vic nez u zdroje. A postupne pricitat k obema +16.
Muzeme to delat po blocich i od konce. SP u cile bude zase ukazovat 1 bajt za posledni bajt dat a SP zdroje jak cil-16. A k obema postupne odcitat 16.
Pokud teda nemame nejak otoceny data u zdroje, protoze se to snazime udelat nejak ve smycce.
Ukazka jak by to mohlo vypadat pro kopirovani hornich 4 pixelu prvniho radku a 16 znaku. Takze bude videt kopie jen leve pulky prvniho radku a jeste jen horni pulka znaku. Celkove 7 znaku protoze prvni je mezera a jeste za kazdym znakem se zase tiskne mezera.
Vynechal jsem prvni pixelovy radek, protoze je prazdny. A kazdy dalsi pixelovy radek je o 256 bajtu nize.
; Example #90:
; Print ASCII table on screen + copy it to second part of screen using stack.
SCREEN_ADR equ $4000
SCREEN_BLOCK_SIZE equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
CHAR_ADR equ $3c00
ENTRY_POINT equ $8000
SIZE_MOVE equ 8*2
org ENTRY_POINT
; Vstupní bod celého programu
start:
call fill_in_screen ; vyplnění obrazovky ASCII tabulkami
di
ld (save_sp+1),sp
ld sp, SCREEN_ADR+256 ; vynechame prazdny nejhornejsi (nulty) pixelovy radek v znaku a zacneme az dalsim
pop af
pop bc
pop de
pop hl
exx
ex af, af'
pop af
pop bc
pop de
pop hl
ld sp, SECOND_SCREEN_BLOCK+256+SIZE_MOVE
push hl
push de
push bc
push af
exx
ex af, af'
push hl
push de
push bc
push af
ld sp, SCREEN_ADR+2*256
pop af
pop bc
pop de
pop hl
exx
ex af, af'
pop af
pop bc
pop de
pop hl
ld sp, SECOND_SCREEN_BLOCK+2*256+SIZE_MOVE
push hl
push de
push bc
push af
exx
ex af, af'
push hl
push de
push bc
push af
ld sp, SCREEN_ADR+3*256
pop af
pop bc
pop de
pop hl
exx
ex af, af'
pop af
pop bc
pop de
pop hl
ld sp, SECOND_SCREEN_BLOCK+3*256+SIZE_MOVE
push hl
push de
push bc
push af
exx
ex af, af'
push hl
push de
push bc
push af
save_sp:
ld sp, 0
ei
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ů
ret ; návrat z podprogramu
draw_ascii_table:
; Vytištění ASCII tabulky
;
; vstupy:
; DE - adresa v obrazové paměti pro vykreslení znaku
ld a, ' ' ; kód vykreslovaného znaku
next_char:
push af ; uschovat akumulátor na zásobník
call draw_char ; zavolat subrutinu pro vykreslení znaku
ld a, ' ' ; vykreslit za znakem mezeru
call draw_char ; zavolat subrutinu pro vykreslení znaku
pop af ; obnovit akumulátor ze zásobníku
inc a ; ASCII kód dalšího znaku
cp ' ' + 96 ; jsme již na konci ASCII tabulky?
jr nz, next_char ; ne? potom pokračujeme
ret ; návrat z podprogramu
draw_char:
; Vytištění jednoho znaku na obrazovku
;
; vstupy:
; A - kód znaku pro vykreslení
; DE - adresa v obrazové paměti pro vykreslení znaku
;
; výstupy:
; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
;
; změněné registry:
; všechny
ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků
ld h, c ; C je nulové, protože CHAR_ADR=0x3c00
ld l, a ; kód znaku je nyní ve dvojici HL
add hl, hl ; 2x
add hl, hl ; 4x
add hl, hl ; 8x
add hl, bc ; přičíst bázovou adresu masek znaků
ld b, 8 ; počitadlo zapsaných bajtů
ld c, d
loop:
ld a,(hl) ; načtení jednoho bajtu z masky
ld (de),a ; zápis hodnoty na adresu (DE)
inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
inc d ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
inc e
ret z ; D+=8,E=E+1=0
ld d, c
ret ; D=D,E=E+1
end ENTRY_POINT
PS: Koukam, ze jsi se nepokusil to dostat do smycky.
Takze tady je ukazka jak z toho udelat smycku. Kopiruje se prvni tretina do druhe. Vysledek je prevraceny, protoze kvuli rychlosti se jen obnovuje SP. A pak se kopiruje prevracena druha tretina do treti tretiny. Ta uz vypada normalne jako prvni.
; Example #90:
; Print ASCII table on screen + copy it to second part of screen using stack.
SCREEN_ADR equ $4000
SCREEN_BLOCK_SIZE equ 32*64
SECOND_SCREEN_BLOCK equ SCREEN_ADR+SCREEN_BLOCK_SIZE
THIRD_SCREEN_BLOCK equ SCREEN_ADR+2*SCREEN_BLOCK_SIZE
CHAR_ADR equ $3c00
ENTRY_POINT equ $8000
SIZE_MOVE equ 8*2
org ENTRY_POINT
; Vstupní bod celého programu
start:
call fill_in_screen ; vyplnění obrazovky ASCII tabulkami
ld ixl, SCREEN_BLOCK_SIZE/16 ; 3:11
ld hl, SCREEN_ADR ; 3:10 na zacatek zdroje
ld de, THIRD_SCREEN_BLOCK ; 3:10 za konec cile
call copy
ld ixl, SCREEN_BLOCK_SIZE/16 ; 3:11
ld hl, SECOND_SCREEN_BLOCK ; 3:10 na zacatek zdroje
ld de, THIRD_SCREEN_BLOCK+SCREEN_BLOCK_SIZE ; 3:10 za konec cile
call copy
ld HL, 0x2758 ; 3:10 aby basic nerval tak vratim HL'
exx ; 1:4
ret ; navrat do basicu
; HL = from, DE = to+size, ixl = size/16
copy:
ld (loop_from+1),hl ; 3:16
ld (loop_to+1),de ; 4:20
di
ld (ret_sp+1),sp ; 4:20
loop_from:
ld sp, 0 ; 3:10 vynechame prazdny nejhornejsi (nulty) pixelovy radek v znaku a zacneme az dalsim
pop af
pop bc
pop de
pop hl
exx
ex af, af'
pop af
pop bc
pop de
pop hl
ld (loop_from+1),sp ; 4:20
loop_to:
ld sp, 0 ; 3:10
push hl
push de
push bc
push af
exx
ex af, af'
push hl
push de
push bc
push af
ld (loop_to+1),sp ; 4:20
dec ixl ; 2:8
jp nz, loop_from ; 3:10
ret_sp:
ld sp, 0
ei
ret
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ů
ret ; návrat z podprogramu
draw_ascii_table:
; Vytištění ASCII tabulky
;
; vstupy:
; DE - adresa v obrazové paměti pro vykreslení znaku
ld a, ' ' ; kód vykreslovaného znaku
next_char:
push af ; uschovat akumulátor na zásobník
call draw_char ; zavolat subrutinu pro vykreslení znaku
ld a, ' ' ; vykreslit za znakem mezeru
call draw_char ; zavolat subrutinu pro vykreslení znaku
pop af ; obnovit akumulátor ze zásobníku
inc a ; ASCII kód dalšího znaku
cp ' ' + 96 ; jsme již na konci ASCII tabulky?
jr nz, next_char ; ne? potom pokračujeme
ret ; návrat z podprogramu
draw_char:
; Vytištění jednoho znaku na obrazovku
;
; vstupy:
; A - kód znaku pro vykreslení
; DE - adresa v obrazové paměti pro vykreslení znaku
;
; výstupy:
; DE - adresa v obrazové paměti pro vykreslení dalšího znaku
;
; změněné registry:
; všechny
ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků
ld h, c ; C je nulové, protože CHAR_ADR=0x3c00
ld l, a ; kód znaku je nyní ve dvojici HL
add hl, hl ; 2x
add hl, hl ; 4x
add hl, hl ; 8x
add hl, bc ; přičíst bázovou adresu masek znaků
ld b, 8 ; počitadlo zapsaných bajtů
ld c, d
loop:
ld a,(hl) ; načtení jednoho bajtu z masky
ld (de),a ; zápis hodnoty na adresu (DE)
inc l ; posun na další bajt masky (nemusíme řešit přetečení do vyššího bajtu)
inc d ; posun na definici dalšího obrazového řádku
djnz loop ; vnitřní smyčka: blok s osmi zápisy
inc e
ret z ; D+=8,E=E+1=0
ld d, c
ret ; D=D,E=E+1
end ENTRY_POINT
Spocital jsem takty a ten prepis do smycky trva 84+ixl*262=84+16.375 taktu na bajt.
Takze je to pomalejsi nez ldi.
Pokud se to rozvine 2x a ve smycce se prenese 32 bajtu tak uz je to 84+ixl*506=84+15.8125 taktu na bajt, to uz zacina byt rychlejsi.
Pokud by se to jeste zdvojnasobilo a prenaselo ve smycce 64 bajtu tak je to 15.53125 bajtu na takt. Stale relativne nic moc.
Pokud by se prenaselo misto 16 bajtu 18 bajtu (+iy) tak by to trvalo 291 bajtu.
loop_from:
ld sp, 0 ; 3:10
pop af ; 1:10
pop bc ; 1:10
pop de ; 1:10
pop hl ; 1:10
pop iy ; 2:14
exx ; 1:4
ex af, af' ; 1:4
pop af ; 1:10
pop bc ; 1:10
pop de ; 1:10
pop hl ; 1:10
ld (loop_from+1),sp ; 4:20
loop_to:
ld sp, 0 ; 3:10
push hl ; 1:11
push de ; 1:11
push bc ; 1:11
push af ; 1:11
ex af, af' ; 1:4
exx ; 1:4
push iy ; 2:15
push hl ; 1:11
push de ; 1:11
push bc ; 1:11
push af ; 1:11
ld (loop_to+1),sp ; 4:20
dec ixl ; 2:8
jp nz, loop_from ; 3:10
;[43:291]
To je 16.1666 taktu na bajt
2x rozvinuta 15.6666 taktu na bajt
4x rozvinuta 15.4166 taktu na bajt.
Za cenu ze ten pocet neni mocnina 2.
11. 5. 2023, 05:20 editováno autorem komentáře
Tak existuje jeste o 3 takty rychlejsi varianta, ktera neprevaci data
Kopiruje se od nejvyssich adres po blocich smerem k nizsim.
V podstate PUSH posouva SP dolu a SP pro zdroj se dopocita tak, ze se SP jeste snizi o rozdil mezi zdrojem a cilem a jeste o 16 bajtu k tomu.
; HL = cil-zdroj-16, DE = cil+size, ixl = size/16
copy:
ld (loop_to+1),de ; 4:20
ld (loop_from+1),hl ; 3:16
di ; 1:4
ld (ret_sp+1),sp ; 4:20
ex de, hl ; 1:4
ld sp, hl ; 1:6
loop_from:
ld hl, 0 ; 3:10 cil-zdroj-16
add hl, sp ; 3:11
ld sp, hl ; 1:6
pop af ; 1:10
pop bc ; 1:10
pop de ; 1:10
pop hl ; 1:10
exx ; 1:4
ex af, af' ; 1:4
pop af ; 1:10
pop bc ; 1:10
pop de ; 1:10
pop hl ; 1:10
loop_to:
ld sp, 0 ; 3:10
push hl ; 1:11
push de ; 1:11
push bc ; 1:11
push af ; 1:11
exx ; 1:4
ex af, af' ; 1:4
push hl ; 1:11
push de ; 1:11
push bc ; 1:11
push af ; 1:11
ld (loop_to+1),sp ; 4:20
dec ixl ; 2:8
jp nz, loop_from ; 3:10
ret_sp:
ld sp, 0 ; 3:10
ei ; 1:4
ret ; 1:10
;[58:94+ixl*259] cca 16.1875 per bytes
Pri rozbaleni 2x by to melo mit 15.625 taktu na bajt
Pri rozbaleni 4x je to 15.34375 taktu na bajt
Diky za feedback.
Ten finalni SP - ja jsem to puvodne chtel nechat na treti casti obrazovky a vysvetlil, jak se po povoleni preruseni najednou zacnou na obrazovce ukazovat nahodne pixely. Ale uz se to do clanku nevlezlo (jasne, idealni je potom SP vratit tam kde byl a teprve potom udelat EI).
Smycka - tam jsem se nedostal pod cca 16 cyklu/bajt, takze to vychazelo zhruba stejne jak o rozbalena smycka s LDI - a tudiz nema smysl si komplikovat zivot zasobnikem :) Podle me se to fakt hodi jen ve chvili, kdy dobehne demo, maji zacit scrollovane titkulky s credits, pozdravy dalsim skupinam a tak - tehdy se muze premazat cela pamet s puvodnim kodem a nechat si tam vygenerovat ten "zasobnikovy" kod (uff ja vim ze delas ve Forthu, proto to "zasobnikovy" ber s velkou rezervou :)
Konkrétně na ZX Spectru se nějakých pár tisíc cyklů dá ušetřit, pokud se kopíruje z backbufru do obrazovky.
Vtip je v tom, že se nahradí realtivně drahý add hl, de 8 bitovým inc.
Vypadá to takhle nějak:
;rozepsat 8 krat
ld sp, ix ;10
pop hl ;10
pop de ;10
pop bc ;10
pop af ;10
exx ;4
ex af, af' ;4
pop hl ;10
pop de ;10
pop bc ;10
pop af ;10 = 98
ld sp, iy ;10
push af ;11
push bc ;11
push de ;11
push hl ;11
ex af, af' ;4
exx ;4
push af ;11
push bc ;11
push de ;11
push hl ;11 = 106
inc hx ;8 pro osmou iteraci vynechat
inc hy ;8 = 16 pro osmou iteraci vynechat
; = 220 / 16 = 13.75
Takhle se zkopíruje 8 polovin pixelových rádků obrazovky nad sebou, pak se zkopíruje dalších 8 polovin pixelových řádků obrazovky podobným kódem, akorát místo inc h* tam bude dec h*. Ten zisk 2.25 cycklú na bajt dává pro 256 přenesených bajtů rezervu asi 550 cyclů. Část z těch ušetřených cyclů se utratí na úpravu pointrů pro dalších 256 bajtů, část zbyde.
Viděl jsem kopírovací rutinu, která "počítala" další adresu zásobniku ve smyčce pomocí instrukce set x,h a res x,h. Viděl jsem i rutinu, která přenášela pomocí zásobníku 256 bajtů, a pro další iteraci přímo v kódu přepsala jen vyšší bajt pointrů. Ale všechno jsou to speciální případy pro kopírování do obrazovky. To jen pro doplnění že LDI lze ve speciálních případech překonat.