forum speccy.pl
ZX Spectrum => PROGRAMOWANIE => Wątek zaczęty przez: siudym w 2021.10.19, 00:36:58
-
Witam :) Jestem nowy w temacie Z80 i sprawia mi on sporo problemów...
Chciałbym skopiować pewną ilość bajtów do RAM, ale dosyć nietypowo bo muszą być skopiowane co drugie bajty. Plik binarny INCBIN ma rozmiar 2KB, ale w ram będzie zajmował 1KB czyli kopiowane będą tylko parzyste bajty licząc od pierwszego. Np. 00,02,04,06,08 itd itd.
Kombinuje i nic za bardzo nie działa, kopiowany jest tylko jeden bajt z incbin do ram.
Adres w pamieci np. D000 do D3FF czyli 1024bajty w RAM zajete.
CopyNT:
ld hl,NameTable ; Location of tile data
ld bc,NameTableSize ; Counter for number of bytes to write
CopyNT_Loop:
ld a,(hl) ; Get data byte
ld ($D000),a ; Wram address
inc hl ; Point to next tile
inc hl ; 2x inc hl to skip every other byte and copy to wram only every (even) first byte of the tile (1KB)
dec bc
dec bc
ld a,b
or c
jr nz,CopyNT_Loop
ret
NameTable: ; 2KB nametable binary
.incbin "nametable.bin" fsize NameTableSize
opcja 2, tez nie dziala:
copy:
ld hl,NameTable
ld bc,NameTableSize
ld de, $D000
copy_loop:
ldi
inc hl
inc hl
jp po,copy_loop
ret
-
Spróbuj :
org 32768
ld hl, 0 ; źródło
ld de, 16384 ; cel
ld bc, 2048 ; licznik
loop:
ldi
inc hl
ld a,b
or c
jr nz,loop
ret
-
Dziala, dzieki! :) Sam system inny niz ZX Spectrum bo Sega Mark III.
ld hl,NameTable ; źródło
ld de,$C000 ; cel
ld bc,NameTableSize ; licznik
loop:
ldi
inc hl
ld a,b
or c
jr nz,loop
-
Mała podpowiedź czemu nie działa:
ld a,(hl) ; Get data byte
ld ($D000),a
Nie ma prawa zadziałać bo zapisujesz każdą pobraną wartość w jedno i to samo miejsce pamięci $d000
copy_loop:
ldi
inc hl
inc hl
jp po,copy_loop
Tutaj problemem jest to że warunkowy skok odnosi się do zwiększania HL - co też nie ma prawa zadziałać ;)
-
Tak, już wcześniej przeanalizowałem kod :) Powoli ogarniam 16-bitowe rejestry, tylko trzeba szukać przykładów bo goła teoria jakoś słabo mi wchodzi.
-
copy_loop:
ldi
inc hl
inc hl
jp po,copy_loop
Tutaj problemem jest to że warunkowy skok odnosi się do zwiększania HL - co też nie ma prawa zadziałać ;)
Racji nie masz, młody padawanie, nie dlatego nie działa :P
Skok warunkowy odnosi się do LDI, tylko warunek jest źle dobrany.
I za dużo zwiększania HL, instrukcja LDI sama już raz zwiększa.
Tak jest dobrze:
copy_loop:
LDI
INC HL
JP PE, copy_loop
RET
-
Racji nie masz, młody padawanie, nie dlatego nie działa :P
oj tam, oj tam ;) ale dziękuję - nie pamiętam już by ktoś mnie nazwał "młody" :D
-
Jeszcze bym prosil o weryfikacje kodu:
NameTable to bedzie plik incbin o wielkosci do 1024bajtow, ale ze wzgledu, ze nie bedzie zawsze tej samej wielkosci bedzie ograniczony bajtem o wartosci FF w "srodku". Czyli kopiujemy dane pod adres $C100 i skopiuje sie tyle ile pozwoli ustawiony w pliku $FF:
CopyNT:
LD HL,NameTable ; kopiujemy do HL adres zrodla
LD DE,$C100 ; kopiujemy do DE adres docelowy
CopyNT_Loop:
LD A,(HL) ; ladujemy do A wartosc spod adresu wskazanego w HL
CP $FF ; sprawdzamy czy wartosc w A = FF
JR Z,CopyNT_End ; jak wartosc sie zgadza skok zakonczyc funkcje a jak nie to wykonaj "LD (DE),(HL)" nizej instrukcja LDI
LDI ; ("LD (DE),(HL)", then increments DE and HL, and decrements BC)
JR CopyNT_Loop ; skocz zapetlic operacje
CopyNT_End:
-
Wygląda całkiem OK.
Jedyne, do czego bym się przyczepił, to brak "bezpiecznika". Jeśli z jakichś przyczyn pętla nie znajdzie kodu #FF, to się zrobi nieprzewidywalna (w skrajnym przypadku może kopiować bajty nawet aż do "przekręcenia licznika" i znalezienia jakiegoś #FF w pamięci ROM, a w międzyczasie m.in. zamazać stos). Warto więc pierwszy bajt po buforze ustawić na #FF.
-
@trojacek Yhym... Pytanie tylko jak to sobie wyobrażasz? Zakładam, że "bezpiecznik" jest dlatego, że dane będą wczytywane "skądś" czyli jakieś I/O albo coś więc co do zasady nie wiesz ile ich będzie ciężko więc postawić znacznik w odpowiednim miejscu. Trzeba by wypełniać cały bufor FFami przed wczytaniem danych albo zmienić procedurę wczytywania żeby dopisywała znacznik.
Z drugiej strony jeśli dane są faktycznie wczytywane statycznie poprzez incbin przy kompilacji to cała koncepcja jest zupełnie bez sensu bo wtedy dokładnie wiemy ile jest tych danych i wystarczy zrobić LDIRa.
ld hl,NameTable
ld de,$c100
ld bc,NameTableEnd-NameTable
ldir
...
NameTable: incbin "plik.bin"
NameTableEnd:
-
Wlasnie BC nie moge uzyc jako koniec wskaznika wielkosci pliku. Moze nie opisalem dokladnie: Wskaznik FF bedzie zawsze w srodku pliku. Myk jest taki, ze plik binarny bedzie zawsze 1024 bajty (kwestia edytora, ktory taki bin wypluje), a skopiowane dane beda MAX 768 bajtow LUB MNIEJ (768 bo tyle tilesow 8x8 bedzie na ekranie w Sega Master System [256x192=768 - lub mniej). Wskaznik FF tym sterowac bedzie :) Aha i nie bedzie opcji aby wartosc FF byla jakos dodatkowo w innym miejscu bo ten 1kb to pamiec tilesow ekranu i FF to ostatni TILES ekranu zarezerwowany do tego celu (uzyty tylko raz w tym celu i nigdy w innym).
BC i NameTableEnd uzywalem wczesniej, ale szkoda mi bylo zajmowac bezsensu RAM na nieuzywane dane 768b+
-
OK. Ale... nie możesz na ten moment odłożyć BC na stos? Całość z LDIRem i tak będzie szybsza niż pętla sprawdzająca znacznik.
-
W sumie to funkcja dziala ok, ale zawsze warto dopytac o alternatywy i analize :)
Pytanie o odpowiednik EOR z 6502, widze w Z80 jest instrukcja XOR - nie wiem czy poprawnie zapisalem kod Z80:
6502:
LDA zmienna
EOR #$1D ; EOR (bitwise Exclusive OR)
STA zmienna ; zapisz wynik
LD A,(zmienna)
XOR $1D ; czy teraz XOR wykona sie na A ?
LD (zmienna),A ; i mozna zapisac do zmiennej wynik?
-
Dobrze. Warto też zapamiętać, że XOR A jest często używany do zerowania akumulatora.
-
LDA zmienna
LSR A
LSR A
LSR A
STA wynik
Wartosc w zmienna jest w zakresie 00-FF, wykonujemy logiczne przesuniecie w prawo aby wywalic 3 nizsze bity.
Efektem bedzie "zaokraglenie" pewnych zakresow wartosci do jednej np:
- $00-$07 zapisze do wynik $00
- $08-$0F zapisze do wynik $01
- $10-$17 zapisze do wynik $02
- $18-$1F zapisze do wynik $03
- $20-$27 zapisze do wynik $04
(...)
- $E0-$E7 zapisze do wynik $1C
- $E8-$EF zapisze do wynik $1D
- $F0-$F7 zapisze do wynik $1E
- $F8-$FF zapisze do wynik $1F
W kodzie Z80 mam problem bo wyniki sa totalnie inne, domyslam sie, ze bit0 przenosi sie do bit7 czego nie ma w przypadku 6502
gdzie wypada i nie przenosi sie do bit7.
Probowalem uzyc rozkazu kasujacego znacznik C ale nic nie daje, z tego co czytam CCF nie kasuje ale robi invert C, niby OR A kasuje C
ale dalej nic nie zmienia - pewnie powodem sa same rozkazy shift right w Z80, ktore automatycznie przenosza bit0 do bit7??
LD A,(zmienna)
;CCF ? OR A - nic nie zmieniaja
RRC A
RRC A
RRC A
LD (wynik),A
EDIT: hmm teraz dziala - czy to jedyna metoda aby uzyskac ten sam efekt?
LD A,(zmienna)
RR A
OR A
RR A
OR A
RR A
OR A
LD (wynik),A
wychodzi na to, ze musze za kazdym RR zerowac C aby bit0 nie wlazil w bit7 ?
-
Zamiast RRA trzeba użyć RRCA.
http://www.z80.info/z80syntx.htm#RRCA (http://www.z80.info/z80syntx.htm#RRCA)
Zawsze zaglądam na tą stronę jak coś mi nie działa w przesuwaniu, bo często myślę odwrotnie: że RRA przesuwa "normalnie", a RRCA przez C ;)
-
Ale wlasnie robilem 3x RRCA i wynik byl zly. Chodzi o to aby bit0 wyskakujac nie pojawial sie w bit7.
-
Ach! Zasugerowałem się że użyłeś RRA. RR to skrót od Rotate Right, czyli przesuwa w kółko.
A tu trzeba użyć SRL A - Shift Right Logically (bo jeszcze jest przesuwanie arytmetyczne - zachowujące znak liczby (czyli bit 7)).
http://www.z80.info/z80syntx.htm#SRL (http://www.z80.info/z80syntx.htm#SRL)
Ale uwaga, przesuwania logicznego w lewo - nie ma >:( (jest jakiś nieudokumentowany, ale coś robi inaczej, chyba wsuwa "1" zamiast "0").
-
Dziala :) Kurcze jak ja zerkalem na rozkazy shiftu w prawo, ze go przegapilem...
-
Nie moge juz edytowac, ale jeszcze taki myk jest:
AND %11111000
RR A
RR A
RR A
Zerujemy bity 0,1,3 i teraz nawet jak wyskocza do bit7 to nie spowoduja krzywdy ;)
-
Niezłe :)
O bajt krótsze i o pięć taktów szybsze niż 3*SRL A!
-
Kolejny problem 8)
Mam w HL adres pamieci, ale musze odjac od H $89 - czyli jak w HL mam $C100 to odejmujac od H $89 powinienem miec w HL adres $3800.
Myslalem, ze wystarczy SUB H,$89 i potem w HL bedzie odpowiedni wynik, ale niestety tak to nie dziala.
LD D,$89
LD E,$00
SBC HL,DE
To dziala, nie wiem czy inna mozliwosc jest.
-
Arytmetyka 8-bit działa wyłącznie na akumulatorze. Czyli musiałbyś zrobić ld a,h/sub $89/ld h,a.
Jeśli robisz na 16 bitach to ld de,$8900 - będzie krócej niż dwa ld do połówek. Poza tym pamiętaj, że SBC odejmuje z przeniesieniem więc jeśli nie jesteś pewien czy CY jest wyzerowane to trzeba zrobić np. or a przed odjęciem.
-
ok dzieki, a wartosci odejmowania nie spowoduja wykroczenie poza 0-255 bo zrodlowy adres ma zawsze C1-C6 wiec adres odjety bedzie tez podobnie zawsze 38-3D
-
Ale to nie o to chodzi co się stanie w wyniku - SBC HL,DE odejmuje od wartości HL wartość DE oraz wartość znacznika CY. Jeśli nie upewnisz się, że CY jest wyzerowane to może być tak, że SBC odejmie ci o jeden więcej niż się spodziewasz.
-
Nie bardzo rozumiem zastosowania rejestrow indexowych w Z80 - dzialaja albo totalnie inaczej niz te w 6502, albo ich nie rozumiem:
Przykladowo mam w pamieci ram jakis ciag zmiennych np. zaczynaja sie w $C000
zmienna00 db
zmienna01 db
zmienna02 db
....
zmienna98 db
zmienna99 db
teraz w 6502 uzywajac np, rejestru indexowego Y moge zaladowac do akumulatora zmienna wskazana przez index:
ldy #$20
lda zmienna00,y
teraz w aku mam zaladowana wartosc zmiennej nr 32 (nazwy maja dec, wiec bedzie to wartosc ze zmienna32)
Jak cos podobnego zrobic w Z80?
-
Rejestry indeksowe IX/IY służą do "prostego" dostępu do bloku 256 bajtów adresowanego bezpośrednim indeksem. To co napisałeś to byłoby mniej więcej coś takiego:
ld ix,zmienna00
ls a,(ix+32)
Tyle, że to nie jest to, co robią rejestry indeksowe (i ich tryby adresowania) w 6502. Z80 jest pod tym względem znacznie bardziej "uproszczony" - ma w zasadzie trzy tryby adresowania na krzyż i nadrabia skomplikowanymi instrukcjami i liczbą rejestrów.
-
ld hl,zmienna00
ld de,$20
add hl,de
ld a,(hl)
Indeksowe w Z80 działają inaczej - nie możesz wziąć indeksu z rejestru.
ld ix,tabela
ld a,(ix+0)
ld e,(ix+64)
ld c,(ix-10)
Natomiast, jeśli wyrównasz tabelę do granicy 256 bajtów ( do adresu $xx00) to możesz zastosować prostą sztuczkę
ld hl,tabela
... ; dowolne operacje traktujące rejestr l jako rejestr indeksowy
ld d,(hl)
-
Nieco pogubiłem się.
Kombinuje: ;) pewnie zle
ld a,$10 ; a jako index
call funkcja ; skocz wybrac z adresu $C000 bajt zwiekszony o wartosc w A
ld a,d
ld (zmienna),a ; teraz w zmienna zapisalem wartosc spod adresu $C000+$10 ?
funkcja:
ld hl,$C000
; - teraz mozna dodac do L wartosc aku uzyskujac zwiekszony adres HL
add l,a ; adc?
ld d,(hl)
ret
-
ld a,$10
call pobierz
ld (zmienna),a
... ; reszta kodu
ret
pobierz:
ld hl,$c000 ; lub ld h,$c0 - szybciej i jeden bajt mniej :)
ld l,a
ld a,(hl) ; zwraca wartość od razu w akumulatorze
ret
-
Dokladnie, B niepotrzebne :)
W temacie Z80 jak na razie powoli ale do przodu, uczac sie procesora na warsztat wzialem dwa systemy i na chwile obecna cos tam sie udalo sklecic (nieco bardziej zaawansowane HELLO WORLD'y ):
GameBoy klasyczny (wprawdzie procek to jakis custom-Z80 sharpa, ale jest podobnie)
https://www.youtube.com/watch?v=dVYdYis4nSQ
Sega Master System (ty klasyczny Zilog)
https://www.youtube.com/watch?v=BgfkQdMRYa4
Co nastepne? W planach albo MSX bo architektura podobna do SMS, albo ZX :)
-
Takie podstawy podstaw z programowaniem Z80 z analogiami do BASICa opublikowałem w dwóch ostatnich numerach Zin80. Być może pomogą. Link: https://www.speccy.pl/archive/prod.php?id=576
-
Dzięki, to zawsze jest przydatne :)
Z ksiazek mam pewnie klasyczny, znany zestaw:
(https://i.postimg.cc/7ZGPg470/2021-11-18-4850.jpg)
-
To ja zawsze jeszcze sugeruję to: http://www.zilog.com/docs/z80/um0080.pdf
-
Oraz Z80 Undocumented (http://www.z80.info/zip/z80-documented.pdf)
-
@steev Yup. Choć w wypadku kogoś, kto zaczyna albo przechodzi z innej architektury undocumented zaczynają być potrzebne jakoś tak później... ;)
-
Takie podstawy podstaw z programowaniem Z80 z analogiami do BASICa opublikowałem w dwóch ostatnich numerach Zin80. Być może pomogą. Link: https://www.speccy.pl/archive/prod.php?id=576
Właśnie sobie czytam i coś mnie zastanawia i zgłupiałem już :o
Chodzi o:
LD h,8
LD l,45
jest odpowiednikiem:
LD hl,2093
Powyższe dotyczy także par BC i DE.
Tego nie rozumiem, bo kompilujac wyniki mam takie:
LD H,8 ;dec
LD L,45
- HL ma adres $082D, a gdy:
LD H,$08 ;hex
LD L,$45
- HL ma adres $0845
-
$082D = 2093 dziesiętnie.
-
ehhh racja.... nigdy nie uzywam adresow dziesietnie i zakrecilem sie odczytujac "domyslnie" w hex ;)
-
Mam drobną zagwostkę... Mam pewną funkcję, która ma wyczyścić 1008 bajtów ($3F0) - zeruje ona jednak 736 bajtów:
https://i.postimg.cc/1XGt0Kg8/wram.png
ClearWRAM:
LD HL,$C000
LD BC,$03F0
XOR A
ClearWRAM_Loop:
LD (HL),A
INC HL
DEC C
JR NZ,ClearWRAM_Loop
DEC B
JR NZ,ClearWRAM_Loop
RET
- pierwsza pętla wykona DEC C z F0 do 00, po czym wykonuje DEC C wykonując 3x loop 256 bajtow - tak sie jednak nie dzieje
Można oczywiście zrobić tak - działa, ale zastanawia mnie gdzie tkwi błąd wyżej?
ClearWRAM:
XOR A
LD ($C000),A
LD HL,$C000
LD DE,$C001
LD BC,1024-15
LDIR
RET
-
Gwoli ścisłości wypełnia ci 752 (0x2f0) bajty a to dlatego, że w B masz 3 - pierwsze 240 kończy z 2 w B, potem 256 z 1 i na koniec 256, po którym B schodzi do zera - 240+256+256 - 752 :)
-
Pętla w pętli? Jest sens tak komplikować?
LD HL, $C000
LD B, 252 ; 1008 / 4
XOR A
LOOP
LD (HL), A
INC HL
LD (HL), A
INC HL
LD (HL), A
INC HL
LD (HL), A
INC HL
DJNZ LOOP
Pisane w samolocie na telefonie, więc wybaczcie brzydkie formatowanie.
BTW, ten LDIR powinien mieć w BC 1024 - 17, czyli 1007.
-
Heh jak zwykle banalne rozwiązanie, źle przeanalizowałem. Dzięki.
-
Teraz pytanie nieco bardziej skomplikowane ;)
Znowu mam problem z zastąpieniem rejestrów indexowych w 6502 na Z80. Mam taki kod, mam nadzieje dobrze opisałem - chodzi o zastąpienie go możliwie podobnie ale pod Z80:
- licznik "counter" bedzie miec wartsci od 0 do 7
- teraz przykladowo:
- ladujemy do rejestru X counter majacy wartosc np. 01
- ladujemy do akumulatora flagi z "flags"
- wykonujemy and zawartosci akumulatora na bitmaskach zwiekszonych o rejestr x czyli 01 czyli o 00000010
- and w tym przypadku wyzeruje wszystko w akumulatorze poza bit1
- gdy bit1 bedzie zapalony, wykona sie skok BNE, gdy zgaszony wykona kod ponizej czyli "jsr innafunkcja"
- analogicznie kazda inna wartosc podana w "counter" np. 07 = wykona and 10000000 (bitmask zwiekszone o 7) na flags,
czyli sprawdzany bedzie bit7 flags czy jest zapalony czy nie i zaleznie od tego wykonany lub nie "jsr innafunkcja"
LDX Counter
LDA Flags ; gdy wykryjemy gold, sprawdzamy czy bit zapalony pierwszego w kolejnosci z levactivesprites
AND BitMask,x ; i gdy zapalony pominie wstawienie gold skaczac do zwiekszenia indexu X
BNE skip ; wykonaj skok BNE gdy wynikiem and bedzie zapalona wskazana flaga w "flags"
JSR innafunkcja
skip:
;################################################################################
BitMask:
.byte %00000001
.byte %00000010
.byte %00000100
.byte %00001000
.byte %00010000
.byte %00100000
.byte %01000000
.byte %10000000
-
Jest proste rozwiązanie, jeśli BitMask zaczyna się od adresu modulo 256.
W takim przypadku:
LD HL, BitMask ; właściwie wystarczy do H załadować starszy bajt adresu
LD L, Counter
LD A, Flags
AND (HL)
JR NZ, skip
JR innafunkcja
skip
-
No właśnie a gdybym chciał mieć w dowolnym miejscu BitMask?
-
To wtedy będzie trochę więcej kodu.
Pierwsze, co mi przychodzi do głowy:
LD HL, BitMask
LD D, 0
LD E, Counter
ADD HL, DE
LD A, Flags
AND (HL)
JR NZ, skip
JR innafunkcja
skip
-
Do weryfikacji - tym razem inny przyklad, ale takze zastepowanie indexowania z uzyciem X z 6502:
- przenosi przy kazdym przebiegu petli cztery kolejne zmienne pod adres ram, po czym zwieksza o cztery aby znowu zapisac je w kolejnych 4 bajtach ram itd itd
; 6502:
loop:
LDX counter ; startuje od 00, zwiekszany co cztery przy kazdym przebiegu petli 00,04,08,0C itd
LDA temp0
STA $200,x
LDA temp1
STA $201,x
LDA temp2
STA $202,x
LDA temp3
STA $203,x
;------
TXA
CLC
ADC #4
STA counter
jmp loop
;---------------------------------
; z80:
LD D,$00
loop:
LD A,(counter)
LD E,A
LD HL,$200
ADD HL,DE
LD A,(temp0)
LD (HL),A
INC HL ; LD HL,$201
ADD HL,DE
LD A,(temp1)
LD (HL),A
INC HL ; LD HL,$202
ADD HL,DE
LD A,(temp2)
LD (HL),A
INC HL ; LD HL,$203
ADD HL,DE
LD A,(temp3)
LD (HL),A
;------
LD A,E
ADD $04
LD (counter),A
jp loop
-
Jeśli dobrze zrozumiałem co chcesz zrobić to powiedziałbym, że albo:
LD HL, $200
loop:
LD A,(temp0)
LD (HL),A
INC HL
LD A,(temp1)
LD (HL),A
INC HL
LD A,(temp2)
LD (HL),A
INC HL
LD A,(temp3)
LD (HL),A
INC HL
<test na wyjście z pętli>
JR loop
albo np.
LD DE,$200
loop:
LD HL,temp0
LDI
LD HL,temp1
LDI
LD HL,temp2
LDI
LD HL,temp3
LDI
<test na wyjście z pętli>
JR loop
(dużo wolniej, ale jest taka opcja :) )
'Dosłowne' przeniesienie wyglądałoby pewnie jakoś tak...
LD IY,$200
LD DE,$04
loop:
LD A,(temp0)
LD (IY+0),A
LD A,(temp1)
LD (IY+1),A
LD A,(temp2)
LD (IY+2),A
LD A,(temp3)
LD (IY+3),A
ADD IY,DE
<test na wyjscie z pętli>
JR loop
(nie idź tą drogą)
-
Dzięki. To jest tylko przykładowa funkcja, nie ma wyjścia z pętli - chodzi tylko o zastąpienie tego przykładowego kodu. Użycie IY nie wpadłem na to, zapomniałem, że też podobnie jak HL można wysyłać pod wskazany przez niego adres.
-
Kolejna zagwostka..
Bawię się różnymi funkcjami związanymi ze zmianami w ram,
funkcja niżej powinna zapisać $E8 co czwarty bajt w obszarze pamięci 128-bajtow - niestety robi to losowo:
LD A,$E8
LD HL,$C300
LD DE,$0000
LD B,32
loop:
LD (HL),A
INC DE
INC DE
INC DE
INC DE
ADD HL,DE
DEC B
jp nz,loop
- B uzylem jako licznik czyli 32x4=128
- tak wygląda pamięć po wykonaniu tej funkcji: (https://i.postimg.cc/tJvksPFQ/rerere.jpg)
Powinny byc E8 co czwarty bajt, a nie sa.
-
Zwiększasz dodawaną wartość przed dodaniem, ergo zwiększasz przesunięcie :)
albo
ld a, $e8
ld hl, $c300
ld b, $20
loop:
ld (hl),a
inc hl
inc hl
inc hl
inc hl
djnz loop
end
albo
ld a, $e8
ld b, $20
ld hl, $c300
ld de, $04
loop:
ld (hl),a
add hl,de
djnz loop
end
-
Ale jak zwiększam po dodaniu, mam to samo :/
EDIT. Przeanalizowałem i faktycznie 8) 8)
LD A,$E8
LD HL,$C300
LD DE,$0004
LD B,32
loop:
LD (HL),A
ADD HL,DE
DEC B
jp nz,loop
RET
-
Przeanalizowałem i faktycznie 8) 8)
Przeanalizowałeś i zepsułeś to, co napisał steev ;)
Tego się trzymaj:
ld a, $e8
ld b, $20
ld hl, $c300
ld de, $04
loop:
ld (hl),a
add hl,de
djnz loop
end
Hint: DJNZ = DEC B + JR NZ.
-
Przeanalizowałeś i zepsułeś to, co napisał steev ;)
Ja tylko wstawiłem działający kod bez błędu popełnionego wcześniej (niepotrzebne 4x INC DE, powodujące, że mialem E8 wstawiane co 4,8,12,16,20,24 itd itd ;) ). To, że użycie DJNZ znacznie lepsze to już wiem.
-
Wracam do pytania związanego z zamiennikiem indexowania 6502 w Z80:
Mam przykładowy kod - jest to bardzo uproszczona, przykładowa pętla ruchu sprites na ekranie.
Pętla jest jedną, wspólną funkcją dla wszystkich sprites, jedynie zmieniane są wartości indexow i wykonywanu skok do tej funkcji.
Dla kodu 6502 używam indexu Y dla zmiennych, które w pamięci RAM są co jeden bajt, natomiast index X dla zmiennych sprite's, które są co 4 bajty.
Interesuje mnie jak lepiej to zrobić w przypadku kodu Z80 (bo domyślam się, można to zrobić lepiej... :) )
; RAM:
ENEMY0_FLAGS ; te zmienne są jedna po drugiej w pamięci RAM
ENEMY1_FLAGS
ENEMY2_FLAGS
ENEMY3_FLAGS
(...)
ENEMY0_Y ; tu jest nieco inaczej bo każda zmienna np. Y jest co 4 bajty dla kolejnego Sprite'a
ENEMY0_X
ENEMY0_T
ENEMY0_P
ENEMY1_Y
ENEMY1_X
ENEMY1_T
ENEMY1_P
ENEMY2_Y
ENEMY2_X
ENEMY2_T
ENEMY2_P
ENEMY3_Y
ENEMY3_X
ENEMY3_T
ENEMY3_P
(...)
; ################################################
; 6502:
LDY $00 ; ENEMY0 FLAGS
LDX $00 ; ENEMY0 Y/X
JSR FUNKCJA_WSPOLNA
LDY $01 ; ENEMY1 FLAGS
LDX $04 ; ENEMY1 Y/X
JSR FUNKCJA_WSPOLNA
LDY $02 ; ENEMY2 FLAGS
LDX $08 ; ENEMY2 Y/X
JSR FUNKCJA_WSPOLNA
LDY $03 ; ENEMY3 FLAGS
LDX $0C ; ENEMY3 Y/X
JSR FUNKCJA_WSPOLNA
(...)
FUNKCJA_WSPOLNA:
LDA ENEMY0_FLAGS,Y
AND %00000001
BEQ FUNKCJA_WSPOLNA_NO
INC ENEMY0_Y,X
INC ENEMY0_X,X
FUNKCJA_WSPOLNA_NO:
RTS
; ################################################
; Z80:
LD HL,$0000
LD (TEMP1),HL ; ENEMY0 FLAGS
LD (TEMP2),HL ; ENEMY0 Y/X
CALL FUNKCJA_WSPOLNA
LD HL,$0001
LD (TEMP1),HL ; ENEMY1 FLAGS
LD HL,$0004
LD (TEMP2),HL ; ENEMY1 Y/X
CALL FUNKCJA_WSPOLNA
LD HL,$0002
LD (TEMP1),HL ; ENEMY2 FLAGS
LD HL,$0008
LD (TEMP2),HL ; ENEMY2 Y/X
CALL FUNKCJA_WSPOLNA
LD HL,$0003
LD (TEMP1),HL ; ENEMY3 FLAGS
LD HL,$000C
LD (TEMP2),HL ; ENEMY3 Y/X
CALL FUNKCJA_WSPOLNA
(...)
FUNKCJA_WSPOLNA:
LD HL,(TEMP1)
LD DE,HL
LD HL,(ENEMY0_FLAGS)
ADD HL,DE
LD A,(HL)
AND %00000001
JR Z,FUNKCJA_WSPOLNA_NO
LD HL,(TEMP2)
LD DE,HL
LD HL,(ENEMY0_Y)
ADD HL,DE
LD A,(HL)
INC A
LD (HL),A
LD HL,(ENEMY0_X)
ADD HL,DE
LD A,(HL)
INC A
LD (HL),A
FUNKCJA_WSPOLNA_NO:
RET
-
Wracam do pytania związanego z zamiennikiem indexowania 6502 w Z80:
Nie da się. :)
Mam przykładowy kod - jest to bardzo uproszczona, przykładowa pętla ruchu sprites na ekranie.
Pętla jest jedną, wspólną funkcją dla wszystkich sprites, jedynie zmieniane są wartości indexow i wykonywanu skok do tej funkcji.
Dla kodu 6502 używam indexu Y dla zmiennych, które w pamięci RAM są co jeden bajt, natomiast index X dla zmiennych sprite's, które są co 4 bajty.
Interesuje mnie jak lepiej to zrobić w przypadku kodu Z80 (bo domyślam się, można to zrobić lepiej... :) )
Nie lepiej.
Inaczej :)
;; robimy_wode_z_mozgu.asm
org $8000
sprites equ 3
start:
ld b, sprites
exx
ld hl, pos
ld de, 4
exx
ld hl, flags
loop:
bit 0,(hl)
jr z, go_away
exx
inc (hl)
inc hl
inc (hl)
dec hl
exx
go_away:
inc hl
exx
add hl,de
exx
djnz loop
ret
flags:
db 0 ; ENEMY0_FLAGS
db 0 ; ENEMY1_FLAGS
db 0 ; ENEMY2_FLAGS
pos:
db 0 ; ENEMY0_Y
db 0 ; ENEMY0_X
db 0 ; ENEMY0_T
db 0 ; ENEMY0_P
db 0 ; ENEMY1_Y
db 0 ; ENEMY1_X
db 0 ; ENEMY1_T
db 0 ; ENEMY1_P
db 0 ; ENEMY2_Y
db 0 ; ENEMY2_X
db 0 ; ENEMY2_T
db 0 ; ENEMY2_P
Nie testowałem, ale chyba powinno robić to co powinno...
-
ld b, sprites
exx ; ? wymien bc,de,hl na ich rejesty "cienie"
ld hl, pos
ld de, 4
exx ; ? teraz znowu wymien bc,de,hl na "cienie" - czy teraz zapis HL/DE wyzej nie bedzie zniszczony?
ld hl, flags
- tu nie rozumiem zastosowania EXX, jaki ma cel?
- czy nie bedzie tak, ze ponowny EXX po wykonaniu LD HL / LD DE zniszczy nam ich stan bo przecież drugie EXX wykona przeniesienie pustych "cieni rejestrow" znowu do DE i HL ?
-
EXX nic nie przenosi. Procesor ma dwa bloki rejestrów i EXX przełącza ten, który jest aktualnie używany. Po wykonaniu twojego kodu w "drugim" bloku (tym normalnie niedostępnym) będziesz miał pos w HL i 4 w DE, a w "głównym" sprites w B i flags w HL. Kolejne EXX "zamieni" znowu bloki rejestrów. Jedyna niedogodność do której dostosowuje się algorytmy to to, że nie da się używać obu bloków równocześnie.
-
Racja, źle zrozumiałem zasadę działania EXX. Wszystko jasne.
-
Po przerwie znowu wracam do Z80, znowu do prób zastępowania indexowania typowego dla 6502 takim powiedzmy "pseudo-indexowaniem" w Z80.
Mam przykładowy kod, ktory zwiększa dynamicznie prędkość animacji przeciwników używając dwie zmienne na jednego przeciwnika.
Jedna to sam licznik animacji, a drugi to ustawienie prędkości tego licznika (SetTimer).
Chodzi o to, aby nie robić wielu takich osobnych funkcji, ale użyć jednej, która będzie zmieniała kolejność bajtu wg. indexu
(uzywam do tego celu osobnej zmiennej "indexregister1").
Napisalem jedna, indexowana funkcje dla wszystkich przeciwnikow (nie testowane, bo robie to na "brudno"),
kod jest oczywiscie mniejszy rozmiarowo niz wiele osobnych, ale tez zjada wiecej cykli cpu bo i tak musi byc wywolany wiele razy,
ale tu chodzi o sam fakt i przyklad takiego rozwiazania, wiec bede prosil o analize i jakies uwagi :) Bo zapewne da sie to samo zrobic niebo lepiej... ;)
IndexRegister1 DB ; zmienne w ram
ENEMY00_Animation_Timer DB
ENEMY01_Animation_Timer DB
ENEMY02_Animation_Timer DB
ENEMY03_Animation_Timer DB
(...)
ENEMY31_Animation_Timer DB
(...)
ENEMY00_Animation_SetTimer DB
ENEMY01_Animation_SetTimer DB
ENEMY02_Animation_SetTimer DB
ENEMY03_Animation_SetTimer DB
(...)
ENEMY31_Animation_SetTimer DB
(...)
Przykladowy, prosty kod sterujacy licznikiem animacji, osobny dla kazdego przeciwnika:
LD A,(ENEMY00_Animation_Timer)
LD B,A
LD A,(ENEMY00_Animation_SetTimer)
ADD A,B
LD (ENEMY00_Animation_Timer),A
...
LD A,(ENEMY01_Animation_Timer)
LD B,A
LD A,(ENEMY01_Animation_SetTimer)
ADD A,B
LD (ENEMY01_Animation_Timer),A
(...)
Teraz proba indexowania tych zmiennych o index zawarty w zmiennej IndexRegister1:
TimerAnimacji:
LD HL,ENEMY00_Animation_Timer
LD A,(IndexRegister1)
LD E,A
ADD HL,DE
LD A,(HL)
LD B,A ; Teraz w B mam adres ENEMY00_Animation_Timer zwiekszony o IndexRegister1
LD HL,ENEMY00_Animation_SetTimer ; Podobnie jak wyzej ale indexuje zmienna SetTimer
LD A,(IndexRegister1)
LD E,A
ADD HL,DE
LD A,(HL)
LD C,A ; Teraz w C mam adres ENEMY00_Animation_SetTimer zwiekszony o IndexRegister1
ADD C,B ; Dodaje SetTimer do Timer
LD HL,ENEMY00_Animation_Timer ;EDIT: tu moge na poczatku zachowac wynik HL do np. IX/IY i teraz nie wyliczac znowu tylko przeniesc do HL...
LD A,(IndexRegister1)
LD E,A
ADD HL,DE ; Teraz w B mam 'znowu" bajt ENEMY00_Animation_Timer zwiekszony o IndexRegister1
LD A,C ; Wynik dodawania mialem zachowany w C wiec musze go przeniesc do A aby zapisac pod adres wskazany w HL
LD (HL),A ; Zapisz wynik pod adres bajtu ENEMY00_Animation_Timer zwiekszonego o adres indexu czyli odpowiedniego Enemy..
RET
Teraz wystarczy ustawic index o numer danego przeciwnika i wywolac:
ld d,0 ; oczywiscie d musze miec wyzerowane
LD A,31 ; wywolaj kod licznika dla ENEMY31
LD (IndexRegister1),A
CALL TimerAnimacji
-
Tak na szybko... nie ma opcode'u "add c,b" ;) Dodawać można tylko do akumulatora.
Twój przykład zrobiłbym w taki sposób (bez nadmiernej optymalizacji):
TimerAnimacji
ld hl,ENEMY0_Animation_Timer ; adres pierwszego bloku
add hl,de ; "indeksowanie"
push hl ; zapamiętujemy na później
ld b,(hl) ; pobieramy dane
ld hl,ENEMY00_AnimationSetTimer ; drugi blok
add hl,de ; "indeksowanie"
ld a,(hl) ; pobieramy dane
add a,b ; dodajemy
pop hl ; odtwarzamy pierwszy adres po "indeksowaniu"
ld (hl),a ; zapisujemy dane
ret
I wywołanie:
ld de,31 ; indeks ładujemy do de (albo raz 0 do d a potem indeks do e)
call TimerAnimacji
-
Racja, mozna wykonywac jedynie ADD A,r...
Teraz widze bez sensu dodawalem IndexRegister 3x do DE skoro jest zawsze taki sam.
Co do uzycia zmiennej zamiast od razu przed CALL ustawic wartosc indexu do DE, to chodzi o to, ze mam do przepisania kod z 6502, w ktorym uzywalem np. 3 rozne indexy (np. zwiekszany o jeden, kolejny co cztery bajty a inny co 16) dlatego nie uzylem od razu DE przed CALL do wspolnej funkcji.
Wracajac do pomyslu, aby zachowac zawartosc HL w rejestracy IX/IY to widze, ze nie da sie wykonac LD IX,HL, ale mozna by zrobic backup uzywajac stosu aby w pozniejszym kodzie uzyc, np.:
PUSH HL ; przenies HL do IX za pomoca stosu
POP IX
(...)
PUSH IX ; odczytaj zachowany HL "w IX" znowu do HL za pomoca stosu..
POP HL
Tez powinno byc ok?
-
Tak - PUSH/POP będzie OK poza faktem, że przerzuca dane przez stos.
Poza tym wszystkie rozkazy i IX/IY są dłuższe o bajt prefiksu (oraz ewentualny indeks) oraz wolniejsze o 4-12 taktów zależnie od rozkazu więc jeśli koniecznie nie jest ci potrzebne to lepiej jest jednak nie używać rejestrów indeksowych.
-
Dokładnie.
Czasami bardziej się opłaca korzystać z drugiego zestawu rejestrów (EXX).
-
@trojacek owszem pod warunkiem, że nie chcesz przechować czegoś czego musisz użyć - EXX się do tego nie nadaje.
W ogóle obserwując stawiane tu problemy mam wrażenie, że 6502 i Z80 to są dwie prawie całkowicie różne filozofie i portowanie mechanizmów z jednego do drugiego na siłę nie jest najlepszym pomysłem. 6502 jest bardzo wydajny przy mnóstwie trybów adresowania i stosunkowo krótkiej liście rozkazów, Z80 ma więcej rejestrów różnych dziwnych rozkazów za to znacznie mniej trybów adresowania. Trzeba się chyba dobrze nauczyć obu żeby portowany z jednego do drugiego kod nie był kulawy i średnio wydajny ;)
Ale z drugiej strony może ja po prostu jestem za bardzo skrzywiony przez lata pisania na Z80 ;)
-
@trojacek owszem pod warunkiem, że nie chcesz przechować czegoś czego musisz użyć - EXX się do tego nie nadaje.
Dlatego napisałem, że czasami. Akurat nie mam na tyle czasu, by rozkminiać ten ostatni zadany problem, więc nie wiem, czy akurat tu się to przyda, czy raczej jednak nie.
Ale z drugiej strony może ja po prostu jestem za bardzo skrzywiony przez lata pisania na Z80 ;)
Tak, jesteś - podobnie jak wielu z nas na tym forum, nawet nie mając tego świadomości :)
Bo domyślam się, że nie wszyscy fani Spectrum mieli okazję i chęć zgłębiać architekturę i język maszynowy innych procesorów, zwłaszcza motopochodnych.
I tak, masz racje:
[...]Trzeba się chyba dobrze nauczyć obu żeby portowany z jednego do drugiego kod nie był kulawy i średnio wydajny ;)
Bo moim zdaniem tłumaczenie rozkaz po rozkazie nie ma sensu, podobnie jak tłumaczenia z jednego języka (takiego ludzkiego) na inny (ludzki) - bo przekład wyłoży się np. na idiomach czy zwrotach potocznych.
Kluczem do sukcesu jest cofnięcie się od zastanej implementacji do definicji problemu i potem analiza, jak go zaimplementować z użyciem specyficznej architektury danego procesora.
-
W ogóle obserwując stawiane tu problemy mam wrażenie, że 6502 i Z80 to są dwie prawie całkowicie różne filozofie i portowanie mechanizmów z jednego do drugiego na siłę nie jest najlepszym pomysłem
No właśnie ostatnio zauważyłem, że lepiej nie próbować czegoś zastąpić 1:1 z 6502 na Z80, tylko napisać "od nowa" wszystko aby tylko osiągnąć ten sam cel.
Co ciekawe, ostatnio miałem epizod z nauką MC68K i tam poznikały wszystkie problemy jakie miałem w przypadku 6502->Z80 :) Świetny procesor, kiedyś czytałem, że jest chyba najprzystępniejszy do nauki ASM i chyba faktycznie tak jest.