ASSEMBLER PRO
Retro bilgisayarHerkese merhaba. Bu yazıda assembler uygulamalarında çok zorluk çıkaran bir konuyu ele alacağım. O da yazılan programın farklı konfigürasyonlarda çalışmaması, kısaca UYUMSUZLUK. Uyumsuzluk, birden fazla sebepten oluşabilir. Şimdi bu sebeplere sırayla değinmeye çalışacağım.
1. Sabit Adresler Üzerinde Çalışmayın. Birçoğumuz Assembler'a başlarken grafik başlangıç adresi olarak $60000, $70000 gibi sabit adresleri alırız, sonra da bu adresten itibaren belirli bir hafıza bloğunu siler ve üstünde grafik işlemleri yaparız. İyidir, hoştur ve o anda Amiga'nızda çalışmaktadır. Fakat bu aslında iyi bir şey değildir çünkü gözümüze kestirdiğimiz bu sabit adres her zaman boş olmayabilir. Arkada çalışan başka herhangi bir programın çalışma alanını ya da daha kötüsü farklı bir konfigürasyonda, sisteme ait bellek alanını değiştiriyor olabiliriz. Sabit adresleme metoduyla yazdığımız programın iki şansı vardır: Ya çalışacak ve çıktığı zaman sistemi göçertecek ya da şansa boş adresi taban aldığı için düzgün çalışıp işi bittiğinde normal bir şekilde sisteme dönecektir. Fakat ikincisi çoğunlukla olmaz ya da Murphy kurallarına uyarak, üç-dört ay adam gibi çalıştıktan sonra ara sıra guru vermeye başlar. Hele hele programınızın başka programlarla birlikte kardeş kardeş multitask etmesini düşünüyorsanız, sabit adreslemeyi hepten unutmanız gerekecek, çünkü multitask bir ortamda sabit tek bir adresi değiştirmek bile öldürücü etki yapabilir. En iyi ihtimalle 2.0 ve üstü bir sistemde çalışıyorsunuzdur ve sistem problemli alanı diğer programlardan izole etmeyi başarabilir. En kötü ihtimalle, gene 2.0 ve üstü sistemle çalışıyorsunuzdur, ama sistemin kendi yapılarını alt üst etmişsinizdir. Dolayısıyla, doğrudan sevgili gurumuzla karşılaşırsınız. O zaman yapılması gereken şey, kullanacağınız kadar hafızayı sistemden istemektir. Bu gerçekten çok hoş bir şeydir çünkü sistem zaten istediğiniz kadar hafızaya sahipse bunu size seve seve verecek ve başka programların sizin hafızanıza bulaşmasını -tabii ki sistemle uyumlu çalışanların- önleyecektir. Peki nasıl hafıza istenir? Bu iş için ROM'da bulunan AllocMem() fonksiyonunu kullanacağız. Öncelikle istediğimiz hafızayı byte cinsinden D0 registerine yazmalıyız. Sonra da ne tür bir hafıza istediğimizi belirtmeliyiz. Bunun için bir takım değerler vermem gerekiyor. Şimdi sıralayacağım özellikler içinden hangileri istediğiniz hafıza tipine karşılık geliyorsa, o özelliğe ait rakamı bir kenara yazın ve sonra kenara yazdığınız tüm rakamları toplayıp D1 adresine yazın. Ve ardından ofseti -198 olan bu fonksiyona dallanın. (Nasıl yapacağını bilemeyenler için biraz aşağıda kısa bir uygulama örneği var.) MEMF_CHIP: İstediğiniz hafızanın Chip RAM'den seçilmesini sağlar. Custom Chip'ler sadece Chip RAM'e erişebilirler. Copperlist, görüntü alanı, sprite bilgileri, müzik sample'ları vs. Hepsi Chip RAM'de bulunmalıdır. Bu özelliğe ait değer $02'dir. MEMF_FAST: Chip RAM dışında kalan, genişletilebilir hafıza tipi. Fast RAM'e sadece CPU eriştiği için buradaki programlar Chip RAM'de yer alanlara oranla daha hızlı çalışacaktır.(Bunun için bu tip hafızaya FAST MEM adı verilir.) Bu özellikte hafızaya ait değer $04'dür. MEMF_PUBLIC: Elde ettiğiniz hafızanın başka bir yere oynatılmamasını sağlar. Şu anki sistemler zaten ayrılan hafızaları bir yerden diğerine taşımıyorlar, fakat siz yine de ileriki bir sistemde hafızanızın yerinden oynatılmasını istemiyorsanız bu bellek tipini isteyin. Değeri $01. MEMF_CLEAR: Size verilen hafıza bloğunun önce temizlenip, sonra verilmesini sağlar. Eğer hafızayı grafik alanı için kullanmak istiyorsanız bu faydalı olur, çünkü bu hafıza parçasında zırva şeyler varsa ekrana yansıyacaktır. Bu fonksiyon, sizi bir temizleme rutininden kurtarır. Değeri $10000.
ASSEMBLER PRO Programlarınız Tüm Amiga Modellerinde çalışsın.
NOT: Eğer Chip ve Fast seçeneklerini de açmışsanız, sistem öncelikle Fast memory arar. Bulamadığı zaman ise, Chip memory'yi araştırır. Aşağıda kısa bir örnek program bulabilirsiniz. Amaç, $5000 byte'lik Chip RAM alanını sistemden istemek ve sistem bu alanı bize veremezse insan gibi programı terk etmek. İyice inceleyin. Sizin, bu tür bir programı kendi rutinlerinize ekleyebilecek kadar bilginizin olduğunu varsayıyorum.
lea 4.w,a6b
move.l #$5000,dü
move.l
jsr -198(a6)
cmp.l #0,d0
beg.s END
; hafıza miktarı
7 $100004$02
; hafızamızı istiyoruz
; eğer istediğimiz türden
; bir hafıza yoksa
; END label'lı adrese git.
ok: ... rutinimiz buradan başlar..
DO'da bize ait hafızanın
; başlangıç adresi var
END: rts Artık tüm işlemlerinizi sabit bir adrese göre yapabilirsiniz, o adres de size D0'ın içinde verildi. D0 ile size verilen bu değeri bir taban değer olarak alıp, hafıza ile ilgili tüm işlemlerinizi bu adresten itibaren kullanın. Bazılarınızın aklına şu soru gelebilir. Önceden $70000, $60000 gibi adresleri copperlist'imize yazmak kolaydı. Çünkü değerlerin ne olduğunu biliyorduk ve bu adresi WORD'lere ayırıp gerekli adreslere yazıyorduk. Örnek:
copperlist: dec.w $00e0,$0006,$00e2,$0000
Ama şimdi sistemden alacağımız hafıza her seferinde farklı olacak. Her seferinde değişen bir değeri Copperlist'e nasıl yazabilirim? Aslında soru yanıtıyla birlikte geliyor. Değeri elde ettikten sonra, onu Copperlist'e yazacak bir rutin yazmalısınız. Mesela, hafızayı Allocate ettikten sonra onu BASE adında bir longword'e attınız diyelim. Aynı zamanda 2 bitplane'iniz var ve her bir bitplane $2800 bayt yer kaplıyor. Siz de grafik hafızası olarak allocate ettiğiniz hafızadan itibaren $2800*2 bayt'ı grafik hafızası olarak kullanmak istiyorsunuz. Yapmanız gereken şey, şöyle bir şey..
setbplpointers:
move.l base,di
move.w di,
swap di
move.w di,
swap di
add.l #$2800,d1i
move.w
swap dl
move.w
rts
copperlist:
dc.w $00e0,$0000,$00e2, $0000
dc.w $00e4,$0000, $00e6, $0000
dc.w $0100,$2200
$fffe,$fffe Aynı şekilde, grafik işlemlerinizde kullanacağınız taban adresi de BASE adlı adresten alıp, gerekli ofseti üzerine ekleyerek kullanmalısınız. Göründüğünüz kadar da zor değil.. Umarım bunu anlamışsınızdır. Sırada ikinci uyumsuzluğumuz var..
2. Kendini Değiştiren Kod Tekniğini Kullanmayın. Aslında şimdi anlatacağımı bazılarınız yapmıyor olabilirler. Yine de yazılan kodun cache hafızalı çiplerde (68020, 68030, 68040) çalışmamasının en büyük sebeplerinden biri bu. Peki nedir bu ??
Mesela bir sinüs tablonuz var ve bu sinüs tablosundan devamlı ardışık değerler alıyorsunuz, sonra tablonun başına dönüp tekrar aynı değerleri alıyorsunuz ve bu böyle gidiyor.. Benim ilk bulduğum çözüm şu olmuştu: doit: lea sine,a0 cmp.b (a0) bne.s nowrap move.l #sine,doit+2
; problemli yer burası.
; sakatlığı yaratan ; satır, no:11
bra.s doit
...
add.l #1,doit42 ; sakatlığı yaratan
! ; satır, no:2
rts
sine: dc.b
dc.b $£Ef
Dikkat ettiyseniz, rutin kendi üzerinde değişiklikler yapıyor (move.l ve add.l #1,doit+2). Peki ama bunun ne gibi bir dezavantajı olabilir ki?? Bu teknik, 68000'de çalışır. Fakat yanıltıcıdır çünkü cache hafızalı işlemcilerde büyük bir sorun yaratır. Cache hafızanın bir tanımını yapmanın tam zamanı. Çoğunuz biliyorsunuz, işlemci işlediği komutları hafızadan teker teker alır. Fakat hafızaya erişmek işlemci için zaman alan bir olaydır, çünkü hafıza birden fazla chip tarafından paylaşılmaktadır (Chip RAM'den bahsediyorum), fakat bu paylaşımın bir sıra içerisinde olması gerekir. Bir sıra içinde olması demek de İŞLEMCİNİN BEKLEMESİ anlamına gelir. Günümüzde işlemcilerde aranan en önemli özellik hız olduğuna göre, hafızaya erişimin daha az zamanda gerçekleşmesini sağlamak gerekecektir. Bunu da nasıl sağlarız diye düşünen mühendisler, işlemciye sadece kendisinin kullanacağı küçük bir hafıza ekleyince daha hızlı işlemcilerimiz ortaya çıkmış. İşlemci, belli bir kurala göre işlettiği komutları kendi içinde yer alan belleğe alır. Daha sonra, program içinde kendi belleğine attığı komutlara erişmesi gerekirse belleğe erişmekle hiç uğraşmaz, doğrudan doğruya kendi belleğinde yer alan komutu işleme sokar. Bu sayede Fast RAM'de yer alan bir komuttan bile hızlı çalışır komutunuz. Fakat, yapısı gereği, Cache'li sistemler en çok döngülerde etkin olabiliyorlar. Örneğin, 68020'de ya bir ekran silme rutini, tamamıyla cache'e sığdığı zaman blitter'dan hızlı çalışabilir. İşte cache hafızalı chiplerin hikayesi de bu. Şimdi bizi esas ilgilendiren ufak bir ayrıntıyı açığa kavuşturalım. Komutun kendisi üzerinde bir işlem yaptığınızda, 68000 komutları bilgisayarın hafızasından aldığı için normal çalışır. Buna karşılık, 68020 gibi cache'li bir CPU, bu değişikliği bellekte yapacak fakat cache'e attığı komutu aynen koruyacaktır. Sizin değiştirdiğinizi sandığınız komuta tekrar erişilince, bellekteki değiştirilmiş komut yerine cache'in içindeki çağrılacaktır. Bunun sonucunda programınız ya hatalı çalışacak ya da hepten göçecektir. Öyleyse neymiş? Kendini değiştiren kod yazılmazmış! Tamam.. Size bir de deminki rutinin cache hafızalı işlemciler için yazılmış
bir versiyonunu vereyim.
doit: lea sine,a0
move.l ofset,d0
move.b (a0,d0.1),d1i
cmp.b #ŞEf,di
bne.s nowrap
moveg.l #0,ofset
bra.s doit
nowrap: ...
adda.l #1,ofset
rts
sine: de.b
dc.b $£f
even
ofset: O
3. Zamanlamalarınızı İşlemci Döngüleri İle Yapmayın. Bir işleme girişmeden önce biraz beklemek için kullanılan en eski (ve en uyumsuz) yöntem, bir döngü kurup, 0'dan yüksek bir sayıya kadar saymaktır. Bu işlem, CPU hızıyla doğru orantılı şekilde bir zamanlama yaratır. Biliyorsunuz, bilmem kaç satırdır değişen işlemcilerden bahsediyorum. Meretler her seferinde daha da hızlanıyorlar. Peki ama durum böyleyken siz zavallı 68000 işlemcisi için yazdığınız bekletme döngüsünün bir 68040'da aynı hızda çalışmasını nasıl beklersiniz?? Hemen umutsuzluğa kapılmayın. Bu zamanlama işini sizin yerinize halledebilen bir zamanlayıcı zaten var. CIA çipi, birçok saate sahiptir. İstediğiniz hızda darbe üretir ve tabii ki HER AMIGA'da aynı sonucu verir. Bu iş için size yine bir sistem fonksiyonu önereceğim. DOS LIBRARY'de bulunan fonksiyonu işinizi görür. Delay(), tek bir değer ister sizden. Bu değer, beklemek istediğiniz sürenin 1/50'sidir. Aşağıda, bu komutu kullanmak için programa eklemeniz gereken rutini vereceğim. Bu rutin, Dos library'yi açar ve fonksiyonu yardımıyla üç saniye bekler. Rutinimiz bekleme süresini değerleştirdikten sonra DosBase'in 198 bayt gerisindeki Delay rutinini çağırmaktan başka bir işlem yapmıyor: 6
Bekler: move.l 4.w,a6
lea DosName,al
jsr -408(a6) ; Dos Library'yi aç.
emp.l #0,d0
beg.sgs END
move.l d0,a6 DosBase'i a6'ya al.
move.l #150,d1 ; Bekleme süresi 3 saniye
jisr -198(a6) ; Delay() |
buraya rütinlerinizi yerleştirin
move.l a6,al
move.l 4.w,a6
jsr -4A14(a6) ; Dos Library'yi kapat.
END: rts Bekletmeleri sağlıklı yapmanın bir yolu da raster taramalarını kontrol ederek bekletme döngüleri yazmaktır. Raster taraması için kısa bir tanım yapmak istiyorum. Bilgisayarınızın görüntü çipi, görüntü birimine saniyede 50 video görüntüsü yollar. Bu 50 karenin her birine biz 1 frame diyoruz. Aynı zamanda, her frame, ekrana bir sıra içerisinde gönderilir. Yani çıkacak görüntü yukarıdan aşağıya doğru satır satır taranarak Biz de o anda kaçıncı satırın tarandığını $dff006 adresinden okuruz. Bir frame içerisinde bu adres sadece bir kez $40 ve sadece bir kez $ff olacaktır. Bu ikisinin kontrolü sağlandığında 1 frame taranmış demektir (yani saniyenin 1/50'si). Böylesi bir kontrolü 50 * istenen saniye kadar yaparsanız tüm Amiga'larda eşit zamanda bekleyen bir rutin
sunuz. Olayın tam açıklık kazanabilmesi için örnek bir rutin...
move.l #(50*10)-1,â6 ; 10 saniye
; beklenecek
testl: cmp.b #$40,Şd£f006 ; £rame'in yukarı
; kısımı tarandı mı?
bne.s testl t
test2: cmp.b #$E£E£,$d£f006 ; £rame'in a
aşağı
; kısmı tarandı
bne.s test2
dbra d6,testi
rts NOT: Aslında $40 ve $ff, video taramasının uç noktalarıdır fakat bu değerler her frame içinde sadece 1 kez set edilirler. Bu iki değer arasındaki herhangi iki değer de aynı işi görür. Yalnız bu uygulamada dikkat etmeniz gereken bir nokta var: Multitask ortamlarda çalışmasını düşündüğünüz rutinlerinizde bu yöntemi yapmayın. Hatta ve hatta işlemcinin zamanını yiyecek herhangi bir döngü bile açmayın. Bu gibi durumlarda saydırma işini sistemin yapması gerekecek.
Eğer hayal gücünüzü zorlarsanız, başka uyumsuzluklar da yaratabilirsiniz. Fakat temel olan uyumsuzluk sebepleri bunlar. Lütfen artık uyumlu bir şeyler çıkaralım. Demolarınızın Amiga 1200 ve
4000'lerde çalışmasını istiyorsanız ve Assembler kullanmakta kararlıysanız bunlara dikkat edin. Yine de bu yazı yeterli olmazsa yardıma ihtiyacınız varsa hiç çekinmeden probleminizi bilginizle uzun uzun anlatın ve aşağıdaki adrese postalayın.
P.K.41 81311 Bahariye - İSTANBUL — sa ve e bir