Diky za clanek. Dnes to bylo pro me fakt prekvapeni cist... .)
Skoda jen ze u toho prikladu v kapitole 14. se to nedotahlo do konce.
Myslim tim ze pokud potrebuji nejak uklizet u fce tak osobne preferuji kdyz to dela primo ta funkce sama jednou a ne pokazde kdyz je volana. Takze kdyz uz to ma byt trochu prakticke tak bych uchovaval jak HL tak BC. A to zrovna u BC neni tak snadne a bude to stat instrukci navic. Uchranit AF by bylo jeste drazsi...
Mozna jeste dulezitejsi vec je si u kazde rutiny psat jaky to ma vstupy a jaky vystupy a i co to zmeni. Je to prace navic, ale pak kdyz neco pouzijeme tak si staci nastudovat tu prvni rutinu a ne hledat co to dela a co to vola a rekurzivne hledat dale...
Dale stoji za uvahu jak je to s tou funkci co prepocitava XY na adresu, zda ji nepouzivame nahodou jen kvuli prehlednosti a nedavame tam konstanty jako ted. Co to dat do makra. Neni to zrovna co bych preferoval, protoze se staneme trochu zavisly na preklaci (pasmu).
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT start: ld b, 15 ; x-ová souřadnice ld c, 12 ; y-ová souřadnice call calc_char_address ; výpočet adresy ld hl, TEXT ; adresa prvního znaku v řetězci call print_string ; tisk celého řetězce finish: jr finish ; žádný návrat do systému calc_char_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: ; DE - 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 and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld e, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld d, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce call draw_char ; tisk jednoho znaku inc hl ; přechod na další znak jr print_string ; na další znak ; Input: A = char, DE = address ; Output: DE = adress next char, zero flag for x,y=0,8;0,16;0,24(overflow DE=0x5800) ; Poluttes: AF draw_char: push hl ; uschovat HL na zásobník push bc ; uschovat BC na zásobník 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 ld a, c pop bc ; obnovit obsah BC ze zásobníku pop hl ; obnovit obsah HL ze zásobníku ret z ; D+=8,E=E+1=0 ld d, a ret ; D=D,E=E+1 ; nulou ukončený řetězec TEXT: db "Hello, Speccy!", 0 end ENTRY_POINT
Makra se v pasmu delaji dvema zpusoby a jeden je:
; Input: x = 0..31 ; y = 0..23 ; Output: ; ld de, 0x4000 + ((0x18 & y) << 8) + ((0x07 & y ) << 5) + ( 0x1F & x ) XY_2_DE_addr MACRO _x, _y ld de, 0x4000+((24&(_y))<<8)+((7&(_y))<<5)+(31&(_x)) ENDM XY_2_DE_addr 15,12
Takze kod pak muze vypadat i takto:
kompilace pres:
sjasmplus input.asm --raw=output.bin
tady nic krome labelu (vcetne equ) nesmi zacinat na novem radku
nebo:
pasmo -d input.asm output.bin
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 org ENTRY_POINT ; Input: x = 0..31 ; y = 0..23 ; Output: ; ld de, 0x4000 + ((0x18 & y) << 8) + ((0x07 & y ) << 5) + ( 0x1F & x ) XY_2_DE_addr MACRO _x, _y ld de, 0x4000+((24&(_y))<<8)+((7&(_y))<<5)+(31&(_x)) ENDM start: XY_2_DE_addr 15, 12 ld hl, TEXT ; adresa prvního znaku v řetězci ; call print_string ; tisk celého řetězce finish: ; jr finish ; žádný návrat do systému if 0 calc_char_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: ; DE - 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 and %00000111 ; pouze spodní tři bity y-ové souřadnice (řádky 0..7) rrca rrca rrca ; nyní jsou čísla řádků v horních třech bitech or b ; připočítat x-ovou souřadnici ld e, a ; máme spodní bajt adresy ; Y2 Y1 Y0 X4 X3 X2 X1 X0 ld a, c ; y-ová souřadnice and %00011000 ; dva bity s indexem "bloku" 0..3 (dolní tři bity už máme zpracovány) or %01000000 ; "posun" do obrazové paměti (na 0x4000) ld d, a ; máme horní bajt adresy ; 0 1 0 Y5 Y4 0 0 0 ret ; návrat z podprogramu endif print_string: ld a, (hl) ; načíst kód znaku z řetězce and a ; test na kód znak s kódem 0 ret Z ; ukončit podprogram na konci řetězce call draw_char ; tisk jednoho znaku inc hl ; přechod na další znak jr print_string ; na další znak ; Input: A = char, DE = address ; Output: DE = adress next char, zero flag for x,y=0,8;0,16;0,24(overflow DE=0x5800) ; Poluttes: AF, BC ;draw_char_save_bc draw_char: push hl ; uschovat HL na zásobník ifdef draw_char_save_bc push bc ; uschovat BC na zásobník endif 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 ifdef draw_char_save_bc ld a, c pop bc ; obnovit obsah BC ze zásobníku pop hl ; obnovit obsah HL ze zásobníku ret z ; D+=8,E=E+1=0 ld d, a else pop hl ; obnovit obsah HL ze zásobníku ret z ; D+=8,E=E+1=0 ld d, c endif ret ; D=D,E=E+1 ; nulou ukončený řetězec TEXT: db "Hello, Speccy!", 0
Vysledek ma pouze 54 bajtu z puvodnich 78.
Jeste jsem tam teda udelal zase nejake neciste triky s propadavanim do fci...
Jojo, od příště už je nutný popisovat parametry, návratové hodnoty a "zničené" registry. Jinak s tím, kdo bude uchovávat hodnoty registrů mám pořád "ideovou" potíž :) Někdy prostě ta rutina bude dělat push/pop naprosto zbytečně, ale obecně je samozřejmě lepší, když registry uchovává (a potom je otázka, co s příznaky). Na MOS 6502 takové problémy nejsou...
Je to slozitejsi nez to na prvni pohled vypada... .)
Doslo me, ze uz FORTHu si mohu dovolit vetsi volnost jen diky tomu ze v kodu nikde nejsou fce co vraceji priznaky, nebo fce co vraceji 8 bitovou hodnotu v A.
Proste FORTH kdyz porovna neco, tak si ulozi hodnotu TRUE (-1) nebo FALSE (0) do 16 bitoveho registru (TOS) a pak se s tim dale pracuje. Nastesti to neni tak strasne, jak to vypada, protoze prekladac je schopen odchytit vetsinu techto slov a spojit je do nejakeho celku.
Misto
EQ IF
vygeneruje spojene slovo
EQ_IF, ktery vnitrne pouzije ten priznak co ma procesor na kterem ten forth pobezi. EQ ktery ulozi TRUE/FALSE na TOS a IF ktery ho teprve vyzvedne je virtualni zpusob jak je FORTH navrzen a to neni nic proti FORTHu. A ani to neni nic proti Z80, jen to jen problem prekladace, ze musi najit spravny zpusob jak to prevest do instrukci Z80.
Slova (spojeni slov ) jsou pak neco jako inline fce a ja pak nemusim nic resit. Jen si uhlidat HL (TOS), DE (NOS) a HL' (RAS).
V beznem asembleru mit tohle bude uz horsi, bude fakt zalezet jak si definujes v cem se ti budou slova vracet a dokonce jestli budes kombinovat 1, 8 a 16 bitove vstupy/vystupy.
Ani mit A pouzity pro 8 bitovy vstup/vystup neni zadna slava pokud musis ochranovat F.
A ani neplati vzdy ze je vhodnejsi mit PUSH + POP reseny v ramci fce (skoro neco jako pascalova konvence.. .) )
Kdyz bude ta fce typu SWITCH... a ty v kazde vetvi budes muset delat POP POP POP... Tak se to vyplati delat v tom volani. Pripadne to obalit v dalsi fci a teprve tu volat.
Je to cele uplna dzungle i kdyz muzou existovat nejaka optimalni pravidla, tak ne vzdy se vyplati. Neco jako je v sachu pozicni a kombinacni hra. Nektere pozice jsou nebezpecne na ne na prvni pohled viditelne kombinace, kde nic neplati vsechno si musis spocitat. Takze se nemuzeme drzet jen pozicnich pravidel i kdyz ty nam mohou hodne pomoct.
Nebo je to asi stejny chaos jako formalni a kontextove gramatiky. To by musel rici nekdo kdo informatiku studoval. .)
PS: Nastesti vetsinou plati, ze problem je na ZX skoro vzdy velikost kodu/grafiky a ne rychlost procesoru. Ja bych treba rutinu pro tisk znaku nerozbaloval. A kdybych musel tak prvne zkusil misto 8 cyklu mit 4 cykly. Proto preferuji mit mene PUSH POPu ve funkci nez mit vice, jen okolo volani kde je to nutne.
Pak jsou tu nektere veci co za me resi proste makro (M4)
Potrebuji nahrat do osmibitoveho registru nejakou konstantu? Ok. necham si to vygenerovat makrem, do ktereho jsou vstupy jake hodnoty/konstanty maji ostatni registry. A on najde tu spravnou variantu. Zmeni se nekde konstanta? Nevadi, nic nemusim menit, vygeneruje se jiny kod.
9. 3. 2023, 18:59 editováno autorem komentáře
Ted jsem si teprve uvedomil, ze v te rutine draw_char je skryta vada. Protoze adresa fontu je zapsana pres promennou CHAR_ADR, aby tam nebyla magic cisla... .)
Pak tam nemuzeme natvrdo prirazovat do registru H registr C s tim, ze je tam nula. Protoze to tak uz nemusi byt. Kdybych potreboval zmenit fonty a videl na zacatku ze staci zmenit CHAR_ADR a je hotovo, tak bych mozna ani neprochazel kod a nehledal vyskyty, protoze kdyz to vytknu tak to mohu menit.
Spravne by to melo bys nejak takto:
;draw_char_save_bc draw_char: push hl ; uschovat HL na zásobník ifdef draw_char_save_bc push bc ; uschovat BC na zásobník endif ld bc, CHAR_ADR ; adresa, od níž začínají masky znaků if CHAR_ADR & 255 .warning Pouzivas adresu fontu nezarovnanou na segment, to neni nejlepsi napad. ld h, 0x00 ; C je nulové, protože CHAR_ADR=0x3c00 else ld h, c ; C je nulové, protože CHAR_ADR=0x..00 endif ld l, a ; kód znaku je nyní ve dvojici HL
K Atarist, muzes to mit ve stinovych registrech, ale taky to neco bude stat. Nejhorsi na tomhle je, ze dopredu nevis co vsechno budes jeste potrebovat. Muzes to delat i tak, ze to budes neustale prepisovat v prubehu jak se budou pozadavky menit a dokud te to bude bavit. (nastesti retroprogramovani je jen hobby programovani).
Kdyz se kouknes co to dela ted, a jestli je to uzitecne... tak to zatim neni lepsi nez pouzit ROM rutinu pro me. Tam budes mit jen problem s poslednim radkem (predposledni se da osetrit zmenou jedne ROM promenne).
Tiskneme font v matici 8x8 a nemenime atributy, takze je jasne kam by se to vyvijelo dal. A pokud chceme tisknout atributy, ma smysl to na zacatku pozicovat jako XY? Neni to lepsi pozicovat rovnou jako adresu atributu?
Pak potrebuji jinou rutinu calc_char_address. To uz nekdo musel resit... .)
0x5800..0x5AFF
-->
0x4000..0x57FF
00 01 00 11 11 00 Y5 Y4 Y3 Y2 Y1 X5 X4 X3 X2 X1
-->
00 01 00 Y5 Y4 00 00 00 Y3 Y2 Y1 X5 X4 X3 X2 X1
Vypada to, ze to staci segment vynasobit osmi a invertovat 7.bit/vynulovat 7. bit/pricist 0x80.
;[7:27] ld A, ? ; 1:4 add A, A ; 1:4 2x add A, A ; 1:4 4x add A, A ; 1:4 8x xor 0x80 ; 2:7 ld ?, A ; 1:4 Varianta pokud neni volne A ;[8:32] sla D ; 2:8 2x sla D ; 2:8 4x sla D ; 2:8 8x res 7, D ; 2:8
Kod upraveny tak, aby dekodoval v retezci nektere specialni znaky "\n", AT, ink, paper, flash, bright.
SCREEN_ADR equ $4000 CHAR_ADR equ $3c00 ENTRY_POINT equ $8000 ATTR_T equ $5C8F ZX_EOL equ 0x0D ; zx_constant end of line ZX_INK equ 0x10 ; zx_constant colour ZX_PAPER equ 0x11 ; zx_constant colour ZX_FLASH equ 0x12 ; zx_constant 0 or 1 ZX_BRIGHT equ 0x13 ; zx_constant 0 or 1 ZX_AT equ 0x16 ; zx_constant Y,X ZX_BLUE EQU %001 ; zx_constant ZX_YELLOW EQU %110 ; zx_constant org ENTRY_POINT ; Input: x = 0..31 ; y = 0..23 ; Output: ; ld de, 0x4000 + ((0x1F & y ) << 5) + ( 0x1F & x ) LD_R_XY2addr MACRO _r, _x, _y ld _r, 0x5800+((31&(_y))<<5)+(31&(_x)) ENDM start: LD_R_XY2addr DE,15,12 ld HL, TEXT ; 3:10 adresa prvního znaku v řetězci ; call print_string ; 3:17 tisk celého řetězce finish: ; jr finish ; 2:12 žádný návrat do systému print_string: ld A,(ATTR_T) ; 3:13 ld C, A ; 1:4 print_string_l: ld A, (HL) ; 1:7 načíst kód znaku z řetězce inc HL ; 1:6 přechod na další znak cp 0x20 ; 2:7 call nc, draw_char ; 3:10/17 jr nc, print_string_l ; 2:12 na další znak or A ; 1:4 test na kód znak s kódem 0 ret z ; 1:5/11 ukončit podprogram na konci řetězce call print_set ; 3:17 jr print_string_l ; 2:12 na další znak ; Input: A = spec char print_set: sub ZX_EOL ; 2:7 ret c ; 1:5/11 jr nz, print_set_ink ; 2:7/12 ld A, 0x1F ; 2:7 or E ; 1:4 ld E, A ; 1:4 nastaveni X na 31 inc DE ; 1:6 prechod na dalsi radek ret ; 1:10 print_set_ink: sub ZX_INK-ZX_EOL ; 2:7 jr nz, print_set_paper; 2:7/12 ld A, C ; 1:4 and 0xF8 ; 2:7 or (HL) ; 1:7 inc HL ; 1:6 přechod na další znak ld C, A ; 1:4 save new ink ret ; 1:10 print_set_paper: dec A ; 1:4 jr nz, print_set_flash; 2:7/12 ld A,(HL) ; 1:7 inc HL ; 1:6 přechod na další znak add A, A ; 1:4 2x add A, A ; 1:4 4x add A, A ; 1:4 8x xor C ; 1:4 and 0x38 ; 2:7 xor C ; 1:4 ld C, A ; 1:4 save new paper ret ; 1:10 print_set_flash: dec A ; 1:4 jr nz, print_set_brig ; 2:7/12 ld A, C ; 1:4 add A, A ; 1:4 or (HL) ; 1:7 inc HL ; 1:6 přechod na další znak rrca ; 1:4 ld C, A ; 1:4 save new flash ret ; 1:10 print_set_brig: dec A ; 1:4 jr nz, print_set_at ; 2:7/12 ld A,(HL) ; 1:7 inc HL ; 1:6 přechod na další znak rrca ; 1:4 rrca ; 1:4 xor C ; 1:4 and 0x40 ; 2:7 xor C ; 1:7 ld C, A ; 1:4 save new bright ret ; 1:10 print_set_at: sub ZX_AT-ZX_BRIGHT ; 1:4 ret nz ; 1:5/11 ld A,(HL) ; 1:7 new y inc HL ; 1:6 přechod na další znak ld D, 0x16 ; 2:7 add A, A ; 1:4 add A, A ; 1:4 add A, A ; 1:4 add A, A ; 1:4 rl D ; 2:8 add A, A ; 1:4 rl D ; 2:8 xor (HL) ; 1:7 new x inc HL ; 1:6 přechod na další znak ld E, A ; 1:4 ret ; 1:10 ; Input: A = char, DE = address, C = attr ; Output: DE = adress next char (overflow DE=0x5800) ; Poluttes: none draw_char: push AF ; 1:11 uschovat AF na zásobník push BC ; 1:11 uschovat BC na zásobník push HL ; 1:11 uschovat HL na zásobník ld L, A ; 1:4 kód znaku do L ld A, C ; 1:4 ld (DE),A ; 1:7 uložení atributu znaku ld BC, CHAR_ADR ; 3:10 adresa, od níž začínají masky znaků if CHAR_ADR & 255 .warning Pouzivas adresu fontu nezarovnanou na segment, to neni nejlepsi napad. ld H, 0x00 ; 1:4 C je nenulové else ld H, C ; 1:4 C je nulové, protože CHAR_ADR=0x..00 endif add HL, HL ; 1:11 2x add HL, HL ; 1:11 4x add HL, HL ; 1:11 8x add HL, BC ; 1:11 přičíst bázovou adresu masek znaků ld B, 8 ; 2:7 počitadlo zapsaných bajtů ld C, D ; 1:4 uschovat D sla D ; 2:8 2x sla D ; 2:8 4x sla D ; 2:8 8x res 7, D ; 2:8 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 (nemusíme řešit přetečení do vyššího bajtu) 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 obnovit obsah D inc DE ; 1:6 pop HL ; 1:10 obnovit obsah HL ze zásobníku pop BC ; 1:10 obnovit obsah BC ze zásobníku pop AF ; 1:10 obnovit obsah AF ze zásobníku ret ; 1:10 DE+=1 ; nulou ukončený řetězec TEXT: db "Hello, Speccy!", 0x0D,"dalsi radek na ",ZX_PAPER, ZX_YELLOW, "zlute...", ZX_AT,0,0,"pocatek", ZX_INK, ZX_BLUE," modrou!",ZX_AT,7,13,"ZX_AT 7,13 bright: ",ZX_BRIGHT,1,"ON",ZX_BRIGHT,0," OFF",ZX_AT,23,3,"ZX_AT 23,3 FLASH: ",ZX_FLASH,1,"ON",ZX_FLASH,0,"OFF",0
Stoji tam za povsimnuti jak je udelana ta SWITCH..CASE cast. Je to vnoreno do funkce aby se vyskakovalo pres RET. Hodnota podle ktere se to rozhoduje je v A a pokud mezi sebou vetve jsou vzdaleny jen o 1 tak staci "DEC A".
9. 3. 2023, 18:19 editováno autorem komentáře