Názory k článku Kopie datových bloků na ZX Spectru s využitím zásobníku

  • Č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.

  • 11. 5. 2023 7:25

    atarist

    Me zaujalo to DMA, tedy Z80-DMA. To byl asi cip, so se primo pripojil na sbernice, ziskal pres IO instrukce pro prenos a potom pres BUSRQ (???) odpojil CPU i ULU od sbernice?

  • 11. 5. 2023 7:26

    atarist

    Jako asi nejhorsi je odpojeni ULA ne? (tedy mam za to, ze ta proste pres sbernici porad zastavuje CPU a cuca si data z VRAM?)

  • 11. 5. 2023 19:07

    Pavel Tišnovský
    Zlatý podporovatel

    Diky za odkaz. Ono je to skutecne udelany chytre, ale stejne inicializace DMA si nejaky ty stovky cyklu vezme, takze by to bylo jen na delsi bloky.