Vlákno názorů k článku Kopie datových bloků na ZX Spectru s využitím zásobníku od _dw - Prilis nerozumim u te kapitoly 5 co to...

  • Článek je starý, nové názory již nelze přidávat.
  • 11. 5. 2023 4:32

    _dw

    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+SE­COND_SCREEN_BLOC­K+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
  • 11. 5. 2023 5:20

    _dw

    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

  • 11. 5. 2023 6:11

    _dw

    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

  • 11. 5. 2023 19:06

    Pavel Tišnovský
    Zlatý podporovatel

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

  • 12. 5. 2023 0:54

    Korporátní lopata

    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.