Retrojen Forum
Dijital Sanat => Kodlama => Konuyu başlatan: Skate - 03 Mayıs 2022, 10:28:31
-
@Alcofribas, @hades'in açmış olduğu Z80 Makine diline meraklı olan? (https://retrojen.org/pano/index.php?topic=539) başlığı altında yaptığım paylaşımları başka bir başlığa taşımayı önermişti. Ancak henüz hazır olmadığım gerekçesiyle reddetmiştim. O başlıkta paylaştığım çalışmam da hem sorunlu, hem de optimize edilmemiş bir versiyon idi. Şimdi bir nebze hazırım.
Hollywood filmlerinin sıkça kullandığı filmi ortadan başlatıp, arada geçmişe gidip, en son başladığı noktayı yakalayıp devam etme şablonunu kullanmaya karar verdim. Çünkü an itibariyle filmin ortasındayım.
Ulaştığım noktayı hızlıca görmek isterseniz aşağıdaki linki kullanabilirsiniz.
http://speccy.akaydin.com/ (http://speccy.akaydin.com/)
Yeşil border alanı 256 noktayı çizme ve silme için harcanan CPU'yu temsil ediyor. Yaklaşık %50'lik bir CPU kullanımı var. Yani CPU'nun yarısı boşta. Bu rutin üzerinden ilerleyecek olursam şu anki haliyle sinüse bağlı hareket eden 50 FPS hızında 512 nokta basılabilir gibi duruyor. Tabii hatırlatıyorum, bu filmin ortası.
Ekte ekran görüntüsü, TAP dosyası ve kaynak kodu paylaşıyorum. Şu anda yorgun argın şehirlerarası yolculuktan gelmiş olduğum için sadece materyalleri paylaşıyorum. Biraz dinlendikten sonra fırsat buldukça ZX Spectrumla karşılaştığımız bir cafede ilk muzip bakışmamızdan şu ana kadar geçen süreci yazıya dökeceğim.
-
Amacım sizlerle Z80 maceramda yaşadıklarımı hızlıca paylaşmak olduğu için oldukça özet biçimde ilerlemeye çalışacağım. Yani ilk Z80 ile ne zaman tanıştım, ne zaman öğrenmeye başladım, hangi yollardan geçtim, bunları paylaşmak istiyorum.
I. Z80 İle İlk Tanışma
Parmak emmeden hallice yaşlardan beri 6502 tabanlı bilgisayarlar kullanan ve zamanla 6502/6510 Assembly öğrenmeye başlamış biri olarak Z80 uzun yıllar hiç hayatıma girmedi. 2000'li yıllara kadar Z80 benim sadece bir işlemci olarak varlığını bildiğim ve arada meraktan açıp dokümantasyondan opcodelarına ve registerlarına bakıp, "amma da çok registerı varmış, hem de 16-bit registerları bile var" dediğim, bundan bir gıdım fazlasını bilmediğim bir işlemciydi. 7DX Partileri sırasında Gameboy sık sık gündeme geliyordu, hatta Gameboy ile canlı müzik yapan bile çıkmıştı. O zaman yaptığımız sohbetlerden birinde 90'lı yıllarda oynamaktan çok keyif aldığım Gameboy'un içinde de Z80 olduğunu, ancak çok daha limitli, bazı özellikleri eksik bir Z80 olduğunu öğrenmiştim. Yani ne ZX Spectrum, ne Amstrad CPC, ilk merakı yaratan aslında Gameboy olmuştu. Tabii ki Gameboy henüz kuyruğun sonuna geçeceğinden bihaberdi.
Yıl 2011'e geldiğinde Amstrad CPC scene'inde Batman Group bombayı patlattı, "Batman Forever". Bana hediye olarak gelmiş bir Amstrad CPC'ye sahiptim ama hakkını veremiyordum, kenarda yatıyordu öylece. Yani elimde mevcut bulunan tek Z80 tabanlı platformda adamlar hatırı sayılır demo efektleri içeren bir demo patlattılar. Tabii hemen çevremdeki @Alcofribas, @Ref, @matahari, @ssg gibi Z80 tabalı platformlara aşık insanlardan da tepkiler gelmeye başladı. @Alcofribas her RAAT partisinde parti boyunca sık sık Batman Forever izleterek bizleri Batman'den soğuturken, Amstrad CPC'ye ve kendi adıma Z80'e ilgimizi artırmaya başladı, bizi alttan alttan işledi.
@matahari ve @ssg Amstrad CPC'de 256 byte ürünler de yayınlamışlardı o dönemlerde, @Ref ve @hades de 7DXlerde ZX Spectrum intro ve 256 byte ürünlerine imza atmışlardı. En son bir RAAT partisinde canıma tak etti... Hazır @matahari'yi de yakalamışken kendisinden biraz bize Z80 göstermesini istedim. Yanlış hatırlamıyorsam @Fero bir Z80 kodu ile uğraşıyordu. Sağ olsun @matahari gelip bize hızlandırılmış bir Z80 kursu verdi. O gün ile ilgili @Alcofribas'ın her zamanki misafirperverliği ve çok eğlendiğim haricindeki bir çok detay aklımdan uçup gitmiş ancak @matahari'nin ağzından çıkan teknik kelimelerden tek bir tanesinde bile kayıp olmadı, hepsini belleğime kazıdım.
2018'e geldiğimizde hala Z80'de tek satır kod yazmış değildim. @Ref o sene RAAT partisinde "an Unexpected Blizzard" isimli bir ZX Spectrum ürünü yayınladı, sadece yayınlamakla da kalmadı, üzerine yarı sunum yarı sohbet havasında bir de konuşma yaptı. Ben sonrasında konuşmanın uzunluğu ile ilgili kendisine çok takıldım, şakalar yaptım. Ama elbette ki yine benim için teknik açıdan çok faydalı oldu ve belleğime kazınan bilgiler arasına girdiler.
2019 Ocak Ayı'na geldiğimizde ilk kez kolları Z80 programlama için sıvadım. Amstrad CPC'nin donanım özellikleri ve Z80'i birlikte öğrenmeye karar verdim. Sırasıyla;
- Amstrad CPC'nin grafik ekranı özlellikleri
- Kesme rutinleri ve ekran başlangıcında tarama yakalatma
- Müzik çaldırma
gibi şeyleri öğrendim. Jaruzi efekti diye tabir edebileceğim, sağdan soldan şeritler halinde çizilerek ekrana grafik basma gibi bir şeyler yaptıktan sonra dedim en temizi Amstrad CPC'de bir grup kuralım, yerli bir ürün çıkaralım CPC'ye. Türkiye'de Amstrad CPC ile ilgilenen tanıdığım herkes ile görüştüm, gruba davet ettim, sağ olsun herkes destek vereceğini de söyledi. Ancak bir süre benden pek bir aktivite çıkmayınca şimdilik o proje uykuda. Amaç aslında @Alcofribas'a RAAT partisinde bir Amstrad CPC ürünüyle sürpriz yapmaktı. Ama artık sürprizlik bir durumu kalmadığı ve günün birinde yapacak olursak yine de sürpriz olacağı için açıklamakta sakınca duymuyorum. Araya giren pandemi iyiden iyiye planlarımızı alt üst etti.
Amstrad CPC'de Z80 ile bir şeyler yaparken işlemciye hiç hakim değildim. Yani hep istediğimi yapmanın bir yolunu buluyordum ama hala kendi yazdığım kodlar bana yabancı görünüyordu. Benzerini geçmişte Motorola 68k, x86 ve ARM'da da yaşamıştım. 6502'ye olan aşırı ilgim diğer platformlara alışmamı güçleştiriyordu. Ama Z80 yapı olarak bana çok ters gelmemişti. Biliyordum ki zamanla hepsi oturacak. İşlerim de yoğunlaşınca o oturma süresini doğal olarak sağlamış oldum, bir kaç sene Z80 ile hiç ilgilenmedim. Ta ki geçtiğimiz ay olan Nisan 2022'nin son 10 gününe kadar.
-
II. ZX Spectrum'un 40. Yılı Şerefine
Bu forumda yakın zamanda yayınlanan, teknik olduğu kadar edebi olarak da yüksek değer içeren @matahari'nin yazıları tahmin ediyorum okuyan herkese ilham vermiştir. Beni zaten zor tutuyorlardı, o yüzden dedim artık bu işin vakti geldi. Ama yine de son bir kıvılcım iyi gider dedim. Bir baktım @hades başlık açmış.
Z80 Makine diline meraklı olan? (https://retrojen.org/pano/index.php?topic=539)
Hani ilkokuldaki sorunun cevabını bilmenin verdiği heyecanla parmak kaldırmakla yetinmeyip bir de parmağı sağa sola sallayan öğrenci vardır ya, o hislere büründüm. @hades yazdığı bazı rutinleri ve aynı işi farklı şekillerde yapma yöntemlerini paylaşıyordu. Doğrudan kaynak kodlar uçuşuyordu paylaşımlarında. Benim böyle başlamamam lazım dedim. Çünkü paylaşacağım kodlar birilerine kötü örnek olabilirdi. O yüzden bir şeyler yapıp, gelişme aşamalarımı paylaşıp, ilk hedefime ulaştığımda ise kötüden iyiye doğru sırasıyla paylaşmayı uygun gördüm. ZX Spectrum'un grafik ekranının byte dizilimi biraz farklı olduğu için ilk olarak ona hakim olmamı sağlayacak bir noktadan başlamaya ve bir plot rutini yazmaya karar verdim. Her ne kadar @Ref belki kendi kar yağdırma rutini ile kapışmaya çalıştığımı düşünse de amacım ZX Spectrum platformunda "ekrana nokta basmayı öğrenmek"ten ibaretti. Çünkü ekrana bir soft sprite basacaksam, polygon çizdireceksem önce nokta koyabiliyor olmam lazım. Bir diğer hedef ise ekrana grafik, logo basabilmekti.
Şu sıralar Commodore 64'de demo geliştirme araçlarımı tamamen Python dilinden scriptlerle yazmaya başladım. Aslında kullandığım Kick Assembler o kadar güzel özellikler içeriyor ki, Kick Assembler içinde Java dili yapısında bir scripting desteği ile dışarıdan hiç bir ek geliştirme aracına ihtiyaç duymadan işi çözebildiğim oluyor. Ama iş çapraz geliştirmede grafikleri hedef platforma uygun hale getiren converter araçlar yazmaya gelince, derleme sürelerini de çok olumsuz etkileyeceği için Python kullanmayı tercih ediyorum. Sadece Commodore 64 için de değil. Atari 800 XL, Commander X16, hepsinde araç zincirim bu şekilde kurulmuş durumda. ZX Spectrum da bu konuda bir istisna teşkil etmedi. Hemen ihtiyacımı görecek kadarıyla bir script yazıp yayınladım. Zamanla bu scripti geliştireceğim. Şimdilik 8 parlak renge göre ayarlanmış durumda. Gelişmeleri aşağıdaki github linkinden takip edebilirsiniz.
https://github.com/c64skate/zx-spectrum-bitmap-converter (https://github.com/c64skate/zx-spectrum-bitmap-converter)
Bu script ile artık PNG olarak oluşturduğum speccy grafiklerini ZX Spectrum formatına çevirip görüntüleyebilecektim. Ama hala geliştirme ortamım hazır değildi. Sıradaki işler IDE, derleyici, debugger seçimi, özetle geliştirme araç zincirinin belirlenmesi ve kurulması.
-
@Skate eline sağlık, @matahari'den sonra senin de bu konularda yazmana çok sevindim. ayrıca zirvede olduğun C64'teki konfor alanından çıkmana da şapka çıkartıyorum.
-
@Skate eline sağlık, @matahari'den sonra senin de bu konularda yazmana çok sevindim. ayrıca zirvede olduğun C64'teki konfor alanından çıkmana da şapka çıkartıyorum.
Çok teşekkürler Sedat. Yazıya devam ediyorum şu anda. Tecrübeli z80ciler olarak sizlerin okurken eğleneceğinizi tahmin ediyorum. Kulağımı epey tersten göstermişimdir. Ama keyifli olan da oydu, dediğin gibi konfor alanından çıkınca o ilk heyecanlar geri geliyor. 40 yıldır herkesin bildiği bir şeyi yeniden keşfedip sevinmek falan. :)
-
III. Çapraz Geliştirme Araçları
Bu kısım, geliştirmeye kazandırdığı rahatlık açısından önemli kısımlardan biri. Ama yıllarca çok limitli koşullarla, yeri geldiğinde derleyicinin kendi kodları ve hafızadaki kaynak kodların üzerine program derlemeye alışmış kişiler olarak hızlı karar almayı tercih ettim. Sonuç olarak yazdığım kodu derlememi sağlayacak herhangi bir derleyici temelde yeterliydi. Tabii bunu söylerken aklımda hep Commodore 64'de son senelerde hayranlıkla kullandığım Kick Assembler çıtayı yükseltiyordu. Kick Assembler'ın sağladığı olanaklar, bugüne kadar bazen saatlerimi, hatta günlerimi alan çeşitli işleri her derlemede parametrik olarak yapabilmemi sağlayan scripting özellikleri ve platforma özel olarak geliştirilmiş grafik modları, ses dosyaları şablonlarını tanıma gibi yetenekleri seçimi yaparken biraz daha iyi bir şeyler aramama neden oldu.
Commodore 64 için bir çok Visual Studio Code plugin'i olduğunu biliyor, gelişimlerini takip ediyorum. Ancak oturmuş, sorun yaşamadığım geliştirme platformumdan dolayı Visual Studio Code ve benzeri IDE alternatiflerini kullanmayı tercih etmemiştim. Şimdi yeni bir platform ile temiz bir sayfa açarken Visual Studio Code pluginleri tereddütsüz ilk tercihim oldu. Ama çok fazla sayıda olduklarını da tahmin ettim. Bu durumda iki şansım vardı, ya bir bilene danışacaktım ya da Google'a. Sonra Commodore 64'de kendi kullandığım geliştirme ortamımın o kadar da modern olmadığı geldi aklıma. Dedim iyisi mi ben bir bilenden önce Google Amca'ya sorayım, bakarsın 2022'de yeni bir platform / plugin çıkmıştır, ilk ben keşfederim. Yani böyle biraz da kendimi kandırarak hızlı bir araştırma yaptım. Ulaştığım Visual Studio Code içeren ilk referans sayfası şu oldu.
Z80 Development Toolchain (http://www.breakintoprogram.co.uk/computers/zx-spectrum/assembly-language/z80-development-toolchain)
Bu sayfadan sadece isimleri seçtim.
- Visual Studio Code plugini: Z80 Assembly by Imanolea
- Visual Studio Code plugini: DeZog by Maziac
- ZEsarUX Emulatörü
- SjASMPlus Derleyicisi
Zaten hali hazırda kullandığım ZX Spectrum emülatörlerim vardı. Bunları belki geliştirme zincirinde uyumlulukları vardır diye seçtim. Ancak kurduğum versiyonlar sitede önerilenlerden çok daha ileri versiyonlardı. Anlaşılan gelişme sürecinde bazı uyumsuzluklar yaşanmış. Örneğin ZEsarUX emülatörünün remote debugger özelliği çalışsa da derlediğimde emülatörün çökmesine neden oluyordu. Ben de çözüm olarak hızla benzer pluginleri kullanan kod örnekleri aradım ve bir tane buldum. Bulduğum örnek kullandığım pluginlerden DeZog'u yazan Maziac'a aitti.
GitHub - maziac / z80-sample-program (https://github.com/maziac/z80-sample-program)
Bu repodaki .vscode içerisinde yer alan dosyalar çok yol gösterici oldu. Böylece Asm Code Lens, Z80 Instruction Set, SNA File Viewer gibi diğer bazı pluginleri de öğrenmiş oldum. Gerçi bunların bir kısmını gerekli görmeyip kullanmadım. Yine de bu çapraz geliştirme platformumu kurmak adına son durağım oldu. Bu repoyu inceledikten 20 dakika sonra artık istediğim kodu yazıp derleyebiliyordum.
Burada anahtar kelimeler Visual Studio Code, DeZog (by Maziac), Z80 Assembly (by Imanolea), SjASMPlus. Bunlar yeterli ama işin lüksüne de kaçmanız mümkün. Ekte son durumda kullandığım geliştirme ortamından iki ekran görüntüsü örneği paylaşıyorum.
Böylece artık kod yazmaya hazır hale gelmiş oldum. Sanırım geliştirme platformunu ayağa kaldırmam bu gönderiyi hazırlamamdan kısa sürdü. Bu nedenle kesinlikle "en mükemmel, en modern çözüm" diye düşünmeyin. Ancak bir kaç eksiği olmasıyla birlikte işimi gayet iyi görüyor. Eksiklerinden de kısaca bahsedeyim.
- Düzgün bir hafıza görüntüleyicisi yok. Konsoldan komutlar yazarak ekrana getirebildiğim hafıza blokları oldu ama günümüzün modern hafıza görüntüleyici örneklerine kıyasla biraz zayıf gibi. Tabii bu başka bir Visual Studio Code plugini ile de çözülüyor olabilir. Şimdilik basit işlerle uğraştığım için bu konuyu sonraya bıraktım.
- SjASMPlus'ın desteklediği scripting dili LUA. İlk başta LUA'yı görünce sevinmiştim. Bir süre CoronaSDK ile kullanmıştım, sevmiştim de. Ancak sanırım burada kullanılan versiyon bayağı eski ya da eksik özelliklere sahip. Çok temel işlemleri bile yapamıyor. Kaynak kodumun sonunda şöyle bir kısım göreceksiniz.
; LUA common definitions and functions
LUA ALLPASS
OR, XOR, AND = 1, 3, 4
function bitoper(a, b, oper)
local r, m, s = 0, 2^31
repeat
s,a,b = a+b+m, a%m, b%m
r,m = r + m*oper%(s-a-b), m/2
until m < 1
return r
end
ENDLUA
Bu kodu ben yazmadım, internetten olduğu gibi bulup aldım. Bit tabanlı işlemler için bunu kullandım. AND, OR, XOR içermiyormuş LUA, yani en azından SjASMPlus ile birlikte gelen versiyonu. LUA'yı araştırdığımda yeni versiyonda bu tarz desteklerin geldiğini gördüm. Ama şimdilik hiçbirini çalıştıramadım SjASMPlus bünyesinde. Geçici olarak böyle ucube fonksiyonlar kullanıyorum. Tabii proje küçük olunca derleme sürelerini etkilemiyor ama fonksiyonu gördükçe gözüm seğirmiyor dersem yalan söylemiş olurum.
Bu anlattığım dez avantajların henüz benim bazı özellikleri çözememiş olmamdan kaynaklanabileceğini hatırlatmak isterim. Zaman zaman "peeh, becerememişler" dedikten kısa süre sonra "peeh, meğerse ben becerememişim" demişliğim vardır. Bu yüzden şimdilik bunları potansiyel dezavantajlar olarak nitelendiriyorum. Hatta ilerleyen dönemde geliştiricileri ile irtibata geçip, bana eksik gelen kısımları tamamlamaları için destek de olabilirim. Nitekim Kick Assembler'a 65c02 işlemci desteğini bu şekilde ekletmiş ve Commander X16 için daha konforlu, ekstra opcodelar için yazdığım gereksiz makroları çöpe atarak devam etmiştim. Bu geliştirici araçlarında bir iki eksikten dolayı projeden hemen vazgeçmemek gerekiyor. Ancak öksüz kalmış projelerde mecburen alternatif arayışına gitmek durumunda kalıyoruz elbette ki.
-
IV. ZX Spectrum / Z80 Kullanarak İlk Efekti Programlama
"I. Z80 İle İlk Tanışma" bölümünde zaten Z80 ile flörtleştiğimden bahsetmiştim. Yani belli bir temel ancak oturmamış bilgim mevcuttu. En büyük sorun da 6502 ile mimari farklılıklarından kaynaklanıyordu. Örneğin bir değeri bir tablodan index ile okumak ve yazmak 6502'de çok kolay yapılan bir şeydir.
ldx $9000,y
lda $9100,x
Bu kod $9000'de yer alan tabloda y kadar ilerideki değeri x registerına atar ve ikinci satırda $9100'deki tabloda az önce okuduğumuz x değeri kadar ileriden okunan değer accumulatore aktarılır. Bunun z80'de bu kadar kolay bir karşılığı ne yazık ki bulunmuyor. Aynı işi yapan bir z80 örneği. x / y registerları olmadığı için örneğin b registerını kullanacağım.
; ldx $9000,y karşılığı, y yerine b kullanıp, sonucu da x yerine yine b'ye alacağız
ld hl, $9000
ld a, l
add a, b
ld l, a
ld b, (hl) ; sonucu yine b'ye alıyoruz, x yerine de b'yi kullanacağız
; lda $9100,x karşılığı, x yerine b kullanıyoruz
ld hl, $9100
ld a, l
add a, b
ld l, a
ld a, (hl)
Tabii bu optimize edilebilecek bir örnek, mesela;
ld hl, $9000
ld a, l
add a, b
ld l, a
ld l, (hl) ; sonucu l'ye alıyoruz
ld h, $91
ld a, (hl)
ya da tabloların $100'lük alanlara oturduğundan yola çıkarak daha da kısa biçimde;
ld h, $90
ld l, b
ld l, (hl)
ld h, $91
ld a, (hl)
Bu başka şekillerde de yazılabilir, işe diğer registerlar da girebilir, ix, iy de kullanılabilir, kendini modifiye eden (self modifying) kod da kullanılabilir. Ama öncelikle yaşadığım kaosu anlamanız için bu örneği paylaştım. 6502 sadece 3 adet 8 bit registerı olan (a, x, y) oldukça primitif bir işlemci. Yani z80'in 16 bit registerları, blok halinde hafıza kopyalamak için getirdiği özellikler, shadow registerlar incelendiğinde şu kodu yazmanın bu kadar uzun tutabileceğini düşünmüyor insan. Bunun bu şekilde uzun olmasındaki en büyük faktörlerden biri 8 bit ve 16 bit işlemlerin ayrı tutulmuş olması ve her registerın accumulator kadar yetenekli olmayışı. Örneğin;
ld hl, $9000
add hl, b
diyebilsek "ld a, l", "add a, b", "ld l, a" bu şekilde üç opcode harcamamız gerekmeyecek. Bunu diyemiyoruz çünkü "add" opcode'u ya 8 bit ile 8 bit ya da 16 bit ile 16 bit toplayabiliyor. Hay hay, zaten tablomuz $100'lük alanlara oturtulmuş durumda, bir byte ile topladığımızda low byte taşma yapmayacak. O zaman sadece low byte'ını toplayalım gitsin.
ld hl, $9000
add l, b
Voila! Değil ne yazık ki çünkü add 8 bit registerlarda sadece accumulator ile toplama yapabiliyor. Yani bu durumda hl'e b'nin değerini eklemek için iki şansımız kalıyor geriye. Birincisi yukarıda gördüğünüz gibi l'yi a'ya aktar, a ile topla, l'ye geri aktar yöntemi. Ya da alternatif olarak şunu kullanabiliriz.
ld hl, $9000
add hl, bc
bc yerine de kullanmamız da mümkün. Ama işte bunları 16 bit olarak kullandığımızda high bytelarının da boş olduğundan emin olmamız ya da boşaltmamız lazım. Bu nedenle ilk yazdığım versiyonlarda sık sık 16 bit kullanmaya çalışma, high bytelarında gelen değerleri de hesaba katarak tablolar üretme gibi boşa kürek çekip durdum. Bu konudaki mücadelemi adım adım paylaşıyor olacağım.
-
Bir yerlerden başlamak lazım...
Öncelikle ZX Spectrum'un hafıza yerleşiminin biraz farklı olmasından ötürü ilk yapmam gereken işi biliyordum. Y ekseninde tüm satır başlarına denk gelen hafıza adreslerini sıralı biçimde tablolamak. Bunun için JavaScript'den kısa bir tablo oluşturucu yazdım. Bunu yazma nedenim henüz SjASMPlus'ın özelliklerine hakim olmamam ve hızlıca ekranda pikselleri görmek istememdi.
Yazdığım scripti aşağıdaki URL'den görebilirsiniz.
https://jsfiddle.net/90L6heub/ (https://jsfiddle.net/90L6heub/)
Bu tabloyu da kullanarak yazdığım ilk örnek şu şekilde. Sonucu da eklerde bulunuyor. Ayrıca comment out ettiğim kodlar, derlenmesi için gereken diğer kısımlar da ekteki main_01.asm dosyasında mevcut.
ORG $8000
main:
; Disable interrupts
di
ld sp,stackTop
; fill color memory
ld hl,$5800
ld e,l
ld d,h
inc de
ld (hl),$07
ld bc,$300
ldir
; put pixel
ld b, $aa
ld a, 0
ld de, readHere+1
loop1:
ld (de), a
readHere:
ld hl, (lines)
ld (hl), b
ld i, a
ld a, 255
xor b
ld b, a
ld a, i
add a, 2
cp a, 64*2
jr C, loop1
loop2:
halt
jr loop2
ALIGN $100
lines:
defw $4000, $4100, $4200, $4300, $4400, $4500, $4600, $4700
defw $4020, $4120, $4220, $4320, $4420, $4520, $4620, $4720
defw $4040, $4140, $4240, $4340, $4440, $4540, $4640, $4740
defw $4060, $4160, $4260, $4360, $4460, $4560, $4660, $4760
defw $4080, $4180, $4280, $4380, $4480, $4580, $4680, $4780
defw $40a0, $41a0, $42a0, $43a0, $44a0, $45a0, $46a0, $47a0
defw $40c0, $41c0, $42c0, $43c0, $44c0, $45c0, $46c0, $47c0
defw $40e0, $41e0, $42e0, $43e0, $44e0, $45e0, $46e0, $47e0
defw $4800, $4900, $4a00, $4b00, $4c00, $4d00, $4e00, $4f00
defw $4820, $4920, $4a20, $4b20, $4c20, $4d20, $4e20, $4f20
defw $4840, $4940, $4a40, $4b40, $4c40, $4d40, $4e40, $4f40
defw $4860, $4960, $4a60, $4b60, $4c60, $4d60, $4e60, $4f60
defw $4880, $4980, $4a80, $4b80, $4c80, $4d80, $4e80, $4f80
defw $48a0, $49a0, $4aa0, $4ba0, $4ca0, $4da0, $4ea0, $4fa0
defw $48c0, $49c0, $4ac0, $4bc0, $4cc0, $4dc0, $4ec0, $4fc0
defw $48e0, $49e0, $4ae0, $4be0, $4ce0, $4de0, $4ee0, $4fe0
defw $5000, $5100, $5200, $5300, $5400, $5500, $5600, $5700
defw $5020, $5120, $5220, $5320, $5420, $5520, $5620, $5720
defw $5040, $5140, $5240, $5340, $5440, $5540, $5640, $5740
defw $5060, $5160, $5260, $5360, $5460, $5560, $5660, $5760
defw $5080, $5180, $5280, $5380, $5480, $5580, $5680, $5780
defw $50a0, $51a0, $52a0, $53a0, $54a0, $55a0, $56a0, $57a0
defw $50c0, $51c0, $52c0, $53c0, $54c0, $55c0, $56c0, $57c0
defw $50e0, $51e0, $52e0, $53e0, $54e0, $55e0, $56e0, $57e0
Burada en çok dikkat çekmek istediğim nokta her Y satırının adres değerlerini okuyan kısma koyduğum "readHere" isimli etiketi kullanarak "readHere+1" adresine değer yazarak "ld hl, (lines)" adresinde lines tablosunu "şu kadar ileriden oku" demiş oluyorum. Bir önceki paylaşımımda örneklediğim yöntemler kafamda henüz tam olarak oturmamış durumda. Ben de opcodelarla boğuşmak yerine kodu modifiye ederek ilk sonucu görüyorum. Evet, ZX Spectrum'da ekrana bir şeyler çizdirilebiliyormuş, 2022 Nisan ayı sonlarına doğru bunu kanıtlamış olmak beni mutlu ediyor, derhal madalyamı bana takdim ediyorlar. :)
Bu arada "ld a, 0" yerine "xor a" gibi optimizasyonlardan da haberdarım, hatta @Ref ile yakın zamanda yaptığımız bir telefon görüşmesinde de geçmişti. Dediğim gibi bu aşama "sıfır optimizasyon, önce sonuç" şeklinde yazılmış bir kod.
-
İkinci versiyonda koda AND / OR işlemleri için gerekli tabloları ekliyorum. Burada amaç X'in alt 3 bitini, diğer bir deyişle 8 ile bölümünden kalanını bulup, daha sonra ona uygun bit değerini bulmakla zaman kaybetmemek. Doğrudan tablo 0-255 arası olabilecek tüm değerleri içermeli. ZX Spectrum'un çözünürlüğünün 256x192 olması da burada epey yardımımıza koşuyor. 9. bit derdimiz yok, tüm koordinat sistemi tablolardan hızlı okumalar yapmamıza uygun değer aralığında. Kodun tamamını paylaşmam uzun olacağından kodu ekte paylaşıp, burada sadece önemli noktalara değiniyorum.
mainLoop:
; put pixel
ld hl, (plotX)
ld de, orBit
add hl, de
ld b, (hl)
ld de, (plotY)
add de, de
ld a, e
ld de, readHere+1
ld (de), a
ld a, l
sra a
sra a
sra a
ld e, a
ld d, 0
readHere:
ld hl, (lines)
add hl, de
ld a, (hl)
or b
ld (hl), a
ld hl, plotX
inc (hl)
ld a, (hl)
and 3
cp 2
jp C, skip1
ld hl, plotY
inc (hl)
skip1:
ld a, (hl)
cp 128
jp C, mainLoop
halt
plotX:
defw #0000
plotY:
defw #0000
Dikkat ederseniz asla 16 bit değerlere ulaşamayacak olsalar da koordinatlar için 16 bitlik değerler tanımlamışım. Bu o aşamada 16 bitlik registerlarla yapacağım işlemlerin daha hızlı olabileceğini düşünmemden kaynaklanan içgüdüsel bir hareket idi.
ALIGN $100
orBit:
defb %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010, %00000001
defb %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010, %00000001
...
ALIGN $100
andBit:
defb %01111111, %10111111, %11011111, %11101111, %11110111, %11111011, %11111101, %11111110
defb %01111111, %10111111, %11011111, %11101111, %11110111, %11111011, %11111101, %11111110
...
Bu tablolardan şimdilik sadece OR'a ihtiyaç duyuyoruz. Hatta tek bir nokta silmemiz gerekmediği için kodun son ulaştığı noktaya kadar AND tablosu hiç kullanılmadı. Yine de alışkanlıktan oluşturmuşum bu versiyonda, yeri gelir gerekir diyerek.
basic_loader:
db $00,$0a,$0e,$00,$20,$fd,"32767",$0e,$00,$00,$ff,$7f,$00,$0d ; 10 CLEAR 32767
db $00,$14,$07,$00,$20,$ef,$22,$22,$20,$af,$0d ; 20 LOAD "" CODE
db $00,$1e,$0f,$00,$20,$f9,$c0,"32768",$0e,$00,$00,$00,$80,$00,$0d ; 30 RANDOMIZE USR 32768
basic_loader_end:
...
SAVESNA "first.sna", main
EMPTYTAP "first.tap"
SAVETAP "first.tap",BASIC,"loader",basic_loader,basic_loader_end - basic_loader, 10
SAVETAP "first.tap",code,"first",main,endOfCode-main,main
Bu kısımsa internetten bulduğum bir hazır bir basic loader kodu ve o kod ile SjASMPlus'ın özelliklerini kullanarak snapshot dosyasının yanısıra TAP dosyası da oluşturmak için eklediğim kısım. @hades'in açtığı bir başlıkta bu yöntemden daha iyisi, yani doğrudan tek parça halinde çalışan Basic+ASM kodu tartışıldı ama ben o işi önceliklendirmedim ve kod günümüze kadar bu şekilde, basic loader + ASM parçası şeklinde iki parçalı olarak geldi. Bu konu yapılacak işler listemde duruyor, henüz sıra gelmedi.
-
Üçüncü versiyona geldiğimizde ilk olarak plot rutinindeki bazı hataları gideriyoruz. Hala optimizasyon derdimiz yok, "srl a" komutları 8'er tstate yiyorlar, bu bana çok batıyor. Ama içimden diyorum ki "ben yine büyük ihtimalle bu kısmı tablolarla çözeceğim, şimdilik olduğu gibi kalsın". Sonuçta daha geniş bir alana çizim yapabilecek hale geliyor kod.
mainLoop:
; put pixel
ld hl, (plotX)
ld de, orBit
add hl, de
ld b, (hl)
ld de, (plotY)
add de, de
ld a, e
ld de, readHere+1
ld (de), a
ld a, l
srl a
srl a
srl a
ld e, a
ld d, 0
readHere:
ld hl, (lines)
add hl, de
ld a, (hl)
or b
ld (hl), a
ld hl, plotX
inc (hl)
ld a, (hl)
and 1
cp 1
jp C, skip1
ld hl, plotY
inc (hl)
ld a, (hl)
cp 128
jp C, mainLoop
halt
skip1:
jp mainLoop
-
Dördüncü versiyonda artık ekranda sadece üç beş piksel görmek canımı sıkmaya başlıyor. Plot rutinini biraz kenara bırakıp, ekrana renk katmayı seçiyorum. Bunun için de daha önce GitHub linkini paylaşmış olduğum grafik dönüştürücü Python scriptimle kendi hazırladığım geçici yarım yamalak bir logoyu ekliyorum. En azından artık ekran o kadar boş değil. Scripti de bu aşamada eklere koyuyorum. Scriptte geçen şu kısma değinmek istiyorum.
# if char area is empty, add a default color as foreground color
if(len(palette) < 2):
palette.append(7) # white color as empty area foreground color
Burada eğer o 8x8 blokta ikinci bir renk bulunamadıysa varsayılan olarak beyaz renk ekletiyorum. Bu logoyu aslında tam ekran bir grafikmiş gibi yükletip, noktaları bastığım alanın renklerini ayrıca ayarlamaya çalışmaktan kurtarıyor beni. Yani ideal, optimize çözümde logo sadece kapladığı alan kadar yükletilir, geri kalan alanın renkleri kod içinden doldurulur. Ama şu aşamada bunu hem pratik buluyorum, hem de logoyu tüm ekranı kaplayan, örneğin süslü bir çerçeve gibi bir grafikle değiştirmenin de kapısını açık bırakmış oluyorum.
Daha iyi yöntemler mutlaka vardır, en basitinden dosyalar sıkıştırılmış olarak tutulur, yüklendikten sonra açılır. Ancak şimdilik hafıza derdimiz olmadığı için logo verilerini kodun bitimine olduğu gibi açık halleriyle koyup, sonradan ekran ve renk adreslerine transfer ettirmeyi uygun görüyorum.
; Copy bitmap
ld hl, bitmapStart
ld de, #4000
ld bc, bitmapEnd-bitmapStart
ldir
; Copy colors
ld hl, colorStart
ld de, #5800
ld bc, colorEnd-colorStart
ldir
...
bitmapStart:
INCBIN "assets/bitmap.bin"
bitmapEnd:
colorStart:
INCBIN "assets/color.bin"
colorEnd:
-
Beşinci versiyonumuzda görüntüde çok bir değişim olmuyor. Ama ilk optimizasyonlar baş gösteriyor. Öncelikle 8*3 = 24 tstate harcayan "srl a" kısmı var. Biliyorum ki bu kısımlar geçici, çöpe gidecek. Ama sonuç olarak kısa bir süre sonra ekranda dolaşan noktalarımız olacak ve mevcut haliyle kodu optimize etmem lazım. Bu "srl a" içeren kısımda yaptığımız şey X koordinatı üzerinden karaker bloğu sayısını bulabilmek. Matematiksel olarak bakacak olursak yapmaya çalıştığımız şey 8'e bölüp kalanı önemsemeden sonucu almak. Çarpma bölme işlemimiz olmasa da 8 sayısı 2'nin katlarından biri olduğu için bit kaydırarak bu sonuca kolayca ulaşabiliyoruz. Commodore 64'de olsa;
lsr
lsr
lsr
İş bitti, 2'ye böl, 2'ye böl, 2'ye böl dedik ve toplamda 8'e bölerek karakter bloğumuza ulaştık. Commodore 64'de carry yani "elde" bayrağına bakan ve bakmayan iki alternatif var bit kaydırma opcodelarında. "lsr" elde bayrağına bakmıyor. "ror" dersek elde bayrağındaki değer en soldan giriyor. Bu durumda ihtiyacımız olan lsr, yani elde bayrağına bakmadan işlem yapması. Çünkü hem önceden elde bayrağı set edilmiş olabilir, hem de bitleri kaydırdıkça sağdan çıkan bitler de elde bayrağını set edebilir. Bu yüzden z80'de lsr'nin karşılığı sayılabilecek "srl a" kullanabiliyoruz. Ama 8 tstate yiyor. Halbuki "rrca" gibi 4 tstate yiyen çok daha güzel bir alternatifi var. Ne yazık ki "rrca" Commodore 64'deki "ror"un karşılığı, elde bayrağına bakıyor. Bu durumda aklıma gelen ilk çözümü uyguluyorum.
and %11111000
rrca
rrca
rrca
Baştan alt üç biti temizlersek "rrca" aynı "srl a" gibi davranacaktır. Tabii AND opcode'u 7 tstate yiyor bu haliyle (kodu yazdığımda 4 tstate yiyen register kullanan varyasyonunu fark etmemiş durumdaydım) ancak bit işlemi yapan kısımdan 4*3 = 12 tstate kazancımız olduğu için her türlü kardayız.
Şimdi daha kirli bir numaraya yelken açıyoruz.
ld de, orBit
...
ld e, a
;ld d, 0 ; orBit high byte is subtracted from lines table high bytes to get rid of this line
...
lines:
defw $4000-(orBit&$ff00), $4100-(orBit&$ff00), $4200-(orBit&$ff00), $4300-(orBit&$ff00), $4400-(orBit&$ff00), $4500-(orBit&$ff00), $4600-(orBit&$ff00), $4700-(orBit&$ff00)
defw $4020-(orBit&$ff00), $4120-(orBit&$ff00), $4220-(orBit&$ff00), $4320-(orBit&$ff00), $4420-(orBit&$ff00), $4520-(orBit&$ff00), $4620-(orBit&$ff00), $4720-(orBit&$ff00)
...
"ld de, N" 10 tstate yiyor. Ancak burada tek ihtiyacımız low byte'ı set etmek, "ld e, a" diyecek olursak 6 tstate kazancımız oluyor. Sıkıntı şu ki bu defanormal koşullarda d'nin değerini sıfırlamak gerekiyor. Ama çıkmadık candan ümit kesilmez misali d'nin önceki değerine bakıyoruz, sabit. "orBit" tablosunun high byte'ını almış durumda. Öyleyse d'yi sıfırlamadan, sonraki değer okuyacağımız "lines" tablosunun tüm değerlerinden "orBit"in başlangıç değerini çıkaracak olursak iş çözülüyor.
Gördüğünüz gibi tablolar iyice okunmaz hal aldı, artık derleyicinin özelliklerini kurcalamanın zamanı geldi gibi.
-
Altıncı versiyonumuzda kodda değişen bir şey yok. Sadece tablolar artık elle yerleştirme gibi değil, üretiliyorlar. SjASMPlus'ın bazı özellikleri doğrudan yardımcı olabilirken daha kapsamlı işler için LUA script bloğu açmak gerekiyor. Değişen kısımlar şu şekilde.
ALIGN $100
lines:
y = 0
WHILE y < 192
defw $4000 - (orBit & $ff00) + ((y / 64) & $ff) * $800 + (y % 8) * $100 + (((y & $3f) / 8) & $ff) * $20
y = y + 1
ENDW
ALIGN $100
orBit:
i = 0
WHILE i < 32
defb %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010, %00000001
i = i + 1
ENDW
ALIGN $100
andBit:
i = 0
WHILE i < 32
defb %01111111, %10111111, %11011111, %11101111, %11110111, %11111011, %11111101, %11111110
i = i + 1
ENDW
ALIGN $100
sinX:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' defb ' .. math.floor(128 + 127.5 * math.sin(i / 128 * math.pi)))
end
ENDLUA
sinY:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' defb ' .. math.floor(96 + 31.5 * math.sin(i / 128 * math.pi)))
end
ENDLUA
Henüz kullanmasak da sinX ve sinY isimli iki tablo üretmiş durumdayız.
SjASMPlus hakkında daha detaylı bilgi için şu adresi kullanabilirsiniz.
SjASMPlus Documentation (https://z00m128.github.io/sjasmplus/documentation.html)
-
Yedinci versiyonda efekt görünür olmaya başlıyor. Artık sinüs tablolarından okuduğumuz değerleri kullanıyoruz. Ama iki temel eksiğimiz var. Hem ekran taraması yakalatmıyoruz, sonsuz döngüde deli danalar gibi çizim yaptırıyoruz, hem de silme rutinimiz düz mantık alanın tamamını hiç bir optimizasyon olmadan, korkunç zaman harcayarak temizliyor. Daha sonra değişeceği garanti kısımlardan biri olduğu için "temporary clear area" diye belirtilmiş durumda kod içersinde.
mainLoop:
ld hl, (counterY)
phaseY:
ld de, sinY
add hl, de
ld a, (hl)
ld (plotY), a
ld hl, (counterX)
phaseX:
ld de, sinX
add hl, de
ld l, (hl)
ld h, 0
; put pixel
;ld hl, (plotX)
ld de, orBit
add hl, de
ld b, (hl)
ld a, l
and %11111000
rrca
rrca
rrca
ld e, a
;ld d, 0 ; orBit high byte is subtracted from lines table high bytes to get rid of this line
ld a, (plotY)
add a, a
ld hl, readHere+1
ld (hl), a
readHere:
ld hl, (lines)
add hl, de
ld a, (hl)
or b
ld (hl), a
; end of put pixel
ld hl, counterY
inc (hl)
inc (hl)
ld hl, counterX
inc (hl)
ld a, (hl)
jr NZ, skip1
; temporary clear area
ld hl,$4800
ld e,l
ld d,h
inc de
ld (hl), 0
ld bc, $fff
ldir
ld hl, phaseY+1
inc (hl)
ld hl, phaseX+1
inc (hl)
inc (hl)
skip1:
jp mainLoop
-
Sekizinciye geldik. ZX Spectrum'un dinamik stack yapısını da kullanarak Spectrum'a özel güzel bir hareket yapmanın zamanı geldi. Commodore 64'e karşı kazandığı ilk zafer diyebiliriz bu açıdan. Commodore 64'de aynısını kendini değiştiren kodlarla daha çok zaman harcayarak yapabiliyoruz. Burada ise tüm noktaları sildirmek için her noktanın çizileceği adres değerini stack'e push edip, sileceğimiz noktada stackten peş peşe çekip temizleme imkanımız var.
Dikkat! Bu tür yöntemler kullanırken stack'i başka şeylerin de kullanmadığından emin olun. Mesela arada "call" gibi komutlar kullanacak olursanız bunlar da stack'i etkileyecektir. Aslında kullanmanızda bir sakınca yok, kontrollü olduğu sürece onları da hesaba katarak, stack pointer ile oynayarak çözüme gidebilirsiniz. Ama şahsen önerim stack pointer ile çok fazla oynamamanız, stack'i aşağıda vereceğim şekilde kullandığınız durumlarda arada stack ile uğraşan başka şeylerin olmaması yönündedir. Dünyadaki en meşhur programlama sorunları çözme forumunun adının "Stack Overflow" olması tesadüfi değil.
readHere:
ld hl, (lines)
add hl, de
ld a, (hl)
or b
ld (hl), a
; end of put pixel
; push pixel memory address to stack
push hl
Burada dikkatinizi çekmek istediğim yalnızca son satır. "add hl, de" satırında artık pikseli koyacağımız hafıza adresi netleşmiş oluyor. Takibindeki satırlarda o adresin değerini okuyup, piksel'in bit değeriyle ORlayıp, yerine yazıyoruz. Bu işlemler sırasında "hl" register'ı değişmiyor. Bu noktada tek ihtiyacımız olan şey basit bir "push hl". Bu işlem 256 kere tekrarlandığı için stack boyutumuzu da 256*2 = 512 byte olarak belirlemiş durumdayız.
Döngü bitip sıra noktaları temizlemeye geldiğinde ise şunu yapıyoruz.
; clear plots
ld a, 0
clearLoop:
pop hl
ld (hl), 0
inc a
jp NZ, clearLoop
Bu elbette ki böyle kalmayacak, sadece ilk deneme. Daha sonraki versiyonlarda bu döngüyü açacağız ve z80 öğrenme sürecimizde gözden kaçırdığımız bazı şeyleri fark edip daha da optimize edeceğiz.
-
Dokuzuncu versiyonda çok ufak bir ilerleme var, sonuç hala titreşen noktalar şeklinde. Ama artık her şeyi yapıyoruz, tarama da yakalatıyoruz, noktaları silme rutinimiz de son haline ulaşmış olmasa da döngü şeklinde değil, açık ve hızlı çalışıyor.
; end of frame
; wait for the next frame
ei
ld a, 0
out (0xfe), a
halt
di
Burada sonraki frame'i bekletiyoruz. "ld a, 0" ve "out (0xfe), a" border rengini değiştirip tarama bölgesini görmek için ekleyip o şekilde unuttuğum bir parça, lüzumlu değil. Geri kalan "ei", "halt", "di" benim o zamanki mantığıma göre şunu yapıyor. Kodun başındaki "di" kesme rutinlerini kapatıyor. Ben çizimimi yapıyorum, işim bitiyor. Bu noktada kesme rutinlerini açıp "halt" ile yeni bir kesme rutini tetiklenene dek işlemciyi boşa çekiyorum. Kernel'in kesme rutini çalıştığı noktada tekrar kesme rutinlerini kapatıp sonraki frame'e yelken açıyorum. Ancak burada henüz bilmediğim ve @Ref ile bir telefon görüşmemizde öğrendiğim bir şey var. O da kernel'in tarama yakalama işlemi sonrası epey bir tstate'i çöpe attığı. Bunu ilerleyen versiyonlarda çözeceğiz.
; clear plots
i = 0
WHILE i < 256
pop hl
ld (hl), 0
i = i + 1
ENDW
Bu rutin ideale yakın. Peş peşe 256 kere "pop / ld" ile sadece çizdiğimiz noktaların dokunduğu byteları temizliyor. Ancak buradaki sorun "ld (hl), 0" opcode'unun benim düşündüğüm gibi en az tstate yiyen versiyon olmayışı. Sıfır gibi sabit bir değer yerine bir değişken kullansak daha az tstate harcamış oluyoruz. Bunu da sonradan fark edip düzelteceğim.
-
Onuncu versiyonumuzda artık elle tutulur bir şeye ulaşmış durumdayız. Hala eksikleri varsa da güzel olan ekranda bir titreme olmadan noktalar dolaşıyor. Ama kaç tane nokta dolaşıyor? Saymakla uğraşmasam da net cevabım şudur "256'dan az". Aslında 256 nokta çiziyoruz ve bunu bir frame'e sığacak zamanda çizdirebiliyoruz, frame taşması yaşanmıyor. Ancak ZX Spectrum 48k'da da çalışmasını hedeflediğimiz için double buffer kullanma şansımız yok. Yani bizim o anda ekrana çizmek istediğimiz bir noktanın Y koordinatı o anda ışın taramasının bulunduğu Y koordinatından küçükse o nokta sonraki frame'de görünür olacak şekilde ekrana henüz çizilemiyor ama biz sonrasında da tüm noktaları temizleyerek o noktanın asla çizilememesini sağlamış oluyoruz. Bu sorunla daha sonra mücadele edeceğiz.
ld b, 0
mainLoop:
phaseX:
ld hl, sinX
ld a, l
add a, b
ld l, a
ld l, (hl)
ld h, 0
; put pixel
;ld hl, (plotX)
ld de, orBit
add hl, de
ld c, (hl)
ld a, l
and %11111000
rrca
rrca
rrca
ld e, a
;ld d, 0 ; orBit high byte is subtracted from lines table high bytes to get rid of this line
ld a, (counter)
add a, 2
ld (counter), a
jr NZ, skipHiIcrement
ld hl, plotYlookup+2
inc (hl)
skipHiIcrement:
ld hl, plotYlookup+1
ld (hl), a
plotYlookup:
ld hl, (sinY)
add hl, de
ld a, (hl)
or c
ld (hl), a
; end of put pixel
; push pixel memory address to stack
push hl
djnz mainLoop
Buradaki en önemli değişimlerden biri artık b registerını ve "djnz" opcode'unu kullanıyor olmamız. djnz b register'ını bir azaltıp, sıfıra gelmediği sürece döngüye devam etmeyi sağlayan, sıfıra gelince döngüyü sonlandırıp devam etmesine yarayan, x86 ASM'deki "cx register'ı ve loop opcode'u" ikilisinin bir benzeri. Önceki versiyonlarda djnz'yi bilsem de tüm registerları rahatça kullanabilmek adına kullanmamıştım. Bu aşamada artık b'yi boşa çıkarıp bu iş için kullanma vaktimiz gelmiş durumda.
Henüz tablolarımız tamam değil. Bu nedenle bu versiyonda eklenen bazı kısımlara çok değinmiyorum, zaten yanlış yöntemler. Önümüzdeki versiyonlarda bu kısımları toparlayacağız.
-
On birinci versiyona geldiğimiz şu noktada ışın taraması kovalama işi ile ilgili biraz canım sıkkın. Çünkü o iş hem uğraştıracak, hem yeterince esnek olmayacak. Örneğin noktaları farklı sinüs tablolarına göre çizdirdikçe, 3 boyutlu dönen noktalar gibi başka efektlere dönüştürdükçe, efektin zamanlaması değiştikçe önceki çözüm çöpe gidip, bir yenisiyle uğraşmam söz konusu olabilir. Ama en azından mevcut haliyle 256 noktayı görebilmek için şöyle ümit verici bir durum var. Aslında çizilemeyen nokta sayısı o kadar da yüksek değil. Yani tek bir noktayı basan rutini yeterince optimize edebilirsek belki ışın taraması kovalamayla hiç uğraşmayabilir, o işi 256'den fazla nokta basma ya da farklı efektlere ötelemiş oluruz.
Bu ümitlerle bir kaç optimizasyon yaptım.
; reset d register which is untouched in the main loop
ld d, 0
; reset loop counter
ld b, 0
d register'ının orBit tablo değerinin high byte'ını aldığını ve bu yüzden diğer tablolardan değer çıkardığımızı hatırlarsınız. Bu versiyonda d'yi tamamen boşa çıkararak sadece;
; find char column
ld a, l
and %11111000
rrca
rrca
rrca
ld e, a
...
plotYlookup:
ld hl, (sinY)
add hl, de
ld a, (hl)
or c
ld (hl), a
; end of put pixel
Bu şekilde kullanıyoruz. Yani bir noktada e'ye bir değer verip, ileride "add hl, de" şeklinde 16 bitlik bir toplama yapıyoruz. Ama d döngü içerisinde hiç değer değiştirmediği ve başta da sıfıra eşitlendiği için sorun olmuyor, hl'e asıl amacımız olan 8 bitlik değeri 16 bitlik register kullanarak eklemiş oluyoruz.
; clear plots
xor a
i = 0
WHILE i < $100
pop hl
ld (hl), a
i = i + 1
ENDW
Sonunda daha önce de bahsetmiş olduğum "ld (hl), 0"ın "ld (hl), a"ya kıyasla 3 tstate fazla tuttunu fark etmiş olacağım ki bu noktada düzeltmiş ve 3*256-4 = 764 tstate boşa çıkarmış oldum. Accumulator'ün değerini sıfırlamak için eklediğimiz "xor a" 4 tstate tutsa da 256 kere tekrar eden kod sayesinde kendisini fazlasıyla affettiriyor. Ancak burada tekrar eden bir kod olmasa, sadece bir kerelik bir işlem olsa "xor a" ile birlikte kullandığımızda 1 tstate zararda olacaktık.
Evet, optimizasyonlarımızı yaptık. Daha önce ölçtüğümden aklımda kaldığı kadarıyla CPU kullanımını %90'lardan %70'lere kadar gerilettik. Ama hala noktaların bazıları gözükmüyor. Demek ki daha radikal değişikliklere ihtiyacımız var.
-
On ikinci versiyonda öncelikle kesme rutini işine el atıyoruz. @Ref'in telefonda bahsettiği konuyu araştırıyor ve ZX Spectrum 48k'nın kernel'inde yer alan bir bloğa özel bir çözüm buluyoruz. 128k modelleriyle de uyumlu olması açısından kendi tablomuzu üreterek kesme rutini ile ilgili çözümümüzü üretiyoruz.
im2Jump = $fdfd
...
; set irq
ld hl, im2Jump
ld (hl), 0xc3
inc l
ld (hl), low irq
inc l
ld (hl), high irq
ld a, high im2Table
ld i, a
im 2
ei
...
irq:
ei
ret
...
ALIGN $100
im2Table:
BLOCK 257, low im2Jump
Artık kernel tarama yakalattıktan sonra ek zaman harcamıyor, bizim kendi yarattığımız "irq" labelına düşüyor kod. Bu kısmı aşağıdaki sitede çok güzel açıklamışlar. Ben de bu paylaştığım linkten öğrendim.
http://www.breakintoprogram.co.uk/computers/zx-spectrum/interrupts (http://www.breakintoprogram.co.uk/computers/zx-spectrum/interrupts)
Normalde "irq" içerisinde tüm değişkenleri girişte kaydedip, çıkışta eski değerlerine çevirmek gerekiyor. Ancak irq içinde hiç bir işlem yapmayıp, dolayısıyla hiç bir değer bozmadığım için şimdilik bu şekilde bırakmayı uygun gördüm.
Bu noktada derleyicinin low / high byte alma yazım biçimini de öğrenmiş oluyorum. Örneğin daha önce;
ld h, orBit >> 8
olarak kullandığım kısmı
ld h, high orBit
olarak değiştirmişim.
BLOCK 257, low im2Jump
bu şekilde de kullanımlarım var. Yani 6502 ASM'de genellike < ve > sembolleriyle ifade ettiğimiz 16 bitlik bir sayının alt ve üst 8 bitini al ifadesi SjASMPlus'da sembol yerine "low", "high" ön ekleri ile karşılığını bulmuş.
-
On üç o kadar da uğursuz gelmemiş, sondan bir önceki versiyonumuz. Sonunda z80'de tablodan değer okumayı öğreniyoruz ve her şeyi olması gereken şekilde tabloluyoruz, bit shifting falan da kalmıyor. Z80'de kilit noktalardan biri tabloları kullanım sırasına göre sıralamakmış. Tabii bu 256 byte kaplayan ve $100'lük bloklara düzgün yerleştirilmiş tablolar için geçerli. Bu nedenle daha önce 16 bit olarak kullandığım tabloları da low ve high byte tabloları olarak ikiye bölüyorum. Kritik noktalar şuraları.
; --- put pixel
; get X position from sine table
ld a, d
add a, b
ld l, a
ld h, high sinX
ld l, (hl)
; get or bit pattern for given x in character position
inc h ; orBit is right after sinX
ld c, (hl)
; find char column
inc h ; charPosition is right after orBit
ld e, (hl)
; find plot address
ld l, b
inc h ; sinYLo is right after charPosition
ld a, (hl)
or e
inc h ; sinYHi is right after sinYLo
ld h, (hl)
ld l, a
; set pixel using OR
ld a, (hl)
or c
ld (hl), a
; --- end of put pixel
Bu rutin "put pixel" olarak isimlendirilmiş durumda ancak aslında sadece nokta koymuyor, aynı zamanda X ve Y'de iki sinüs tablosundan da okuma yapıyor. Burada önceki versiyonlara kıyasla bize büyük avantaj sağlayan şey şu oluyor. Bir tablodan bir değer okuyoruz. O değeri okurken doğrudan "L" registerına alıyoruz. HL ile okuma yapacağımız için low byte doğrudan hazır hale geliyor. High byte'ı ise bir önceki tablodan bir fazla değerde olduğu için "inc h" dememiz yetiyor ki bu sadece 4 tstate harcayan bir opcode. Commodore 64'de tabloların hafızada ne sırada durduklarının bir önemi yokken yapısı gereği Z80'de önem teşkil ediyor.
Bir diğer önemli optimizasyonumuz ise önceden boşa çıkarmış olduğumuz d registerını artık "de" registerıyla ilgili kullanımımız da iptal olduğu için frame sayacına çeviriyoruz. b'de de nokta sayacı bulunuyor.
; d register is the frame counter
ld d, 0
...
; get X position from sine table
ld a, d
add a, b
ld l, a
ld h, high sinX
ld l, (hl)
...
Bu sayede bu kod bloğunda hem her nokta için bir ileriden okuma yapmayı sağlarken hem de d'nin değerinin eklenmesiyle frame'den frame'e de ilerleyen biçimde okuma yapılması sağlanıyor. Önceki gibi ek sayaçlar, işlemlerden kurtulup, sadece registerlarla işimizi çözmüş oluyoruz.
En çok dikkat edilmesi gereken şey tabloların sıralı olması. İki tablonun yerini değiştirdiğiniz gibi kod kullanımı aynı kaldığı sürece program düzgün çalışmayacaktır. Tablolar iki yönde de sıralanabilirler. "inc h" yerine "dec h" kullanarak önceki tabloya geçiş yapma seçeneğimiz de var.
-
On dördüncü ve şu an için son versiyonumuza geldik. Bu versiyonda sadece biraz kod toparlama var.
; definitions
debug = 1
stackSize = 512
im2Jump = $fdfd
Kodumuz artık böyle tanımlarla başlıyor, efektin ne kadar CPU harcadığını görmek için eklediğimiz çerçeve renkleri "debug" parametresi ile açılıp kapatılabiliyor.
; set border color for debugging
IF debug == 1
ld a, 1
out ($fe), a
ENDIF
...
; set border color for debugging
IF debug == 1
ld a, 4
out ($fe), a
ENDIF
Ekte "first.zip" dosyasında tüm proje klasörünü ziplenmiş biçimde bulabilirsiniz.
...ve filmin ilk sahnesine ulaştık. Şu anda sahip olduğumuz efekt güzel bile gözükmeyen, yeterince dinamik olmayan sinüs hareketi yapan 256 noktadan ibaret. Ama şu aşamadan sonra ağırlıklı olarak keşfetmekten ziyade kodlamakla uğraşacağım bir aşamaya geldiğimi düşünüyorum. Bu nedenle sizinle bu gelişim sürecini paylaşmak istedim. Yeni başlayanlara biraz katalizör olabilir diye umuyorum. Zamanla bu rutini daha da optimize edeceğim ve farklı efektlerin de altyapısını oluşturacak elbette ki. Yani bu yazıların en başında belirttiğim gibi, henüz filmin ortasındayız, sonunda değil. Lütfen herhangi bir yorum yapmak ya da soru sormaktan çekinmeyin. ZX Spectrum dünyasına yeni adım atmış biri olarak da teknik olsun olmasın her türlü tavsiyeye son derece açığım. Bir ZX Spectrum +2 ve sd card reader edinmeyle ilgili tavsiyeler de buna dahil elbette ki. ;)
-
@Skate 6510'dan sonra Z80'de de ustalığınını konuşturmussun. Üstelik bu kadar kısa bir sürede olmasına rağmen. Z80 artık gençlerin yetenekli ellerinde.
Eline sağlık, ayrıca çok güzel bir tutorial olmuş.
-
@Skate 6510'dan sonra Z80'de de ustalığınını konuşturmussun. Üstelik bu kadar kısa bir sürede olmasına rağmen. Z80 artık gençlerin yetenekli ellerinde.
Eline sağlık, ayrıca çok güzel bir tutorial olmuş.
Teşekkür ederim İsmail. Hele ki o gençlerden biri olarak beni kastediyorsan moralim yerine geldi sabah sabah. :)
@matahari bana dün bir optimizasyon önermişti. Yazıyı bitirene kadar kafayı odaklayamadım. Yazı bittikten sonra önerdiği noktada değil ama başka bir yerde aynı tavsiye yerini buldu, biraz daha tstate traşladık. Değişen kısım.
; find char column
inc h ; charPosition is right after orBit
ld a, (hl)
; find plot address
ld l, b
inc h ; sinYLo is right after charPosition
or (hl)
inc h ; sinYHi is right after sinYLo
ld h, (hl)
ld l, a
Kodu parça parça optimize ettiğim için şimdi bütününe bakarak çok daha güzel şeyler bulunabilir.
-
@Skate zor olanı başardı; kendisini aştı. Yıllardır kimsenin eline su dökemediği C64 scene ortamının verdiği rehaveti bir kenara bıraktı, hiç bilmediği sulara yelken açarak sıfırdan Z80 öğrenmeye başladı. Bunu gizliden gizliye değil, Sokrates misali "Bildiğim tek şey, Z80 hakkında hiçbir şey bilmediğimdir" şeklinde açıkça ifade ederek, camiadan gelebilecek tüm fikir, eleştiri ve ukalâlıklara göğüs gerecek yüreklilikle yaptı. Kod geliştirme ortamı için gereken araç-gereç seçiminden tutun da eserlerini sergileyebilmek için domain name almaya kadar her detayı kafasında planlamış olsa gerek, hem yazdığı kodları forumda paylaştı hem de bu süreç içerisinde edindiği olumlu/olumsuz tüm tecrübeleri kaleme alıp yazılı literatüre katkıda bulundu. Sonuçta Skate, ilk ZX Spectrum projesinde yapması gerekenden çok daha fazlasını üreterek/paylaşarak kendisine yakışanı yaptı; risk aldı ve zor yolu seçti. Kendisini hem çok takdir ediyor, hem de tüm kalbimle kutluyorum!
"O kod böyle olmalıydı", "Bu kod şöyle optimize edilmeliydi" benzeri sözler sarf etmek haddim değil. Sonuçta, karşımda yılların scene tecrübesine sahip sıkı bir yazılımcı var. Su yolunu bulur. Olur da şaşar ise, sorularını sorabileceği bir oyun geliştiricinin her zaman yanında olduğunu kendisi zaten gayet iyi biliyor.
Skate’in azim ve başarılarının diğer yazılımcılara örnek olmasını umar, yıllarca scene ortamının tozunu attıran tüm kodlama ustalarının vakti geldiğinde "bir sonraki zorlu kulvar" olan profesyonel oyun geliştirme işine sıçrayacak cesareti göstermesini içtenlikle dilerim.
-
@Skate zor olanı başardı; kendisini aştı. Yıllardır kimsenin eline su dökemediği C64 scene ortamının verdiği rehaveti bir kenara bıraktı, hiç bilmediği sulara yelken açarak sıfırdan Z80 öğrenmeye başladı. Bunu gizliden gizliye değil, Sokrates misali "Bildiğim tek şey, Z80 hakkında hiçbir şey bilmediğimdir" şeklinde açıkça ifade ederek, camiadan gelebilecek tüm fikir, eleştiri ve ukalâlıklara göğüs gerecek yüreklilikle yaptı. Kod geliştirme ortamı için gereken araç-gereç seçiminden tutun da eserlerini sergileyebilmek için domain name almaya kadar her detayı kafasında planlamış olsa gerek, hem yazdığı kodları forumda paylaştı hem de bu süreç içerisinde edindiği olumlu/olumsuz tüm tecrübeleri kaleme alıp yazılı literatüre katkıda bulundu. Sonuçta Skate, ilk ZX Spectrum projesinde yapması gerekenden çok daha fazlasını üreterek/paylaşarak kendisine yakışanı yaptı; risk aldı ve zor yolu seçti. Kendisini hem çok takdir ediyor, hem de tüm kalbimle kutluyorum!
"O kod böyle olmalıydı", "Bu kod şöyle optimize edilmeliydi" benzeri sözler sarf etmek haddim değil. Sonuçta, karşımda yılların scene tecrübesine sahip sıkı bir yazılımcı var. Su yolunu bulur. Olur da şaşar ise, sorularını sorabileceği bir oyun geliştiricinin her zaman yanında olduğunu kendisi zaten gayet iyi biliyor.
Skate’in azim ve başarılarının diğer yazılımcılara örnek olmasını umar, yıllarca scene ortamının tozunu attıran tüm kodlama ustalarının vakti geldiğinde "bir sonraki zorlu kulvar" olan profesyonel oyun geliştirme işine sıçrayacak cesareti göstermesini içtenlikle dilerim.
Güzel ve nazik sözlerin için tekrar tekrar teşekkür ediyorum @matahari.
-
@Skate tüm hızıyla 6502 denizinden çıkarak kumsala vardı, şimdi ekstra registerleri gelişiyor ve z80'de gezerleri uzaktan izliyor. Yaptıklarını taklit ederek aralarına karışıyor :) Ama bu hızla giderse yakında liderleri olabilir :D Forumda bir z80 rüzgarı esiyor ki bundan en çok ben memnunum herhalde.
IM2 hakkında bir detay eklemek istiyorum, ZX Spectrumda "floating bus" denilen bir olay var. Şimdi tam ezberimde yok ama özetle Z80, spectruma takılı aygıtlardan alınan datayı bus'a koyuyor ve orda kalıyor. Buna ULA da dahil. Eğer bir aygıt takılı değilse ve o anda ula ram'den grafik belleğini okuyorsa, okuduğu bu byte'ı bus'a koyuyor.
şimdi burada şöyle bir sorun olabiliyor, IM2 vektörünün bir byte'ı bus'tan geldiği için, eğer interrupt öncesinde z80 port okumaya çalışmışsa, IM2 vektörün 0-255 arasında bozuluyor. Bu sebepten geleneksel olarak IM2'yi kurarken 256 byte'lık bir jump table yapılması tercih ediliyor. Bu davranışı hades'in başlığında örnek bir kodla paylaşmıştım:
https://retrojen.org/pano/index.php?topic=539.msg4328;topicseen#msg4328
Bazı emülatörler bu davranışı da desteklemediği için yazdığın ve sağlam sandığın kod, gerçek makinede çalışmayabilir. Bu sebepten "ne oluyor burda" diye takılıp kalma diye hatırlatma olarak yazmak istedim.
https://sinclair.wiki.zxnet.co.uk/wiki/Floating_bus
Bu özellik 48K'larda çok tutarlı çalışıyor ve floating bus, raster interrupt yerine kullanıldığı oluyor. Ama 128k için aynı şeyi söyleyemiyorlar, pek araştırmadım, bu bilgiler kulaktan dolma, hatalı olabilir.
-
@Skate, bu kadar kısa sürede benden çok daha ileri gitmişsin. Benim Z80 tecrübem ortaokulda opkod tablosu ezberlediğim vaziyette kaldı (21 00 40 11 00 c0 01 00 40 ed b0) :)
Ama bu beni gaza getirdi, acaba CPC için bir cross-platform development ortamı oluşturabilir miyim diye düşünmeye başladım.
-
IM2 hakkında bir detay eklemek istiyorum, ZX Spectrumda "floating bus" denilen bir olay var. Şimdi tam ezberimde yok ama özetle Z80, spectruma takılı aygıtlardan alınan datayı bus'a koyuyor ve orda kalıyor. Buna ULA da dahil. Eğer bir aygıt takılı değilse ve o anda ula ram'den grafik belleğini okuyorsa, okuduğu bu byte'ı bus'a koyuyor.
şimdi burada şöyle bir sorun olabiliyor, IM2 vektörünün bir byte'ı bus'tan geldiği için, eğer interrupt öncesinde z80 port okumaya çalışmışsa, IM2 vektörün 0-255 arasında bozuluyor. Bu sebepten geleneksel olarak IM2'yi kurarken 256 byte'lık bir jump table yapılması tercih ediliyor. Bu davranışı hades'in başlığında örnek bir kodla paylaşmıştım:
https://retrojen.org/pano/index.php?topic=539.msg4328;topicseen#msg4328
Bazı emülatörler bu davranışı da desteklemediği için yazdığın ve sağlam sandığın kod, gerçek makinede çalışmayabilir. Bu sebepten "ne oluyor burda" diye takılıp kalma diye hatırlatma olarak yazmak istedim.
https://sinclair.wiki.zxnet.co.uk/wiki/Floating_bus
Bu özellik 48K'larda çok tutarlı çalışıyor ve floating bus, raster interrupt yerine kullanıldığı oluyor. Ama 128k için aynı şeyi söyleyemiyorlar, pek araştırmadım, bu bilgiler kulaktan dolma, hatalı olabilir.
Elbette ki detaylarını çok iyi bilemiyorum, yani 128k modellerinde bu konu ile özel bir sorun varsa tabii ki sıkıntı olur. Ancak floating bus benim yabancı olduğum bir konsept değil. Commodore 64'de de benzer mimaride çok fazla trick kullanırız. Ninja'nın FLI üzerine 6 sprite basmada kullandığı teknik, lft'nin safe VSP'yi kodlarken kullandığı teknik hep bu floating bus olayına benzer. Yani rastgele hafızanın bir yerine dallanma, dallanabileceği yerlere aynı kodun kopyalarını yerleştirme gibi şeylere alışığım. Bu bağlamda benim anladığım şey 48k ile 128k arasında bu tarz bir fark olmasından ziyade 48k'nın kernel'ı içinde peş peşe 257 byte boyunca $ff yer alması. Bu sayede kendi kodun içinde tablo yaratmadan kernel'deki tabloyu kullanarak işi çözebiliyorsun. 128k modellerinde ise o 257 bytelık tabloyu kendi koduna eklemen gerekiyor (ki nitekim benim kodumda mevcut). 256 değil 257 byte olma nedeni de pointer rastgele 256 bytelık alanda herhangi bir yere işaret edebiliyor ancak byte değil word okuyor, bu yüzden 256 bytelık alanın son byteına denk gelirse 1 ekstra byte daha okuması gerekiyor.
Tahminimce bu konuda bir sorun çıkmayacaktır. Benzer olarak nitelediğim örnekleri incelemek isterseniz linkleri paylaşıyorum.
The Ninja-Method (https://codebase64.org/doku.php?id=base:nmis_and_distributed_jitter-correction_routines)
lft's Safe VSP (https://www.linusakesson.net/scene/safevsp/index.php)
Emülatörler elbette ki crash edebilir ama benim beklentim gerçek cihazda sorun çıkmaması yönünde.
Bu arada tüm güzel sözlerin, desteğin ve Z80'e olan ilgimdeki payın için tekrar teşekkürler @Ref.
-
@Skate, bu kadar kısa sürede benden çok daha ileri gitmişsin. Benim Z80 tecrübem ortaokulda opkod tablosu ezberlediğim vaziyette kaldı (21 00 40 11 00 c0 01 00 40 ed b0) :)
Ama bu beni gaza getirdi, acaba CPC için bir cross-platform development ortamı oluşturabilir miyim diye düşünmeye başladım.
Tekrar teşekkürler Sedat. CPC'de ben Speccy'e benzer bir ortam kurmamıştım ama yine cross development yapıyordum. İstediğim text editörüyle kodu geliştiriyordum ancak en son WinAPE içinden derliyordum. Kaynak kodu düzenlemek için doğrudan WinAPE'in içinde gelen Assembler'ı kullandığım da oluyordu. Ekte ekran görüntüsünü paylaşıyorum.
Şu anda Speccy için kullandığım geliştirme ortamını birebir CPC için de kullanabilirim elbette ki. Ama @Fero zamanında "CPC'de racon bu" demişti, ben de çok sorgulamamıştım. Derleniyor mu derleniyor sonuçta.
Amaan, büyük projelere girişelim de derdimiz development ortamımızın konforsuzluğu kalsın. :)
-
Kaynak kodu düzenlemek için doğrudan WinAPE'in içinde gelen Assembler'ı kullandığım da oluyordu.
Evet, ben Run SSG Run introsunu partide o düzenekle yazmıştım. Hatta bir indirection file (https://github.com/ssg/run-ssg-run/blob/master/introwrap.asm (https://github.com/ssg/run-ssg-run/blob/master/introwrap.asm)) ile dosyaları da açmana gerek kalmıyor. Ama VS Code içinde emülatör ekranı, source debugging ihtimalleri vs kulağa güzel geliyor. Yoksa haklısın, esas olan projedir :)
-
Şimdi bir yarım saat vakit buldum. Az daha optimize ettim plot rutinini, son halini paylaşmış olayım, ekte 15. ve 16. versiyonlar. 16 en son şu an için. Asıl başarısız olan denememi paylaşmak istedim.
Plot için bitleri OR'luyoruz ama aslında opcode olarak SET mevcut. C64'de bir karşılığı yok, o yüzden çok kafa yormamıştım bu opcode'a. Baktım, hangi biti set edeceğinde parametre almıyor, kendisi bir çok farklı opcode varyasyonu olarak mevcut. İhtiyacım olan "SET n, (hl)" kullanımı.
SET 0, hl => $c6
SET 1, hl => $ce
SET 2, hl => $d6
SET 3, hl => $de
SET 4, hl => $e6
SET 5, hl => $ee
SET 6, hl => $f6
SET 7, hl => $fe
değerlerine denk geliyor. Yani hangi biti set edeceğimizi okuduğumuz şu orBit tablosunu
db %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010, %00000001
bitler yerine SET opcode karışıklarına çevirsek
db $fe, $f6, $ee, $e6, $de, $d6, $ce, $c6
sonra da;
ld a, (hl)
or c
ld (hl), a
olan bölgeyi
setBit:
set 0, (hl)
ile değiştirip, OR değerini okuyan kısmı da;
; get or bit pattern for given x in character position
inc h ; orBit is right after sinX
ld c, (hl)
yerine
inc h
ld a, (hl)
ld (setBit+1), a
ile değiştirsek teorik olarak çalışması lazım. Pratikte de çalıştı zaten. Plot başına 10 tstate fazladan harcadı ama. Başarısız da olsa en azından denemiş oldum. Hala da aklımın köşesinde var.
ld (setBit+1), (hl)
gibi kullanımlar olsa belki de kar edebilirdik. hele ki SET bir sürü opcode alternatifi olacağına register parametresi alsaymış OR kullanmam tamamen cahilliğim olurmuş. Hep sabit bir biti set edecek olsak kazanç sağlıyor OR'a göre. Ama dinamik olduğu durumda şimdilik hala OR doğru tercih gibi duruyor.
-
Bugün erken uyandım (6:15 civarlarında). Aklıma bir fikir geldi, aslında Y ekseni için uyguladığım bir şeyi X ekseni için kullandığım tablolara da uygulama fikri, bir tabloyu elimine edecek bir çözüm. Denedim, çalıştı. Kod çok kısalınca dedim artık loop'a gerek yok. unrolled olarak da plot edebiliriz. Tabii ki bu değişiklikler başka optimizasyonların da önünü açabilir. Bilgisayar başına oturalı 40 dakika olmuş durumda. Ama şu anki versiyonu paylaşıyorum. 17. versiyon bir tablonun uçtuğu, 18. versiyon artık çizim kısmının da unroll edildiği versiyonlar.
Önce bazı istatistikleri paylaşayım.
Sinüse bağlı 256 nokta çizdirme: 24828 t-state (%35.01 CPU kullanımı)
Çizilen alanın temizlenmesi: 4356 t-state (%6.14 CPU kullanımı)
Toplamda çizim+silme: 29184 t-state (%41.15 CPU kullanımı)
Çizim rutinine daha sonra silebilmek için eklemiş olduğumuz "push hl" de dahil. Tek bir plot'un basılmasını ele alacak olursak plot rutinimiz tam olarak 83 t-state ve 16 byte tutuyor. Plot sonrası bir "push" bir de "inc" yapıyoruz, onlar da 15 t-state ve 2 byte tutuyorlar. Yani bir noktadan diğer noktanın çizimine toplamda 98 t-state ve 18 byte harcanıyor.
Online versiyonu da güncelledim, CPU kullanımını temsil eden yeşil bölge epey küçüldü, son durumu oradan da görebilirsiniz. Eke de gerekli dosyaları ekledim.
http://speccy.akaydin.com/
Değişen kısımlar;
256 adet plotu basan rutin
i = 0
WHILE i < $100
; --- put pixel
IF i != 0
inc b
ENDIF
; get orBit of given position
ld a, d
add a, b
ld l, a
ld h, e ; e has the high byte of orBit table
ld c, (hl)
; find char column
inc h ; charPosition is right after orBit
ld a, (hl)
; find plot address
ld l, b
inc h ; posYLo is right after charPosition
or (hl) ; add character X position
inc h ; posYHi is right after posYLo
ld h, (hl)
ld l, a
; set pixel using OR
ld a, (hl)
or c
ld (hl), a
; --- end of put pixel
; push pixel memory address to stack
push hl
i = i + 1
ENDW
Üretilen tabloların kodları düzenlendi, tekrar eden kodlar fonksiyonlara bölündü.
ALIGN $100
orBit:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' db ' .. 2 ^ (7 - bitoper(getX(i), 0x07, AND)))
end
ENDLUA
; charPosition must be right after orBit
ALIGN $100
charPosition:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' db ' .. math.floor(getX(i) / 8))
end
ENDLUA
; posYLo must be right after charPosition
ALIGN $100
posYLo:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' db ' .. (getY(i) % 256))
end
ENDLUA
; posYHi must be right after posYLo
ALIGN $100
posYHi:
LUA ALLPASS
for i = 0, 255, 1 do
_pc(' db ' .. math.floor(getY(i) / 256))
end
ENDLUA
...
function getX(i)
return math.floor(128 + 127.5 * math.sin(i / 64 * math.pi) * math.cos(i / 32 * math.pi))
end
function getY(i)
y = math.floor(64 + 81.5 * math.sin(i / 128 * math.pi) * math.sin(i / 64 * math.pi))
v = 0x4800 + bitoper(y / 64, 0xff, AND) * 0x800 + (y % 8) * 0x100 + bitoper((bitoper(y, 0x3f, AND) / 8), 0xff, AND) * 0x20
return v
end
Son durum budur.
-
Loopu açınca, daha çok register boşa çıkınca, X/Y faz farkı için de biraz farklı bir yöntem kullanınca kod iyiden iyiye optimize edildi. 19, 20 ve 21. versiyonlar ekte.
Sinüse bağlı 256 nokta çizdirme: 21752 t-state (%30.67 CPU kullanımı)
Çizilen alanın temizlenmesi: 4356 t-state (%6.14 CPU kullanımı)
Toplamda çizim+silme: 26108 t-state (%36.82 CPU kullanımı)
21. versiyon
Edit: Bu versiyonda sonunda @matahari'nin önerdiği yöntemi önerdiği noktada kullanabildim.
Ana döngü öncesi verilen değerler:
; b register is plot loop counter
; e register is frame and inner loop counter
; c register holds charPosition table high byte
; d register holds orBit table high byte
; reset counters before main loop
ld b, e
ld e, e
ld c, high charPosition
ld d, high orBit
256 noktayı döngü kullanmadan basan plot rutini:
i = 0
WHILE i < $100
; --- put pixel
IF i != 0
inc b
inc e
ENDIF
; find char column
ld l, e
ld h, c ; c has the high byte of charPosition table
ld a, (hl)
; find plot address
ld l, b
inc h ; posYLo is right after charPosition
or (hl) ; add character X position
inc h ; posYHi is right after posYLo
ld h, (hl)
ld l, a
; set pixel using OR
ld a, (de)
or (hl)
ld (hl), a
; --- end of put pixel
; push pixel memory address to stack
push hl
i = i + 1
ENDW
-
hah tam denk gelmiş hatta biraz payın bile kalmış.
şöyle, zxspectrum 48k'da her raster 224 ts (128k'da 228ts)
üst border kalınlığı 64 raster. Sonra plush logosunun bulunduğu alan yine 64 raster.
yani noktalar 128 raster sonra basılıyor (128k'da 127).
48k için hesaplayalım, tv elektron tabancasının 129'uncu rasterin başına gelmesi
224*128=28.672ts tutuyor.
sende 26108ts kullanmışsın, ula'nın ilk dot'u çizmesine daha 2564 ts var.
(28672-26108=2564ts)
Beam'de tam pozisyonuna gelmiş. şimdi attribute byte'larını doldurarak arkada dönen rasterbars falan multicolor efekt bile yapabilirsin :D
Yazdığın kodu incelediğimde resmen başka bir uzmanlık alanı olduğunu görüyorum :) Burda asm yok, asm parçalarını bir araya getiren bir lua script var. sanki işlemleri fonksiyonlara ayırmışsın ve onları kondüsyonel çağırıyorsun gibi. Ben bodoslama yazıyorum, arada bir iki unroll makrosu ancak :D Bu yazı dizisi tam benlik oldu.
Bu arada emülatörde profiling sonucu 48k zx spectrum için senin kodun, halt, out, jp'ler vs. dahil tuttuğu süre 27605ts (ula contention vs de arada bozuyor hesabı).
-
Ana döngü öncesi verilen değerlerde ld e,e komutu var. Sanki fazladan gibi.
Bu arada forumda like butonu olması şart.
-
Ana döngü öncesi verilen değerlerde ld e,e komutu var. Sanki fazladan gibi.
Bu arada forumda like butonu olması şart.
haha evet, hem de skate kendi yazmış, assembler öyle derledi sandım bir an :D olsun, çıkarılır, 4ts cepte.
eğer optimizasyonun dibine vuracaksak, en sondaki push hl ile HALT peşinden gelen pop hl de lazım değil, arada register değişmiyor :D ahahah ordan da 21 gelir :D
-
Ana döngü öncesi verilen değerlerde ld e,e komutu var. Sanki fazladan gibi.
Bu arada forumda like butonu olması şart.
Haklısın İsmail, uyardığın için sağol.
O ld e,e'nin hikayesi şöyle. Daha önce ld d,e idi o. E'nin değeri ld de, $5800 satırından sıfır geldiği için absolute value yerine e'yi kullanmıştım. Yani amaç sıfıra eşitlemek. Ancak plot rutininin sonundaki ld a, (de)'yi kullanabilmek için d ile başka bir register'ı swap ettim. Dolayısıyla kod içinde tüm d geçenleri e yap gibi bir işlem yapınca o da ld e, e şekline gelmiş, gözümden kaçmış. Ama performansı etkilemiyor. Çünkü main loop içinde değil. Oradan kazanç sağlayacak olsam öncesindeki ldir içeren kopyalama rutinlerini adam etmem gerekir zaten. O tür rutinler en fazla demo partının başlangıcında etkiler. Bir gün trackmo yapmaya karar verirsem büyük ihtimalle logoya bir açılış efekti yaparım, o kısım o şekilde değişir zaten. :)
-
Ana döngü öncesi verilen değerlerde ld e,e komutu var. Sanki fazladan gibi.
Bu arada forumda like butonu olması şart.
haha evet, hem de skate kendi yazmış, assembler öyle derledi sandım bir an :D olsun, çıkarılır, 4ts cepte.
eğer optimizasyonun dibine vuracaksak, en sondaki push hl ile HALT peşinden gelen pop hl de lazım değil, arada register değişmiyor :D ahahah ordan da 21 gelir :D
Çok zekice. Optimizasyon optimizasyondur, sadece sonunucuyu etkiliyor deyip es geçmem asla. 22. versiyon ekte. :)
-
@matahari bana dün bir optimizasyon önermişti. Yazıyı bitirene kadar kafayı odaklayamadım. Yazı bittikten sonra önerdiği noktada değil ama başka bir yerde aynı tavsiye yerini buldu.
Bu versiyonda sonunda @matahari'nin önerdiği yöntemi önerdiği noktada kullanabildim.
[ You are not allowed to view attachments ]
-
Son ulaştığımız noktada sadece plot rutinini olan kısım 6 tanesi 4 tstate, 6 tanesi 7 tstate tutan toplam 12 opcode, 12 byte ve 66 t-state'den oluşuyor.
; find char column
ld l, e
ld h, c ; c has the high byte of charPosition table
ld a, (hl)
; find plot address
ld l, b
inc h ; posYLo is right after charPosition
or (hl) ; add character X position
inc h ; posYHi is right after posYLo
ld h, (hl)
ld l, a
; set pixel using OR
ld a, (de)
or (hl)
ld (hl), a
Bu daha ileri taşınabilir mi bilmiyorum. Tabii bu rutinin aynı zamanda sinüse tablolarına bağlı olarak plot bastığını da hatırlatmam lazım. Gerçi herhangi x, y için de çok bir şey değişmeyebilir.
Şu noktadan sonra ben biraz tıkandım gibi. @Ref'in ve @hades'in önerileri gibi gözüne bir şey takılan olursa lütfen paylaşsın. Örneğin push/pop kısmını o şekilde yaptım ama belki daha iyi bir yöntem vardır. İlk versiyonumda pop etmek yerine stack alanının en tepe noktasından ileriye doğru kendim değerleri okuyup sıfırlamayı denemiştim. Rutinin sonunda da stack pointer'ı resetlemiştim. Ancak sorun şu oluyor. Hafızada peş peşe 16 bit olarak duran iki byte'ın işaret ettiği byte değerini sıfırlayıp, sonra low pointer'ı da 2 ileri almayı denediğimde "pop hl", "ld (hl), a" 17 t-state ile en hızlı çözüm gibi gelmişti. Ancak bunun undocumented opcode'u olur, şu olur bu olur. Bunu daha hızlı yapmanın yolunu bilen var mıdır?
Z80 ile ilgili araştırma yaparken Ghost'n Goblins grafik rutinlerini açıklayan bir siteye denk gelmiştim.
http://www.emix8.org/ggdisasm/
Burada ağır bir push/pop kullanımı var. O nedenle en hızlı yöntemin bu olabileceğini düşünüyorum. Yine de dediğim gibi sizlere bir sormak istedim.
-
Yazdığın kodu incelediğimde resmen başka bir uzmanlık alanı olduğunu görüyorum :) Burda asm yok, asm parçalarını bir araya getiren bir lua script var. sanki işlemleri fonksiyonlara ayırmışsın ve onları kondüsyonel çağırıyorsun gibi. Ben bodoslama yazıyorum, arada bir iki unroll makrosu ancak :D Bu yazı dizisi tam benlik oldu.
Aslında düşündüğün kadar işin içinde LUA falan yok @Ref, yalnızca tablolar var. Bunu ben ilk rasterbar çekmek istediğimde telefonda Mert Hekimci'den öğrenmiştim. Bana "iki tane tablon olacak. biri renkler, biri zamanlamalar için" demişti. Yaş 10 civarı. Tablo dendiğinde aklımda sadece duvara astıklarımız vardı. :) Kısa sürede ne yapmam gerektiğini anlamıştım. Ama elimde günümüzün fancy cross development teknolojileri olmadığı için tabloları ya Basic'den hesaplatıyordum, daha da komiği bir çoğunu elle tek tek oluşturuyordum. Hatta ekrana sinüs benzeri hareketlerle giren logolar, serbest düşme hareketi yapan objelerin tabloları matematiksel değildi, elle deneme yanılmayla oluşturuyordum. :) Yani şimdi sen LUA scripting görünce "ya iş bambaşka bir yerde dönüyormuş" diyorsun. Aslında 30 sene öncesinde de ben birebir böyle kod yazıyordum. Tek farkı şimdi daha bir hesaplı kitaplı tablolar. :)
Bu kod yapısı aslında benim de hoşuma gitmiyor, her şeyi tablolamak, matematiği opcodelardan çıkarmak, üç tane bit shifting'i bile tablodan bakarak yapmak. Ancak bunun için sizinle tüm bu macerayı paylaştım. Gördüğün gibi hala elim tablo dışı yöntemlere gidiyor. Ama günün sonunda rutinlerin ulaştığı nokta bu oluyor.
3d efektlere sarma nedenlerimden biri de buydu açıkçası. 3d efektlerde yine arkaplan tablo deryası tabii ki. Ama çok ciddi de opcodelar üzerinden dönen işlemler oluyor, olmak zorunda. Matris hesapları, dot product alma şu bu, hepsini tek tek kodlamak zorunda kalıyorsun. İş nokta basma rutinine geldi mi demoscene'de elle tutulur bir şey yapmak istiyorsan tablo manyağı olmak zorundasın ne yazık ki. Yoksa bir başkası senden daha iyisini yapıyor, sen de kalkıp "ama ben senin kadar tablo kullanmadım" diyemiyorsun, adam "kullansaydın" diyor. :)
-
Aklımda bir kaç optimizasyon var ama plot rutini için değil. im2 setup ve ldir kısmıyla ilgili. Bir kaç bayt kazandırır sadece. Önce ldir kısmını yazayım.
Logonun $0800 byte uzunluğunda olduğunu kabul ediyorum. İlk ldir komutu çalışınca DE'nin değeri $4800 oluyor. İkinci ldir için ld de,$5800 yerine ld d,$58 yazarak 1 bayt tasarruf ediyoruz. ldir sonrası BC=0 olduğu için ld b,e komutuna da gerek kalmıyor. Hatta renk tablosu 256 byte olduğu için ikinci ld bc yerine inc b yazılabilir. 4 byte kazandık :)
im2 setup kısmı için
; set irq
ld hl, im2Jump
ld (hl), 0xc3
inc l
ld (hl), low irq
inc l
ld (hl), high irq
ld a, high im2Table
ld i, a
im 2
ei
yerine
org $8000
defs 257,$81 ; im2table
di
ld a,$80
ld i,a
im 2
ei
yazarak 8 byte ile im2setup yapabiliyoruz. $8000-$8101 arasını $81 ile dolduruyoruz. Dolayısıyla irq rutini $8181'de yer alacak. Programın çalışma adresi $8101 gözükse de $81 ADD A,C olup programı $8000'den başlatmamıza engel değil. EI'den sonra $8181'e kadar 121 baytlık boş yer var ve logo/renk transfer/diğer ayarlar için kullanılabilir. $8183'ten itibaren tamamen serbest olur. irq set rutini ile hafızanın $FDFD'den sonrası boş kalır.
;irq rutin
org $8181
ei
ret
;--------------
-
Teşekkürler @hades. Özellikle logo transfer rutini bir fade in efektiyle değişecek gibi gözüküyor, o nedenle henüz o kısma el atmadım. irq ile ilgili kısımda da haklısın, gereksiz zaman harcıyoruz orada. Ama hepsi initialization rutini kapsamında olduğu için oralara pek aldırış etmedim. Hafıza dizilimi konusunda da henüz çok ZX Spectrum tecrübem olmadığı için en düz mantık olarak yerleştirdim her şeyi. En son ASM kısmını loader ile yükletmeyip tek parça haline getirecekken hepsini gözden geçireceğim. Hatta sadece şu anki hali için belki 16k modellerinde de çalışacak hale getirmeyi deneyebilirim. Tabii conditional olarak 16k'ya özel derlenecek biçimde, ona özel hafıza yerleşimi yaparım. Diğer platformlarda aynı kod ve tablolar farklı adreslere yerleşir.
Bir de aklımda ekranın tamamına daha fazla nokta basmak var. O zaman logo tamamen çöpe gidebilir, daha ince şerit halinde logo, logolar ya da başka grafikler söz konusu olabilir, @Ref'in dediği gibi rasterbarlar falan da olabilir. Daha o kısmı tasarlamaya başlamadım kafamda. Büyük ihtimalle son ulaşacağım rutinde plot rutini bu versiyondan daha fazla zaman yiyebilir daha dinamik bir efekt olması açısından. O yüzden önce bunun suyunu sıkmaya çalışıyorum ki ekstra özelliklere yer açılsın.
-
@matahari'nin bitmek tükenmek bilmeyen desteği sayesinde kod anlamsız derecede kısa bir hal aldı. :)
Commodore 64'de zamanlama çok kritik olan durumlarda, örneğin yan borderların açılması ve aynı anda her satırda başka hardware registerlarının da set edilmesi gerekmesi gibi hallerde değerleri tablodan okumak yerine doğrudan absolute value (lda #$15 sta $d027 gibi) ile yazıp, daha sonra zaman kritik olmayan alandan tablodan okuyup parametreleri yerlerine yerleştirdiğimiz olur. Ancak bu normalde toplamda daha çok cycle harcamamıza neden olur, yani tek seferde tablodan okusak daha iyi sonuç alırız. Bu bize zaman kazandırmaz, sadece zamanlama yetmeyen kısımda daha çok iş yapabilmemizi sağlar.
Z80'e geldiğimizde "ld a, (hl)" ve "ld a, 1" kıyaslandığında aynı zamanı harcıyorlar. Tabii ki hl değerini set etmek de zaman harcayan bir kısım. Ama tek başına tablodan değer okumak yerine değeri kodun içine yazmak bize zaman kazandırmıyor. Matahari de tam olarak bana bunu teklif etti özelden. Artık loop'u açmış olduğumuz için tablodan değer okumak yerine değerleri doğrudan kodun içine yazmak. Bu tabloyu da çöpe atan bir faktör. Ama teklif ettiği yer ilk denediğimde t-state anlamında bir kazanç sağlamadı. Yani tablo çöpe gitti, dolayısıyla hafıza kullanımı düştü, ama hala rutin aynı zamanı harcıyordu. Ama bana bu teklifi yaptığı mesajında "bonus" olarak bazı registerları boşa çıkarabileceğimizi ve bunu daha fazla optimize etmek için de kullanabileceğimizi yazmıştı ve öyle de oldu.
Kısacası benim Commodore 64'de farklı koşullarda kullandığım ancak burada işe yarayacağı hiç aklıma gelmeyen bir optimizasyon işe yaramış oldu. Ekte bu versiyonları bulabilirsiniz. Şu anki kod şu şekilde.
n = 0
WHILE n < $100
; --- put pixel
IF n != 0
inc c
ENDIF
; find plot address
ld a, (bc)
; or with char column
LUA ALLPASS
_pc(' or ' .. math.floor(getX(_c("n")) / 8))
ENDLUA
; get posYHi
ld l, c
ld d, (hl)
ld e, a
ld a, (de)
; set pixel using OR
LUA ALLPASS
_pc(' or ' .. 2 ^ (7 - bitoper(getX(_c("n")), 0x07, AND)))
ENDLUA
ld (de), a
; --- end of put pixel
; push pixel memory address to stack
IF n != $ff
push de
ENDIF
n = n + 1
ENDW
Çizim bölgesi 16643 t-state ve %23.47 CPU kullanımına düştü (sadece opcodeların harcadıklarını sayıyorum).
"Artık bu kadarı da fazla" diyenlere hak veririm. Ama sonuç olarak hala aynı efekti görüyoruz ekranda, hafıza kullanımı da artacağı yerde düşüyor. Bu nedenle çok da kabul edilemez bir yöntem değil kanımca.
Peki optimizasyon olasılıkları bitti mi? Tabii ki hayır. Aklımda son bir versiyon daha var iyice işi karman çorman edecek ama CPU kullanımını düşürecek. Vakit bulduğum gibi deneyeceğim.
-
Sevgili @Skate
Bu başlık altında paylaştığın detaylı bilgilere, hak ettikleri derinlik ve dikkatle eğilmeye henüz vaktim olmadı. Sadece tarihçe ve geliştirme araçları bölümlerini ful okudum ve diğer mesajları hızlı-okudum. O yüzden, bu çabanın hak ettiği özende bir cevap veremeyeceğimi bilerek ve bu konuda affına sığınarak yine de bir şeyler söylemek istiyorum.
Bu yazılar benim için birkaç yönden inanılmaz değerli. Sevgili Matahari’nin yazılarında da söylediğim gibi, burada senin de bu yazıların sayesinde bir başka dünya çapında, ‘öncü’ Türk mühendisinin, öğrenme süreçlerinden oldukça da detaylı bir kesit görmüş oluyoruz. Burada, senin metodunu, problem çözmeye yaklaşımını, sorun çıktığında ne tepki verdiğini, nasıl aştığını, sabrını, deneyselliğini, desen tanıma yaklaşımını veya bildiklerinden nasıl güç alıp bilmediklerine saldırdığını adım adım görme şansı buluyoruz. Bu gerçekten inanılmaz zengin bir kaynak.
Ayrıca ben kendi adıma bencilce şu faydayı da görüyorum. Özellikle 6502 uzmanı birisinin Z80’e geçerken yaşadığı ‘mental model adaptasyonunu’ o kadar güzel dokümente etmişsin ki. Bir gün ben de Z80’de birşeyler kodlamak istersem, şu an biliyorum ki bu sırf senin sayende, senin bu yazın olmasa olacağından on kat kolay olacak belki.
Son olarak de senin bu yazın ve kodun bana şunu düşündürdü: Bazen uzun yıllar tanıdığımız ve samimileştiğimiz arkadaşlarımızın aslında ne kadar sıradışı insanlar olduğunu unutuyoruz. Bunun neden olduğunu açıklamak zor. Ama hani Skate’in benim kakara kikiri samimi bir arkadaşım olması, Norvax’ın ortaokulda beraber makara yaptığımız biri olması falan bana bu insanların aslında gezegende ince elekle köşe bucak gezsen belki karşına bir avuç zorla çıkan, ne kadar sıradışı, ne kadar ‘nadide’ insanlar olduğu gerçeğini gözden kaçırmama sebep oluyor. Yani daha az değer vermiyorum elbette, ama ne kadar değer verdiğimi de yeterince dillendirmiyorum galiba.
Velhasıl, sevgili dostum, burada benim için senin Speccy’de 700 küsur sine dot yapman elbette sürpriz değil. Bunu birkaç günde yapabilmen de sürpriz değil. Ama işte bu ‘şaşırmama’ ile takdiri hakkıyla dillendirmeyi birbirine karıştırmamak lazım.
Şu başlıkta yazdığın ve paylaştığın hem yazın hem kod, çok üstün bir yetenek ve birikimin ve çok temiz kalpli bir iyi niyetin sonucu, çok değerli, çok nadide. Eminim ki daha bu gördüklerimiz de senin daha sonra yapacaklarının yanında hiçbirşey.
-
@nightlord, aşırı utangaç biri olmadığıma şükrettim, öyle söyleyeyim kardeşim. Benim bile yanaklarımı kızartmayı becerdin ama. :)
Özellikle şu yazdıkların konusunda o kadar hemfikiriz ki.
Son olarak de senin bu yazın ve kodun bana şunu düşündürdü: Bazen uzun yıllar tanıdığımız ve samimileştiğimiz arkadaşlarımızın aslında ne kadar sıradışı insanlar olduğunu unutuyoruz. Bunun neden olduğunu açıklamak zor. Ama hani Skate’in benim kakara kikiri samimi bir arkadaşım olması, Norvax’ın ortaokulda beraber makara yaptığımız biri olması falan bana bu insanların aslında gezegende ince elekle köşe bucak gezsen belki karşına bir avuç zorla çıkan, ne kadar sıradışı, ne kadar ‘nadide’ insanlar olduğu gerçeğini gözden kaçırmama sebep oluyor. Yani daha az değer vermiyorum elbette, ama ne kadar değer verdiğimi de yeterince dillendirmiyorum galiba.
Yıllarca hepimizin başına gelmiştir. Partilerde olsun, iş ve özel yaşantılarımızda olsun, bizim yaptıklarımıza meraklı gençler bizlere "siz nasıl başladınız, nasıl öğrendiniz programlamayı?" tarzı sorular sormuşlardır. Tabii bu benim perspektifimden, grafiker, müzisyen ve diğer uzmanlık alanları da benzer şekilde. Ben hep şu şekilde başlamışımdır.
"Ben çok şanslıydım."
İlk cümlem mutlaka bu olmuştur. Sonrasında devam etmişimdir.
"Daha ben küçükken 64'ler dergisi, Beşiktaş Bilgisayar gibi ortamlara girebilmiş, oradan çok değerli, bu işleri bilen abiler edinebilmiş, sorduğum tüm sorulara cevap alabilmiştim."
Ama işte bu sadece işin başlangıcı. Sonrasında kapalı kapılar arkasında, kendi kendime bir şeyler yapıp, sizleri hiç bir zaman tanımamış olma ihtimalimi düşündükçe yine ilk cümleme geri dönüyorum. "Ben çok şanslıydım." :)
Bu noktadan yola çıkarak bana sık sık sorulan bir diğer soruya geçeceğim. "Scene nedir?" sorusu. Bunu biraz bu başlığa özel bir tanıma dönüştüreceğim. Başka bir yere copy & paste edilince anlamını yitirecek.
- Scene, bilgisayar meraklısı gençlerin yıllar sonra karşılıklı bu tür mesajlar yazabilmek adına sosyalleşme alanıdır. Scene, "Skate, 7DX party organizasyon ekibine hiç dahil olmasaydı, 2004 yılında sabah erken saatlerde, parti öncesi herkes mekanda yerlerde uyurken birden parti mekanının kilitlli arka demir kapısına dan! dan! dan! diye hiç vurulmasaydı, Skate ve Nightlord hiç tanışmasaydı..." gibi senaryolardan bizi uzak tutan sosyal koruyucu meleğimizdir.
Yorumların için tekrar tekrar sağol, varol kardeşim.
-
@Skate Ellerine sağlık! Adını anınca hemen aklıma gelen bugüne kadar yapmış olduğun onlarca kaliteli işin yanına bu serüvenini de eklemiş oldun. Senin tecrübe seviyende ve iş yoğunluğunda birisinin tüm bu macerayı adım adım paylaşmasının kolay olmadığının ama bir o kadar da kıymetli bir jest olduğunun farkındayım. Başta kendim olmak üzere, bu maceradan faydalanan ve dahi ileride faydalanacak herkes adına teşekkür ederim.
Şimdi, ne kadar tutarlı olduğunu görmek üzere sormak istediğim bir şeyler var. Sebebini daha sonra açıklayacağım ;D
Z80 (Spectrum) ASM'ye geçiş yaptığında senin dertlerin şöyle sıralanabilir mi?
1) Bu ASM'de hangi registerlar var ve kaç bit?
2) Nasıl bir registera değer atar, o değeri hafızaya yazar ve bunları point ettirerek kullanabilirim?
3) Genel opcodelar hangileri? (bit shifting, or, and, xor(eor), jmp v.s.)
4) Loop komutları nelerdir?
5) Hangi flagler var ve hangi opcodelar ile kontrol edilebiliyorlar? (carry flag, zero flag, negative flag v.s.)
6) Makinenin genel hafıza adreslemesi, bank/segment mantığı nasıldır?
7) Özel hafıza adresleri (ekran adresleri v.s.) ve ROM adresleri nelerdir?
-
Öncelikle güzel sözlerin için içten teşekkürlerimi iletiyorum @Alcofribas. Daha önce de belirttiğim gibi Z80’e merak sarmamda çok ciddi payın var.
Tahmin ediyorum o liste daha önce birinin saydığı adımlar ve evet, çok güzel saymış, oldukça tutarlı. Ama tabii ben o listeyi bir kaç seneye yaydım. Bende o maddelerden bazıları epey uzun zaman alıyor. Örneğin “genel opcodelar hangileri?” hala sona ermiş durumda değil. Henüz çoğu opcode’un ya hakkını veremedim ya da hiç kullanmadım. O maceranın başından sonuna hep süregelen bir madde. Ancak son maddeler olan hafıza adresleri, banklar, ROM adresleri gibi şeyler bir kaç dakika dokümantasyonlara bakmaktan ibaret olan maddeler. Mecbur kalmadıkça kernel rutinlerini kullanmıyorum zaten.
Özetlemek gerekirse liste oldukça tutarlı ama ağırlık seviyeleri değişiyor. Bu arada geçmişte mesajı benim yazmış olduğumdan da kıllanmaya başladım. Hadi hayırlısı… :)
-
Özetlemek gerekirse liste oldukça tutarlı ama ağırlık seviyeleri değişiyor. Bu arada geçmişte mesajı benim yazmış olduğumdan da kıllanmaya başladım. Hadi hayırlısı… :)
Tutarlılığın için tebrik ederim. Gönül rahatlıyla kıllanabilirsin. 2005 yılından Skate mesajı getirdim sana :D
http://www.tr-demoscene.info/index.php/topic,312.msg1621.html#msg1621
-
Alem adamsın @Alcofribas. Bir de “çok salakça” deyip rezil olmak vardı. :)
-
Alem adamsın @Alcofribas. Bir de “çok salakça” deyip rezil olmak vardı. :)
Alco'nun böyle mind trickleri vardır hep. Ama merak etme, arkadaş 17 sene geçmiş üzerinden, taş olsa değişir :D rezil falan olmazdın, vay, olgunlaşmış falan derdik sorun yok :D
Bu arada geçmişte mesajı benim yazmış olduğumdan da kıllanmaya başladım. Hadi hayırlısı… :)
Hahah "Hayatımda hiç bu kadar aynı fikirde olduğum insan çıkmamıştı karşıma!" :D
-
III. Çapraz Geliştirme Araçları
...
Böylece artık kod yazmaya hazır hale gelmiş oldum. Sanırım geliştirme platformunu ayağa kaldırmam bu gönderiyi hazırlamamdan kısa sürdü. Bu nedenle kesinlikle "en mükemmel, en modern çözüm" diye düşünmeyin. Ancak bir kaç eksiği olmasıyla birlikte işimi gayet iyi görüyor. Eksiklerinden de kısaca bahsedeyim.
- ...
- SjASMPlus'ın desteklediği scripting dili LUA. İlk başta LUA'yı görünce sevinmiştim. Bir süre CoronaSDK ile kullanmıştım, sevmiştim de. Ancak sanırım burada kullanılan versiyon bayağı eski ya da eksik özelliklere sahip. Çok temel işlemleri bile yapamıyor. Kaynak kodumun sonunda şöyle bir kısım göreceksiniz.
; LUA common definitions and functions
LUA ALLPASS
OR, XOR, AND = 1, 3, 4
function bitoper(a, b, oper)
local r, m, s = 0, 2^31
repeat
s,a,b = a+b+m, a%m, b%m
r,m = r + m*oper%(s-a-b), m/2
until m < 1
return r
end
ENDLUA
Bu kodu ben yazmadım, internetten olduğu gibi bulup aldım. Bit tabanlı işlemler için bunu kullandım. AND, OR, XOR içermiyormuş LUA, yani en azından SjASMPlus ile birlikte gelen versiyonu. LUA'yı araştırdığımda yeni versiyonda bu tarz desteklerin geldiğini gördüm. Ama şimdilik hiçbirini çalıştıramadım SjASMPlus bünyesinde. Geçici olarak böyle ucube fonksiyonlar kullanıyorum. Tabii proje küçük olunca derleme sürelerini etkilemiyor ama fonksiyonu gördükçe gözüm seğirmiyor dersem yalan söylemiş olurum.
Bu anlattığım dez avantajların henüz benim bazı özellikleri çözememiş olmamdan kaynaklanabileceğini hatırlatmak isterim. Zaman zaman "peeh, becerememişler" dedikten kısa süre sonra "peeh, meğerse ben becerememişim" demişliğim vardır. Bu yüzden şimdilik bunları potansiyel dezavantajlar olarak nitelendiriyorum. Hatta ilerleyen dönemde geliştiricileri ile irtibata geçip, bana eksik gelen kısımları tamamlamaları için destek de olabilirim. Nitekim Kick Assembler'a 65c02 işlemci desteğini bu şekilde ekletmiş ve Commander X16 için daha konforlu, ekstra opcodelar için yazdığım gereksiz makroları çöpe atarak devam etmiştim. Bu geliştirici araçlarında bir iki eksikten dolayı projeden hemen vazgeçmemek gerekiyor. Ancak öksüz kalmış projelerde mecburen alternatif arayışına gitmek durumunda kalıyoruz elbette ki.
Bu kısmı hatırlarsınız. Aynen dediğim gibi oldu. Şu anda henüz yayınlanmış olmasa da eksikler projeye eklendi. Şu ana kadar gözlemlediğim en büyük eksiği buydu SjASMPlus'ın. O da çözülmüş oldu. Ekte ilgili mesajı paylaşıyorum.
@Ref'e de beni SjASMPlus'ın developerlarının da yer aldığı ortama davet ettiği için teşekkür ediyorum. Ayrıca iletişime geçmekle uğraşmamış oldum.
-
@Skate senin table'dan indexed value çekme işi için biri değişik bir yöntem önermiş: https://retrocomputing.stackexchange.com/a/5755/3986
-
@Skate senin table'dan indexed value çekme işi için biri değişik bir yöntem önermiş: https://retrocomputing.stackexchange.com/a/5755/3986
İnceledim, benim kullandığımdan bir farkı yok. Çok kısa görülme nedeni “değer HL’den geliyorsa” diye başlaması. HL’ye o değeri yüklediğin opcode’u da ekle, bir de o o tabloyu ilerletmek için L’nin değerini artırmaya çalış, uzuyor da uzuyor.
-
İnceledim, benim kullandığımdan bir farkı yok. Çok kısa görülme nedeni “değer HL’den geliyorsa” diye başlaması. HL’ye o değeri yüklediğin opcode’u da ekle, bir de o o tabloyu ilerletmek için L’nin değerini artırmaya çalış, uzuyor da uzuyor.
Haklısın, senin $100 aligned tablolardan bahsettiğini atlamışım ya da unutmuşum.
-
@Ref'e de beni SjASMPlus'ın developerlarının da yer aldığı ortama davet ettiği için teşekkür ediyorum. Ayrıca iletişime geçmekle uğraşmamış oldum.
Abi o sunucular seni kesmedi ama dediğim gibi, güzel bir komünitemiz var, kimsenin sormadığı soruları sorarak aslında ciddi gelişim ve hareket yarattın, en azından kabız bir assembler sayende az buçuk düzelme yoluna girdi.
Speccy camiasına kaliteli coder lazımmış resmen :D