Merhaba e-bergi okuyucuları,
Yazımızın ikinci bölümüyle yeniden birlikteyiz. Geçen yazıda kısaca bir atari 2600 un donanımını ve ileride kullanacağımız emülatör, editor, assembler benzeri programları öğrenmiştik. Bu sayımızda ilk önce atarinin genel özelliklerini gördükten sonra işin kodlama kısmına bir başlangıç yapacağız.Ama ilk önce televizyon nasıl çalışır ona bakalım. Tabi ki de gereksiz yere elektromıknatıslara, elektron dalgalarına bakmayacağız, bizi ilgilendiren kısmı programlamaya başlamadan önce bir atari programcısının bilmesi gereken belli başlı prensipler olacak.
Televizyonun yansıttığı görüntü hareket eden bir görüntü değildir tabii ki. Bu görüntü hareketsiz birçok görüntüden oluşuyor. Videodakiyle aynı mantık yani.Tabi görüntü değişimi çok hızlı olduğu için insan gözü bunu bir bütün olarak algılar. Ayrıca bu görüntüler de televizyon tarafından ekrana yukarıdan aşağıya satır satır yansıtılır. Bulunduğumuz satırın sırasını bilmek bizim için büyük önem arz eder. Bunun sebebi de daha önceden dediğim gibi atarinin görüntü yansıtma kısmının da bizim sorumluluğumuz olması. Bu işi yapmamızı sağlayan donanım parçası TIA’dir (1. yazıda bahsetmiştik).
Televizyona görüntü yansıtma işlemi kısaca şu şekildedir: İşlemci görüntü datasını TIA’ye gonderir.TIA sadece o onda oluşturulan görüntünün bilgisini taşır, onun dışında bir bilgi taşımaz ve işlemci her zaman TIA den bir adım önde olmalıdır ki görüntü sürekliliği korunsun.
Televizyon sistemi evrensel değildir. Televizyon sistemleri saniyede gösterdiği kare sayısı ve her karede bulunan satır sayısına göre 3 tanedir: NTSC,PAL,SECAM. Kullanılan sisteme göre satır sayısı değiştiği için yazılan program da değişecektir. NTSC ve PAL genelde tercih edilen sistemlerdir. SECAM sadece Fransa’da kullanılır. Biz NTSC üzerinden hareket edeceğiz. Ama kod üzerinde temel fark görüntüyü yansıtmada olduğu için, istediğiniz zaman kodunuzu diğer televizyon sistemlerine gore modifiye edebilirsiniz. Ayrıca atari donanımının televizyon sistemiyle uyumlu olması gerekir. Bu sebeble her televizyon sistemine uygun bir atari donanımı bulunur. Yani NTSC için geliştirilen bir atari 2600 sistemi PAL sistemine uyumlu değildir. Çalıştırmaz demiyorum ama donanım uyuşmasızlığından dolayı görüntü kalitesi düşük olur. Ayrıca bir saniyede görüntülenen kare sayısı değiştiği için oyun beklenilenden çok daha yavaş veya çok daha hızlı çalışabilir. O zaman NTCS televizyon sisteminin özelliklerini incelemeye başlayalım:
NTCS televizyon sisteminde saniyede 60 görüntü oluşturulur. Her karede 262 adet satır bulunur. Her satırın çizimi TIA için 226 döngü gerektirirken, bu sayı işlemci için 76’dır. Yani işlemcinin bir döngü yaptığı süreçte TIA 3 döngü yapar. Hadi o zaman işlemcimizin bir saniyede kaç döngü çalıştığını hesaplayalım: 60 kare * 262 satır * 76 döngü = 1194720 Hz 1.19 MHz Hatırlayacağınız üzere işlemci hızı olarak da bu sayıyı vermiştim
Derinlemesine TIA:
TIA’yı şimdiye kadar yüzeysel gördük. Biraz ayrıntıya girmekte fayda var zira bu ayrıntılar geliştiriciler tarafından bizzat kullanılacak özellikleri içeriyor. TIA işlemciden aldığı 8 bitlik datayı video geçiş devrelerine(video modulation circuits) aktararak görüntü elde edilmesini sağlar. Oluşturacağımız yazılımın 1 oyun alanını(playfield) ve 5 tane de objeyi(2 oyuncu,2 misil ve bir de top) oluşturup değiştirebilir. Tabi bu demek olmuyor ki oyunumuzda kesinlikle 2 oyuncu, 2 misil , 1 top olmak zorunda. Bu beş objeyi ve oyun alanını rahatlıkla değiştirebileceğimiz registerlarımız(kaydedici) var. Bu registerları çoğu dilde bulunan değişkenlere (variable) benzetebilirsiniz. Ama, register sınırlıdır ve donanıma gore register sayısı değişir. Register, ona yüklediğiniz bilgiyi tutar ve yeni bir bilgi yüklenene kadar değiştirmez. Atari 2600’da kullanılan registerlar 8 bitliktir. Yani 0-255 arası değerleri tutabilirler. Ayrıca kullanılan TIA’deki registerlar dışında kullandığımız registerlarımız da vardır (A,X,Y,SP,P,PC gibi). TIA ayrıca sesten de sorumludur, görüntü oluşturmadaki gibi işlemciden aldığı 8 bitlik datayı sesi oluşturacak olan devreye gönderir.
Senkronizasyon:
Kodlamaya geçmeden önce bahsetmemiz gereken bir konu da senkronizasyon.Yazılım ile görüntü arasında ve işlemci ile TIA arasında bir senkronizasyon olmalıdır.
Yatay zamanlama:
Bir satırı oluşturmak için TIA nin 228 döngü gerçirmesi gerektiğini söylemiştik. Lakin bu döngülerin hepsi ekranda görüntü oluşturmak için değildir. 68 döngü elektron ışıması sağa dayandığında bunu tekrar soldan başlatmak için geçer. Yani her satır oluşturulduğunda TIA 160 döngüde ekranı renklendirirken, 68’inde en sola dönmeye çalışır. Bu olayı HSYNC (Horizontal Synchronization) olarak ifade ediyoruz.
İşlemci senkronizasyonu:
TIA ise işlemci senkronizasyonudur. Hatırlayacağımız üzere işlemci bir satırda 228 döngü geçirirken, bu sayı TIA’de 3 de biri yani 76’di. Yani her 3 işlemci döngüsüne 1 TIA döngüsü denk gelir. Fakat verdiğiniz komutların harcadığı döngü sayısından emin olmadığınız bazı durumlarda bu denge bozulabilir. Bu durumda bir programcı olarak WSYNC (Wait for SYNC) komutunu verebilirsiniz. Bu komut işlemciyi durdurup bir sonraki satırın en soluna gönderir ve TIA’nin o anki satırını tamamlamasını bekler. WSYNC komutu güven verici olabilir ama zararları da vardır. WSYNC kullandığınız satırda görüntü kalitesi düşük olur.
Dikey zamanlama:
Dikey zamanlama, TIA’nin en son satırı da görüntüye çevrilmesini tamamladıktan sonra ilk satıra dönme çabasıdır. Bu çaba ilk 3 satırı boş bırakır. Bu 3 satırda yapılan kodlama ise 3 kere VSYNC yapmaktır. VSYNC işlemi satırı komple boş bırakır.
Genel olarak bir karede 3 satır VSYNC, 37 satır VBLANK, 192 satır TV görüntüsü ve 30 satır da overscan olur. Overscan, televizyonun görüntüyü uçlarında kesmesidir. Eski televizyonlarda görüntü bir çerçeve içinde gösterilir. Yani televizyonun bütün ekranı görüntü vermez. 16 inçlik ekran aslında 15 inç görüntü vermesi gibi. Bu olay CRT(Cathode Ray Tube) kullanıldığı dönemlerde büyük bir soundu. CRT teknolojisi günümüzde kullanılmasa bile overscan halen televizyonlar için bir sorundur.
Senkronizasyonun ve özellikle registerların soyut kaldığının farkındayım. Bu konular kodlamaya geçildiğinde somut hale gelecektir. O yüzden hadi kodlamaya geçelim:
Kodlama:
Kodlamayı Assembly 6502 ile yapacağız demiştik. İşlemci modelimiz hatırlayacağınız üzere 6502’ydi. 6502 hakkında ilginç bir bilgi. 6502 işlemcisi apple II bilgisayarının da işlemcisidir. Dolayısıyla öğreneceğimiz Assembly ile Wozniak’ın yaptığı gibi apple II için kodlama yapabilirsiniz(Wozniak, apple bilgisayarını ilk tasarlayan insan. Jobs la birlikte atari için kodlama da yapmıştır).
Assembly 6502’yi atari 2600 için kullanırken değişken(variable) oluşturamıyoruz. Atari sistemler dışında Assembly 6502’de değişken oluşturma EQU (equation) komutuyla yapılabilir. Ayrıca malesef bölme, çarpma işlemleri bulunmaz.
Bu arada Assembly 6502 diğer Assembly dillerinden tamamen farklı bir konsept değildir. Diğer Assembly dilleri ile benzerlik gösterdiği noktaların üzerinde fazla durmaya gerek yok. Bu yüzden ebergide yayınlanan assemblyyazısını okumanızı öneririm.
Assembly 6502:
Bu işe basit bir emulator ile başlayalım. “6502asm.com”dan basit bir emulator kullanabiliriz. Emulator zaman kazandıracaktır. Ayrıca javascript uzerinden çalışan emulatorun kaynak kodunu kaynaklar kısmında bulabilirsiniz. Emulatorden bahsetmek gerekirse, atari ortamının birebir aynısı değildir ama birçok kavramı anlamamıza olanak sağlayacaktır.
İşe register kullanımı, store ve load komutlarıyla başlayalım. A,X ve Y registerlarımızı adres belirtirken, değişken olarak ya da bir konuma değer atarken kullanacağız. A registerı accumulator olarak da adlandırılır. Bu registerlar 8 bitlik olduğu için aldıkları değer en fazla 255’tir. Bu değeri atarken binary, decimal, hexadecimal olarak atayabiliriz. Kullanacağımız sayının assembler tarafından doğru anlaşılması için başlarına prefix koyarız. Bu prefixler binary için %, hexadecimal için $’dır. Decimal için ise bulunmamaktadır.Yani boş bıraktığımızda decimal oluyor.
Örnek olarak onluk tabanda 47 sayısı: binary → %101111 decimal → 47 hexadecimal → $2F olarak belirtilir.
Ayrıca # işareti de bir değerin sayı olduğunu belirtmek için kullanılır. Eğer bu prefix yoksa değer, hafızada bir değeri belirtir. Bu çeşitlilk hoş karşılanır bir şey ama genellikle işlem rahatlığı sebebiyle hexadecimal kullanacağız.
Load komutları yani LDA, LDX, ve LDY komutları bir registera bir değer yüklemek için kullanılır. Load komutları 2 bytelıktır.
LDA #$03 ; A registerına 03 yükledik.
LDX #$1B ; X registerına $1B yani 27 yükledik.
LDY #15 ; Y registerına 15 yükledik.
Store(ST_) komutları (STA,STX,STY) ise registerdaki değerleri hafızada bir yere kaydetmemizi sağlayan 3 bytelık komutlardır. Store komutlarından sonra hafızada bir yer belirtmemiz gerektiği için prefixde # işaretini kullanmamamız gerekir.
Örneğin:
LDA #$15 ; A registerına 15 yükledik.
STA $02 ; hafızanın 02 bolgesine A’nın değerini yani $15 i kaydettik.
Emulatorümüzden bahsedeyim biraz da. Emulator bize hafıza olarak $0600 luk yer sağlıyor. Yani store değerimiz $0-$5FF arasında bir değer olabilir. Ayrıca ilk 200 değerimiz hafızada bir değer tutmak ya da array kullanımı için kullanılır. Yani ekrana basacağımız değerler $0200-$05ff arası değerlerdir. Ekrana basmak derken neyi kastediyorum? Aslında zevkli bir şey, hadi onu da görelim.
Hexadecimal tabanında kullandığımız 8 bitlik değerleri renkleri kodlamada da kullanıyoruz.Yani eğer bu sayıları ekrana ait olan bir hafıza alanında kullanacaksak değerler bir renge karşılık gelir. Emulatoru açıp şunları deneyelim o zaman:
lda #$01 ; beyazın renk kodu
sta $020
yazıp assemble tuşuna ardından da run tuşuna basalım. Ekranın sol üst köşesinde beyaz bir bit’in görünüyor olması lazım
Bu beyaz bit’in yerine kırmızı bir bit koyalım. Bu sefer
lda #$02 ; kırmızının renk kodu
sta $200 ; $02 yani mavi kodunu 200’de kaydettik.
Peki bir yanına da mavi bir eklemek istersek üstteki koda şunları ekleyelim:
lda #$06 ; mavi renk kodu
sta $200 ; bu kez 201’e yani 200’ün bir yanına kaydettik.
Renk kodları $00 ile $0f arasındadır. Siz de ekranın 205. birimine pembe bir bit yerleştirmeye çalışın. Ayrıca renk kodlarının ilk basamakları 0 olmak zorunda değildir. İsterseniz ilk basamağı değiştirerek rengin açıklığını değiştirebilirsiniz.
Emulator ekranımız her satırda $20 bit içeriyor. Yani eğer bir bit’in bir alt satırına ulaşmak istiyorsak o değere $20 eklememiz lazım.
Transfer komutlarına bakalım biraz da. Transfer komutları registerlarda, bir registerdan diğerine aktarımı sağlar. 3 indeks registerımız olduğu için dolayısıyla 6 tane transfer komutumuz vardır. Arraylerde de transfer komutu vardır, fakat bunu arrayleri incelerken öğreneriz. Transfer komutlarımız şimdilik TAX,TAY,TXA,TXY,TYA ve TYX. Parametre almazlar, dolayısıyla tek bytelık işlemlerdir.
Örnek olarak:
LDA #$16 ; A registerına $16 değerini yükledik.
TAX ; A registerındaki datayı X’e kopyalamış olduk. Yani, hem A hem de X’de
$16 değeri var
Ya da:
LDY #$3E ; Y registerına $3E değeri yüklendi
TYX ; Y registerındaki değer X’e kopyalandı.
Kodlardan da anlaşılacağı üzere transfer kodları ilk registerdaki datayı ikinci registera kopyalar. Increment(arttırma) komutarı da x ve y registerlarını bir arttırmak için kullanılır.
ldx #$21 ; x’in değeri $21
inx ; x’in yeni değeri $22 oldu.
ldy #$6a ; y değeri 6a
iny ; y’nin yeni değeri 6b
Bir arttırma komutu accumulator yani a registerı için bulunmaz. Onun yerine daha işlevli olan adc’yi kullanırız.
lda #$c8 ; a değeri $c8
adc #$11 ; a değeri $11 kadar arttı. yeni a değerimiz $d9 oldu.
adc komutuyla kullandığımız parametre bir sayı olmak zorunda değildir. Ayrıca hafızadan bir yer de olabilir.
ldx #$12
stx $02 ; hafızanın 02\. yerinde artık $12 değeri var.
lda #$25 ;
adc $02 ; hafızanın 02\. yerindeki sayıyla $25 i topluyoruz. yani a registerının yeni
değeri $37.
Toplama işleminde taşma ve komşudan alma sorunları var bildiğimiz üzere. bunları aşmak için de donanımın bize yaptığı iyilik, bu olayları birer flag ile bildirmektir. flagleri ve sp,pc registerlarını sonraya bırakıyorum.
Toplama olur da çıkarma olmaz mı. Toplamadakine benzer bir durum çıkarmada da söz konusu. Yani, x ve y için birer azaltma komutları varken a için hafızanın belirli bir yerinden veya bir sayıyla azaltma komutları var. Bu komutlar sırayla: dex,dey ve sbc.
ldx #$09
dex ; x registerının tuttuğu yeni değer 08
ldy #$56
dey ; y’nin tuttuğu değer artık $55
lda #$33 ;
sbc #$06 ; a’nın yeni değeri $2c
Aynı şekilde dec ile kullandığımız parametre bir hafıza konumu da olabilir.
lda #$04
sta $25 ; hafızanın 25\. konumunda 04 değeri var.
lda #$52 ; a nın değeri $52 oldu.
sbc $25 ;
Ayıca inc, dec komutları da hafızanın belirli bir yerindeki bir değeri bir azaltıp bir arttırmaya yarar.
lda #$09;
sta $12 ; hafızanın 12\. yerinin değeri 09.
dec $12 ; hafızanın 12\. değerinin yeni değeri 08.
Bu yazı için anlatacaklarım bu kadar. Bir sonraki yazıda assembly 6502 ile devam ederiz. Görüşmek üzere.
Kaynakça: