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