Makine Diline Merhaba (2)
.
6510 Ana İşlem Ünitesinin Komutları ve Adresleme Tipleri
Ana işlem ünitemizin komutları için 256 ihtimal olduğunu belirtmiştik. Fakat bu 256 ihtimalden 151'i komut olarak tanımlanmıştır. Gene de bu rakam gözünüzde büyümesin. Çünkü bunların çoğu bir grup altına giriyor ve adresleme tiplerinde farklılaşıyorlar. 6510'un birbirlerinden tümüyle farklı olarak 56 komutu var. Bizim genel kanımız ise bu komut setini kolayca öğrenebileceğiniz yönünde. Şimdi size bu komutları daha genel kategorilerde toplayıp anlatacağız. Bu arada sırası geldikçe adresleme tiplerini örneklerle açıklamaya çalışacağız.
1. Yükleme Komutları:
Bu komutlar ana işlem ünitesinin registerlerine (yazmaçlarına) verileri almaya veya yüklemeye yarıyor. Ana işlem ünitesinin üç adet register'i olduğu için bunlara ait yükleme komutları da kalıplaşmış durumda. Bunlar:
Akümülatör'e yükle
LDA - LDX — X-Register'ine yükle LDY — Y-Register'ine yükle
Böylece üç komutu öğrenmiş olduk. Kaldı elliüç! Bu komutları örneklerle açıklarken aynı zamanda adresleme tiplerini de kullanacağız. Adresleme tipi, ana işlem ünitesinin söz konusu verinin adresine nasıl ulaştığını belirtir. Biz örnekleri verirken daha anlaşılır olması açısından o işi görecek BASIC komutunu da vereceğiz. Böylece kavramların daha iyi yerleşeceği kanısındayız. İlk inceleyeceğimiz adresleme tipi direkt adresleme (immediate addressing mode):
LDA y£ SOA
10 olarak)
Bu adresleme tipinin karakteristik belirleyicisi, yüklenecek değerin hemen önünden gelen # işaretidir. Direkt adresleme akümülatör'ün doğrudan $0A (onaltılık olarak $0A) değeriyle yükleneceğini gösterir. Bu işe karşılık gelen BASIC komutu ise:
0
Bu adresleme tipi herhangi bir register'e sabit bir değeri yüklemek için kullanılır. Bu adresleme tipi X- ve Y- Register'leri için de vardır. Bunlara örnek olarak:
LDX
LDY $AB
verebiliriz. Burada X-Register'i $7F (ondalık olarak 127) değeriyle, Y-Register'i $AB (ondalık olarak 171) değeriyle yüklenir. Bu adresleme tipinde aynı zamanda yüklenmesi gereken değer de makine dili programın bir parçası haline gelir. Hafızada komut ve yüklenmesi gereken değer peşpeşe Byte'lar olarak durur. Makine dili programı bu adreste yürürken bu Byte'ı alır ve bunu bir komut olarak yorumlar. Diyelim ki komut olarak $A9'a (ondalık olarak 169) rastladı; ana işlem ünitesinin içindeki karmaşık devreleri hemen bunun bir LDA #$.. komutu olduğunu algılar. Aynı karmaşık devreler bu sefer de bir sonraki Byte'ın komut değil de veri olacağına karar verirler. Bir sonraki Byte okunur ve değeri akümülatör'e yüklenir. Burada gördüğünüz gibi bu işlem toplam olarak iki Byte sürüyor (komutun kendisi bir Byte; yüklenecek veri diğer Byte olmak üzere). Ana işlem ünitesi bir komutun başlangıcından, bu komutun bitimine, bir sonraki komutun başına kadar program sayacının değerini otomatik olarak iki artırır. İkinci olarak absolut adresleme tipini inceleyelim. Bu adresleme tipi register'e sabit bir değer değil de, bir hafıza biriminin (adreslenebilir bir Byte'ın) içeriğini yüklemek için kullanılır. Örnek verecek olursak:
LDA $COAF
Burada akümülatör'e $C0AF adresinin içeriği yüklenir. Şimdi ufak bir sorun çıkıyor. Bu işlem hafızada nasıl yerleşmekte?.. $C0AF adresi 16-bitlik bir sayıdır ve bu özelliğinden dolayı bir Byte'a sığmaz. Hemen herkesin aklına gelen çözüm pratikte de gerçekleşiyor ve 16-bitlik bu sayı 8-bitlik iki sayıya bölünüp hafızaya öyle yerleştiriliyor. Bu arada 65XX serisi ana işlem ünitelerinde geçerli olan bir kuralı hatırlatalım: Önce düşük basamaklı Byte, peşinden yüksek basamaklı Byte geliyor. Yani 16-bitlik bir sayının önce sağ tarafı, sonra sol tarafı yazılıyor. Şimdi bu işlem hafızada nasıl görünür, bir inceleyelim. Önce LDA komutunun bu adresleme tipine karşılık gelen kod'u $AD (173), daha sonra $AF (175) ve son olarak $C0 (192) gelir. Aynı işlemi yapan satırı yazmak istersek:
Az
$COAF
Tabii ki aynı komutun X- ve Y-Register'leri için karşılıkları var. Bunlar için ayrı bir örnek vermeyeceğiz. Çünkü akümülatör'le yapılan işlemden farklı bir şey yok. Diyelim ki ana işlem ünitesi bu işlemi hafızada yürütüyor. Önce komutu tanır, arkasından adresin düşük değerli kısmını (Lo-Byte da denir) okur, onun arkasından adresin yüksek değerli kısmı (Hi-Byte da denir) gelir. Şimdi ana işlem ünitesi tüm bir satırı okudu. Ne yapacağını biliyor, fakat henüz hiçbir şey yapmamış durumda. Burada ana işlem ünitesi kısa bir süre programın kendisinden ayrılır ve adreslemesi gereken bölgeyi adresleyip oranın içeriğini alır ve akümülatör'e yükler. Görüldüğü gibi bu işlem tipi üç Byte sürmekte. Yani bu işlem bittiğinde program sayacının değeri üç artacaktır. Şu ana kadar iki ve üç Byte süren komutları gördük. Bunlardan başka bir de bir Byte uzunluğundaki komutlar var ki bunlar genelde ana işlem ünitesinin içini ilgilendiren veya başka bir deyimle herhangi bir operand'a gereksinim duymayan komutlardır. Burada, birinci bölümde andığımız durum register'inden söz edelim. Yükleme komutları sırasında N (negative) ve Z (zero) flag'ları etkilenir. Diyelim ki örneğimizdeki gibi akümülatör'e $0A yükledik. Bu sayı sıfır olmadığı için Z flag'ı sönük (=0) olacak; bu sayının yedinci biti sönük olduğu için N flag'ı sönük olacaktır. Başka bir adresleme tipi de sıfırıncı sayfa adreslemesidir. Bu adresleme tipi yapı bakımından absolut adreslemeyle eşdeğerdir. Fakat geçerli olduğu adres bölgesi hafızanın ilk 256 byte'ıdır. Bu adres bölgesi $0000'dan $00FF'a kadar gider. Dikkat ettiyseniz, adresin yüksek değerli Byte'ı sıfır değerindedir. Sıfırıncı sayfa ismi de buradan gelmektedir. Açıkça görülmektedir ki bu adresleme tipinde aynı işlem için absolut adreslemeye göre bir Byte tasarruf ediyoruz. Böylece bu işlem hafızada daha az yer tutacağı gibi, aynı zamanda ana işlem ünitesi tarafından belli bir miktar daha hızlı yürütülür. Dezavantaj olarak sınırlı adres bölgesi görünmekle birlikte, avantajlar daha ağırlıktadır. Çünkü bilgisayarımızın önemli sistem değişkenleri (işletim sisteminin bilgisayarı hayatta tutmak için kullandığı hafıza bölgesi) bu bölgede saklanmaktadır. Bir örnek verecek olursak:
LDA $73
Bu işlem hafızaya iki Byte olarak yerleşecektir. Bunlar komutun kendisi olan $A5 (165) ve adresi gösteren $73 (115)'tir. Bu işlemi BASIC'te yazmak istersek:
$73
Tabii BASIC'te vereceğimiz karşılaştırmalı örnekler açısından absolut adresleme ile sıfırıncı sayfa adresleme arasında bir fark görünmeyecektir. Bu arada aklımıza gelmişken bir şeyi hatırlatalım: Sıfırıncı sayfa ile ilgili adreslemeleri, isterseniz absolut adreslemeler ile de yapabilirsiniz. Hiçbir engel yok, fakat size sağlanan avantajı kullanmamakla programınız uzar ve biraz yavaşlar. Önemli bir adresleme tipi de indeksli adreslemedir. Burada X- ve Y- indeks registerlerinin asıl önemini daha iyi kavrayacağız. Bilhassa makine dilindeki özel döngülerde çok kullanılan bu adresleme tipine örnek verecek olursak:
LDA $25B8,X
Burada ilk bakışta görünen absolut adreslemenin indeksleme ile birlikte yapıldığı. Peki gerçekte ne oluyor?.. Gayet basit: Ana işlem ünitesi $25B8 adresini oluşturduktan sonra hemen bu adresin içeriğine saldırmıyor. Önce bu adrese X-Register'inin içeriğini topluyor; yeni bir adres oluşturuyor. Bu adresin içeriğini akümülatör'e yüklüyor. Diyelim ki X-Register'inin içeriği $35. Şimdi ana işlem ünitesinin içinde şöyle bir aritmetik işlem yapılıyor:
$25B8 + $35 = $25ED
Yani sonuç olarak akümülatör'e $25ED adresindeki yazmaçın içeriği yükleniyor. Programın bu bölgesi her defasında değişik bir X- değeriyle yürütülürse elde edilen adres farklı olacağı için her defasında farklı bir değer yüklenecektir. Bu adresleme tipi bilhassa döngülerde ve veri tablolarının işlenmesinde kullanılır. Mesela bir şifreleme tablomuz ve bir şifre çözücü tablomuz olsun, bu ikisi sayesinde bu karışık işlemleri, indeksli adreslemeyi kullanarak basit işlemlerle yerine getirebiliriz. Daha ilerde başka uygulama örnekleri de göreceğiz. BASIC'te bu işlemi yapan satır şöyle bir şey olacaktır:
$25B8
Buradaki X, X-registerinin içeriğini; A'da akümülatör'ü simgelemektedir. İndeksleme işleminde X-registerinin yerine Y-registerini de kullanabiliriz. Örneğin:
25B8,4
LDA
Burada ise Y-register'inin içeriği adrese toplanır ve esas adres oluşturulur. Mesela; böylece birbirinden bağımsız iki döngü değişkenimiz olur ve içiçe geçmiş döngüler kurabiliriz. Sıfırıncı sayfanın da kendine özgü indeksli adresleme tipi vardır. Burada sıfırıncı sayfa adreslemesinin avantajlarının yanında indeksleme işlemini de kullanabilmemiz sağlanır. Bir örnek olarak:
LDA $BA,X
Bu da iki Byte'lık bir komut olacaktır. Şimdi geliyoruz; değişik ve kullanışlı olduğu kadar anlaşılması zor olan bir adresleme tipine, indirekt indekslemeli adresleme. Bu adresleme sıfırıncı sayfanın özelleşmiş bir kullanımı olduğu için sıfırıncı sayfanın önemini daha iyi anlayacağız. Sıfırıncı sayfada peşpeşe gelen iki Byte'ın içeriği bir adres oluşturuyor, daha sonra bu adrese Y-register'i toplanıyor ve esas adres oluşturuluyor. Çok mu karışık?.. Peki, bir örnekle görelim:
LDA ($70),Y
Diyelim ki, $0070'in içeriği $20; $0071'in içeriği $C8 olsun. Bu ikisi birlikte $C820 adresini oluştururlar. Hemen arkadan Y-register'inin indeksleme işlemi geliyor. Diyelim ki Y-register'inin içeriği de $B3. Böylece bu değeri de adresimize toplayarak $C8D3 adresini elde ederiz. Bu adresin içeriği (diyelim ki $C8D3'ün içeriği $4F olsun); akümülatör'e yüklenir. Şematik olarak inceleyecek olursak:
LDA ($70),Y
(870) $20
(871) sC8
$C820
m- $B3
$C8D3
(SC8D3)- $4F
Bu değer de akümülatör'e yüklenir. BASIC'te bu işlemi şöyle yaptırırız:
A-PEEK(PEEK 4112)4256*PEEK(113)4 1)
sT0 si
İndirekt adresleme operand'ın parantez içinde yazılmasıyla ayırt edilir. Bu adresleme tipi çok güçlü program yapılarının kurulmasını sağlar. Çünkü iki Byte süren bir işlemle bütün hafıza elinizin altında. Bu adresleme tipi de döngülerde ve tabloların değerlendirilmesinde kullanılır. Basit indekslemeden daha esnek ve güçlüdür. Çünkü yalnızca bir sayfayı (256 Byte'lık bölge) değil tüm hafızayı işleyebiliyorsunuz. Tabii bu işlemleri yapabilmek için sıfırıncı sayfada iki Byte'lık bir göstergeç hazırlamak gerekiyor. Son olarak indekslemeli indirekt adreslemeyi göreceğiz. Bu tip yukarıda anlattığımızın tersine Y-register'i ile değil X-register'i ile yapılıyor. Genelde kullanımı ötekine göre daha az olan bu adresleme tipinde de sıfırıncı sayfada bir göstergeç bulunuyor. Bu göstergeç esas adresi içeriyor. İndeksleme işlemi ise sıfırıncı sayfa içinde bu göstergecin yerini bulmak için yapılıyor. Daha anlaşılır olması için gene bir örnek üzerinde çalışalım:
LDA (70,X)
$08
840
(879)- 820
$2040
(82040)- SA9
Bu değer de akümülatör'e yüklenir. BASIC'te bu satırı şöyle yazarız:
AzPEEK(PEEK(1124 1)
1
Demek ki burada önce X-değeri operand'a toplanıyor ve göstergecin adresi elde ediliyor. Bu göstergecin içeriği alınarak esas adres elde ediliyor ve bu adresten akümülatör'e yükleme işlemi yapılıyor. Bu adresleme tipi pek ender kullanılır ve pek kullanışlı olacağı da şüphelidir. Bu yüzden başlangıçta bu adresleme tipine pek önem vermeyeceğiz. Şimdiye kadar gördüğümüz komutları ve adresleme tiplerini toplu olarak görelim:
Adresleme tipi LDA LDX LDY direkt $A9 $A2 $A0 absolut $AD $AE $AC sıfırıncı sayfa $A5 $A6 X-indeksli $B5 — $BC Y-indeksli $B9 $BE — 0'cı sayfa X-indeks — — $B4 0'cı sayfa Y-indeks — — — — indirekt indeksli sı — — indeksli indirekt
2. Saklama Komutları
Saklama komutları, yükleme komutlarının tam tersi işlemi yaparlar. Bu komutlar yardımıyla bir register'in içeriğini hafızanın belli bir adresine yazabiliriz. Bu işi yapan komutlar şunlardır:
STA STX STY larınız değişmez. Saklama komutundan önce ne ise, aynı şeklini korur. Aşağıdaki listede bütün komut kodlarını ve geçerli adresleme tiplerini bulacaksınız.
Adresleme tipi STA STX STY absolut $8D $8E $8C sıfırıncı sayfa — $86 X-indeksli $9D $9E $9C Y-indeksli $99 — — 0. sayfa X-indeksli — — — $94 0. sayfa Y-indeksli — — — — indirekt indeksli sı — indeksli indirekt sı —
Yapılan işlemleri BASIC'te, POKE komutunu kullanarak yapacağız. Bu komut hafızadaki herhangi bir elemana ulaşıp onun içeriğini değiştirmemizi sağlar. Adresleme tipleri elemanın adresini elde ederken uygulanan farklı yöntemleri gösterir. Birkaç örnek verelim:
Bu komutlar görüldüğü gibi akümülatör'ün (aynı şekilde X-register'inin, ya da Y-register'inin) içeriğini hafızanın belli bir adresindeki elemanda saklamamıza yarar. Kullanabileceğimiz adresleme tipleri, tek eksik dışında, yükleme komutları ile aynıdır. Bu eksik adresleme tipi tabii ki direkt adreslemedir. Çünkü bu adresleme tipinde belli bir adres belirtilmemektedir. Saklama sırasında saklanan register'in içeriği değişmediği için (ya da ana işlem ünitesinin içinde hiçbir değişiklik olmadığı için) hiçbir flag etkilenmez. Bilhassa ilk makine dili programları yazarken çok düşülen bu hataya karşı uyaralım dedik: DİKKAT! Sakladığınız değere göre flag-
STA $8000 POKE
$8000
STA $C020,Y POKE YA
$C020
STY $FI
- $OOF1
diğini bir kere daha hatırlatalım. Özetlersek ilk iki bölümde ana işlem ünitesinin en önemli iki komut grubunu gördük. Bu komutlar ana işlem ünitesinin, bilgisayarın hafıza birimleri ile bilgi alış verişini sağlarlar. Bütün programlarda en yoğun kullanılan komut grupları bunlardır.
3. Ana İşlem Ünitesinin İçinde Bilgi Transferi
Şimdiye kadar, ana işlem ünitesinin registerlerinden hafızaya; hafızadan ana işlem ünitesinin registerlerine bilgi transferinin nasıl yapılacağını gördük. Peki ana işlem ünitesinin bir registerinden bir diğerine bilgiyi nasıl göndereceğiz? Bunun için; herhalde, önce hafızaya yazıp sonra hafızadan okumaktan daha kolay bir yöntem olmalı. Evet var, bunlar bir iki basit ve özel komuttan oluşan bir grup. Bu komutlarla, mesela akümülatör'ün içeriğini X-register'ine kopyalayabilirsiniz veya tam tersi... Burada görüldüğü gibi iki registerin belirlenmesi gerekir. Bunlardan birincisi kaynak register'i (bilgi bu register'den çıkacak), diğeri de hedef register'i (bilgi bu register'e ulaşacak) olacaktır. Yapılan işlem ise kaynak register'inin, içeriği bozulmadan, hedef register'inde bir kopyasının oluşturulmasıdır. Bütün transfer komutları bir byte uzunluktadır ve hiçbir operand'a ihtiyaç göstermezler. Yapılacak işlem oldukça açık ve basittir. Bundan başka bu komutlarla yapılan bilgi transferi diğer komutlara göre çok daha hızlı yürür. Şimdi bu komutları tek tek görelim. Tabii bu arada daha anlaşılır olması açısından, aynı işi yapan BASIC'te yazılmış satırlarımızı unutmadık.
TAX
A
Akümülatör'ün içeriği X-register'ine kopya edilir. Z- ve N-flag'ları etkilenir. Akümülatör'ün içeriği değişmez.
Saklama komutları da yükleme komutları gibi adresleme tiplerine göre 2 veya 3 Byte uzunluğunda olabilirler. Yükleme komutları için belirttiğimiz tüm kurallar aynen geçerlidir. Tabii flag'ların yapılan işlemden etkilenme-
TXA
TAX Bu komut yukarıdakinin tam tersi işlemi yapar. Bilgi akışının yönü değişir. X-register'inin içeriği akümülatör'e kopya edilir. Bu işlem sırasında Z- ve N-flag'ları etkilenir. Burada X-register'inin içeriği etkilenmez.
TAY ği TYA A
Bu komutlar yukarıdakilerin Y-register'ine uygulanmış halidir. Yukarıda belirtilen kurallar aynen geçerlidir. Tek fark, işlemlerin X-register'i yerine Y-register'i ile yapılmasıdır. Geriye kalan iki komut yığın göstergeci (stackpointer) ile ilgili komutlardır. Bu göstergecin ne işe yaradığını tüm detaylarıyla daha ileride göreceğiz. Yığın göstergeci yalnızca X-register'i ile bilgi transferinde bulunabilmektedir.
TSX XxX
Yığın göstergecinin içeriği X-register'ine kopya edilir. Z- ve N-flag'ları etkilenir. Yığın göstergecinin değeri hiçbir şekilde etkilenmez.
TXS SP
Anlaşılacağı üzere, burada yapılan işlem yukarıdakinin tam tersidir. X-register'inin içeriği yığın göstergecine kopya edilir. Bu komut sırasında ana işlem ünitesinin hiçbir flag'ı etkilenmez. Çünkü yığın göstergeci A-, X- ve Y-register'leri gibi işlem register'i değildir. Burada da X-register'inin içeriği korunur. Son iki komut için belirtmemiz gereken bir konu da bunların öyle her yerde değil de, kritik programlarda kullanılan komutlar olduğudur. Aşağıdaki tabloda bu komutları ve onlara ait komut kodlarını bulacaksınız. (Tabii ki burada adresleme tipi diye bir şey söz konusu değil.)
Emir TAX TXA TAY TYA
4. Aritmetik İşlem Komutları
Bu kısımda ana işlem ünitemize hesap yaptıracağız. COMMODORE 64'ümüzün biricik 6510 ana işlem ünitesi direkt olarak sadece toplama ve çıkarmadan anlıyor. Aritmetik işlemler yürütülürken bazı kuralları göz önünde tutmamız gerekiyor. Bunlar: Her hesaplama iki operand'a ihtiyaç gösterir. Sonuç, bu iki operand arasında söz konusu işlem yapıldıktan sonra elde edilir. 6510 ana işlem ünitesinde operand'lardan biri akümülatör'de olmak zorunda. İkinci operand ise hafızada olabilir. Tabii, ikinci operand için değişik adresleme tipleri kullanılabilir. Bulunan sonuç daima akümülatör'de tutulur. Aynı kural bir sonraki kısmımız olan mantıksal işlemler için de geçerli. BASIC komutları ile vereceğimiz örnekler bu anlatılanların daha iyi kavranmasına yardımcı olacaklardır.
İlk önce toplama işlemine göz atalım. Burada hafızanın içeriği akümülatör'e toplanır ve sonuç akümülatör'e yazılır.
ADC
İki adet 8-bitlik sayıyı topladığımızı düşünelim ve sonuç 8-bite sığmayacak kadar büyük olsun (8-biti geçemeyeceği de kesindir, yani yalnızca bir ekstra bite ihtiyacımız olacak). Burada, “elde verme” denilen işlem yapılacaktır. Konuyu lafla değil de örnekle incelersek daha iyi olacak galiba:
ADC #$3A; bu işlemden önce akümülatör $E4 içersin.
$9E — S3A — 9000111010
Toplama işlemi şöyle olacaktır: %10011110
%11011000 = $D8
Burada gördüğünüz ikili sistemdeki toplama işlemi onluk sistemdekinin benzeri kurallarla yürütülmektedir. Göz önünde bulundurmamız gereken durumlar 4 adettir:
Onluk sistemimizde olduğu gibi “elde” olayı, bir sonraki basamakta değerlendirilir. Bu örnekte sonucumuz $D8 veya ikili olarak %11011000 idi. Görüldüğü gibi bu sonuç 8-bite sığdı. Bu olayı bir şans gibi görmektense sığmayacağı durumları da içeren genel bir çözüm görsek daha iyi olacak: ADC #$3A burada akümülatör $E4 içersin. $E4 = %10110100 $3A = %00111010 Toplama işlemi şöyle olacaktır:
Yo 11100100 Yo 00111010
90 100011110
Evet, 8-biti aşmayı becerdik. Ne yapacağız?.. Tabii bizim pek bir şey yapacağımız yok. Ama işlem ünitesinin bu durumda ne yaptığına bakalım. Akümülatör'ümüz 8-biti tutabiliyordu. En alttaki 8 biti o alır. Dokuzuncu bit ise CARRY flag'ine gönderilir. İşte burada CARRY flag'inin esas önemini görmekteyiz. Şayet işlem sonucunda “elde” oluşursa CARRY biti “1” durumuna geçer, şayet “elde” yoksa CARRY biti “0” durumuna geçer. CARRY flag'ını aynı zamanda, toplama işlemlerinde akümülatör'ün dokuzuncu biti olarak görebiliriz. Şayet sekiz bitten daha uzun olan sayıları toplamak istersek CARRY biti imdadımıza yetişir ve sekiz bitlik grupların birbirine bağlanmasını sağlar. Mesela iki adet sekiz bit kullanırsak 0 ile 65535 arasındaki sayıları belirtebiliriz ki bu değer aralığı bile kendi başına birçok problemin çözümünde yeterli olabilir. Bu işlemi yaparken önce alt taraftaki sekiz bitlik kısımları toplarız, daha sonra üst taraftaki sekiz bitlik kısma sıra gelir. Diyelim ki birinci toplama işleminde taşma oldu ve “elde” var. Bu durumda ikinci toplama işlemi sırasında bu “elde”nin değerlendirilmesini sağlamalıyız. İşte burada 6510 ana işlem ünitesinin bir özelliği bizi bu zahmetten kurtarıyor. Çünkü yapılan tüm toplama işlemlerinde CARRY biti de beraber toplanıyor. (Tabii burada ufak bir sorun fark ediyoruz, ilk toplama işlemi sırasında CARRY biti “0” olmak zorunda, yoksa istediğimiz işlemi yanlış yaparız. Bunun için CARRY bitini sıfırlarız. Bununla ilgili komutu daha sonraki bölümlerde göreceğiz.) Şimdi bu toplama işlemini daha doğru olarak bir kere daha yazalım:
ADC SSA ç
$3A Carry biti
Bir toplama işleminden sonra oluşması muhtemel bir aritmetik taşma olayını CARRY flag'ından anlayabiliriz. Toplama işlemi sırasında C-flag'ının yanında N- ve Z- flag'ları da etkilenir. Bunlardan başka V-flag'ı da etkilenir. Bu flag'ın yorumu sadece işaretli aritmetik kullanıldığında (negatif veya pozitif) geçerli olduğundan pek üzerinde durmayacağız. D-flag'ına göre toplama işlemi onluk sisteme benzer şekilde yapılabilmesine rağmen, pek az kullanılan, zahmetli ve tehlikeli bir yöntem olduğu için pek değinmeyeceğiz. Aşağıdaki tabloda ADC komutu için geçerli adresleme tiplerini ve komut kod'larını bulacaksınız.
Adresleme tipi ADC
direkt $69 absolut $6D 0.cı sayfa $65 X-indeksli $7D Y-indeksli $79 0.cı sayfa X-indeks $75 indirekt indeksli indeksli indirekt $61 kat bu sefer yerine “alma” söz konusu. Böyle bir durumda sonuç negatif olacaktır. Gene CARRY flag'ını kullanarak sorunumuzu çözebiliyoruz. “Elde” ve “alma” olayları tümüyle ters şeyleri simgelediklerinden, 'alma' durumunu CARRY flag'ının sönük (0) olması ile belirtiyoruz. Tabii yanık bir CARRY flag'ı “alma” olmadığını gösterecektir. Toplama işlemi için bulduğumuz formül burada da geçerli. Sekiz bitten uzun sayılar arasında çıkarma yaparken, CARRY bitinin yardımını istiyoruz. Şimdi bir örnek ve aynı işi yapan BASIC satırını görelim:
SBC $3A
VA hm $3A Carry biti
İkili sistemde çıkarma işlemi toplama işlemine benziyor. Burada da 4 değişik durum söz konusu:
0-0-0 -1 — | bir sonraki basamaktan “alma”
Diyelim ki akümülatör'de $7F var. Yukarıdaki işlem şöyle yürür:
$7F $3A
YOL 9000111010
CARRY sönük olarak (yani “alma”
olmadan) yapılan çıkarma işlemi şöyle olacaktır:
Yo 00111010
M01000101 — $45
Sonuç olarak 9001000101, yani $45 elde ettik. Bu sayı pozitif olduğu için herhangi bir “alma” olayı olmayacak ve CARRY biti yanık kalacaktır. Şimdiki örneğimizde akümülatör $1E içersin, CARRY flag'ımız gene yanık:
$1E 9000011110
$3A - 9000111010
Çıkarma işlemi de toplama işlemi ile benzer sınırlar içinde yürür. Hafızanın içeriği akümülatör'den çıkarılır, sonuç toplamada olduğu gibi akümülatör'e yazılır. Burada da toplamadakine benzer bir soruna rastlıyoruz. Fa-
Çıkarma işlemi şöyle gerçekleşecek:
96 00011110 Yo 00111010
%0011100100
Sonuç olarak %0011100100, yani $E4 elde ettik. İşlem sırasında karşılaştığımız için CARRY biti sönecektir. Peki bu olayın yorumunu nasıl yapacağız?.. Konuya daha hakim olmak için onluk sistemde düşünelim. İlk sayımız 30'du, ikincisi ise 58; çıkarma işlemi elde etmemiz lazım. Peki burada bulduğumuz $E4, yani 228 ne alaka?.. Şayet bulduğumuz bu sonucu sekiz bitlik gösterimin sihirli sayısı 256'dan çıkarırsak 28 elde ederiz. Sekiz bitlik gösterimde 255'ten sonra gene 0'a geliriz (256 yerine). İşlemimiz sonucunda CARRY biti sönük olduğu için elde ettiğimiz sonucun negatif olduğunu anlıyoruz. Bu sayının “ikili komplement”ini bularak pozitif karşılığını elde edebiliriz. Bu işlem şöyle yapılıyor: Sayının tüm bitlerini tersine çeviririz ve sonuca bir toplarız:
11100100 bitleri ters çevirirsek: 00011011 artı bir
Sonuç olarak %00011100, yani $1C, yani 28 bulduk.
Aritmetik işlemleri kullanırken dikkat edilmesi gereken şey; toplama işlemlerinden önce CARRY flag'ının söndürülmesi (yanık CARRY flag'ı, olmayan bir elde yaratır ve işlem yanlış yapılır), çıkarma işleminden önce ise CARRY flag'ının yakılmasıdır (sönük bir CARRY flag'ı, olmayan bir alma yaratır ve yanlış sonuç bulunur). (Dijital devre tasarımı ile ilgilenenler için bir not düşelim. Ana işlem ünitesi çıkarma işlemini, toplama işlemini yaptığı devrelerin aynısını kullanarak yapıyor. Tek fark, çıkarma işlemi sırasında çıkarılacak sayının bitlerini ters çevirip toplamak. Geri kalan tüm işlemler olduğu gibi aynı. Mesela CARRY bitinin yorumlanması... İnanmıyorsanız, kâğıt, kalemi elinize alıp birkaç örnek deneyin.) Aşağıdaki tabloda komut kodlarını ve adresleme tiplerini bulacaksınız:
Adresleme tipi SBC
direkt $E9 absolut $ED 0.cı sayfa $E5 X-indeksli $FD Y-indeksli 0.cı sayfa X-indeks $F5 indirekt indeksli $F1 indeksli indirekt $E6
Dear Basic Öğren
Bu noktaya kadar değişken kavramını ve değişken türlerini gördük. Ayrıca geçen sayımızda, sayısal değişkenlerin bilgisayar içinde nasıl depolandıklarını da gözden geçirdik. Ancak daha ilk baştan da söylediğimiz gibi, bilgisayar yalnızca sayısal değişkenler üzerinde aritmetik hesaplar yapmakla kalmaz; karakter zinciri dediğimiz türden “harfsayısal” (alphanumeric) değişkenleri kabul ederek, onlar üzerinde de işlem yapabilir. Sıralama, arşiv, database vb. programlarının çoğu bilgisayarın bu yeteneğinden yararlanmaktadır. Peki nasıl oluyor da, yalnızca 1 ve 0'ları tanıyabilen bu alet harfler, kelimeler ve cümleler üzerinde de böyle karmaşık işlemler yapabiliyor.
Konuya bir iki mantık ilkesini gözden geçirerek başlayacağız. Çünkü BASIC'in de temelinde klasik mantığın “Boole cebiri” diye anılan bir formelleştirilmesi yatmaktadır.
Mantığın Temeli: Karşılaştırma
Bütün mantıksal işlemlerin temelini verilerin karşılaştırılması oluşturur. Karşılaştırmanın sonucuna bağlı olarak hangi işlerin yapılacağına karar verilir. Örneğin ödemeleri hesaplayan bir programı ele alalım. Birçok başka program gibi, bu programın da ne zaman duracağını bilmesi gerekir. Bunu sağlamak için makineden gelen her kaydı (örneğin ödeme yapılacak kişilerin isimlerini) “SON” adlı bir veriyle karşılaştırması istenebilir. Karşılaştırmanın sonucu olumsuz ise program işlemesini sürdürecek, yok eğer olumlu ise belirli bir sinyali gönderdikten sonra duracaktır. Kimi bakımlardan mantıksal işlemler aritmetik işlemlere benzetilebilir. Bu benzerliği biraz daha ayrıntılı olarak gözden geçirelim. Herhangi bir aritmetik işlemde birbirinden farklı üç birimin varlığından söz edebiliriz:
— İşlenenler — İşlem işareti — İşlemin sonucu
Aritmetik işlemlerde işlenenler, hesaplamada kullandığımız sayılar, işlem işaretiyse, ne tür bir işlemin yapılacağını belirten simgedir. Sonuç ise bu işaretin gösterdiği işlem sayesinde işlenenlerden çıkarılan sayıdır.
Örneğin, 5+3=8 problemini ele alalım. Sonuç (8), işlenenlere (yani 5 ve 3'e) + (toplama) işleminin uygulanmasıyla bulunmuştur. İşareti değiştirdiğimiz, örneğin +'yı - yaptığımız takdirde, aynı işlenenler bu defa başka bir 10 tabanlı sayıyı, 2 sayısını vereceklerdi. Bu noktadan hareketle farklı işlem biçimlerini (toplama, çıkarma, çarpma, bölme gibi) farklı birleştirme kuralları olarak düşünebiliriz. Çünkü bu işlemler belirli bir sayılar grubuyla, yani işlenenlerle, başka bir sayılar grubunun, yani sonuçların nasıl bir araya getirilebileceğini belirleyen kurallardır. İşte işlem işareti bu kuralı ifade eder.
Mantık İşlemleri
Buraya kadar mümkün bütün sayısal bilgilerin ve aralarındaki ilişkilerin bilgisayarda 2 tabanlı sayı sistemi ile temsil edildiğini gördük. Sayısal bilgilerin bu şekilde temsil edilebilmesini mümkün kılan, bilgisayarı oluşturan elektrik devrelerindeki sinyalin yüksek düzeyinin 1 rakamına, alçak düzeyinin ise 0 rakamına tekabül ediyormuşçasına yorumlanabilme imkânıdır. Fakat bilgisayar sadece sayısal bilgileri değil, diğer başka bazı cins bilgileri de işleyebilir. Tek şart bu bilgilerin elektrik sinyalleri tarafından temsil edilebilmesidir. İşte bu yüzden doğru ya da yanlış olabilecek bütün cümleler de, bilgisayar tarafından işlenebilir. Bu durumda, cümlenin doğru olma hali, elektrik sinyalinin yüksek düzeyine (ya da 2 tabanlı sayı sisteminde 1 rakamına), cümlenin yanlış olma hali ise, sinyalin alçak düzeyine (yani 2 tabanlı sayı sisteminde 0 rakamına) tekabül eder.
Peki, bilgisayar ya doğru ya da yanlış olabilecek cümleleri saklayabilir, diyoruz. Bu cümlelerle ilgili olarak daha fazla ne yapabilir? Birkaç cümleden çıkacak sonucun ne olabileceğini söyleyebilir mi? Doğruluğu ya da yanlışlığı kabul edilmiş bir dizi cümleden ne gibi sonuçların çıkarılabileceğini tespit edebilir mi? Bu soruyu cevaplandırabilmek için, cümlelere özgü birleştirme kurallarını gözden geçirmemiz lazım. Yani farklı cümleler üzerinde ne gibi işlemler yapılabileceğini bilmemiz lazım. İşte, 2 değerli, yani sadece doğru ya da yanlış olabilen cümlelerin tamamına Boole cebiri işlemleri ve bu işlemlerin birleştirme kuralını gösteren işaretlere de Boole cebiri işaretleri denir. Bilgisayar, sayısal olmayan bilgiler üzerinde işlem yaparken, bu işlemleri kullanır. Bu işlemlerin işareti genellikle işlemin İngilizce adından oluşur.
VE işlemi: AND
Örnek olarak iki cümle alalım: “Yağmur yağıyor” ve “Sokaklar ıslak”. Şimdi bu iki cümleyi “ve” kelimesi ile birleştirerek yeni bir cümle elde edebiliriz: “Yağmur yağıyor ve sokaklar ıslak”. Eğer ilk iki cümlemizin her ikisi de aynı anda doğruysa, o zaman “ve” işleminin sonucunda elde ettiğimiz cümle de doğrudur. Yok eğer, cümlelerden herhangi biri yahut her ikisi birden yanlışsa, o zaman bu cümlelerden hareketle “ve işlemi” sayesinde elde ettiğimiz sonuç cümlesi de yanlıştır. O halde ve işleminin mümkün sonuçlarını aşağıdaki gibi bir tabloyla gösterebiliriz:
Yağmur yağıyor — Sokaklar ıslak
<ox<Uu
Yağmur yağıyor ve sokaklar ıslak
<<<
Bu şekilde herhangi bir Boole cebiri işleminin olası sonuçlarını gösteren tablolara “doğruluk değerleri tabloları” denir. Dikkat edilirse, yukarıdaki doğruluk değerleri tablosunda gösterilen sonuçlar kullandığımız cümlelerin içeriklerinden, yani anlamlarından tamamen bağımsızdırlar. Sonuçlar bakımından önemli olan, cümlelerin ne anlama geldiği değil, sadece doğru olup olmadıklarıdır. Bu sebepten ötürü, herhangi bir işlemin olası sonuçlarını belirtirken, her cümle çifti için ayrı bir doğruluk değeri tablosu çizmemiz gerekmez. Doğru ya da yanlış olabilecek bütün cümle çiftleri “ve” işlemi ile birbirlerine bağlandıklarında aynı sonuçları verirler. Bu yüzden her bir Boole cebiri işlemi için tek bir doğruluk değerleri tablosu yazmamız yeterlidir.
VE işlemi ile birbirine bağlayacağımız iki cümleden birincisini A, ikincisini B ile gösterelim. Ayrıca “doğru” değerini 1, “yanlış” değerini ise 0'la gösterelim. Son olarak Boole cebiri işlemlerinin genellikle söz konusu işlemin İngilizce adı ile gösterildiğini de hatırlayalım. Bu takdirde, ve işleminin doğruluk değerleri tablosunu 2 tabanlı sayı sisteminin sembollerini kullanarak aşağıdaki şekilde yazabiliriz:
A B A AND B 1 1 1 1 0 0 0 1 0 0 0 0
Doğruluk değerleri tablosunun bu şekilde yazılması, bize matematiksel işlemlerle Boole işlemleri arasındaki ilişkiyi görme olanağını da veriyor. Şöyle ki, aşağıdaki birinci örnekte 1 rakamları sayısal birer değeri ifade etmektedir. İkinci örnekte ise aynı rakamlar, bir ve işlemi ile birleştirilen iki cümlenin doğruluk değerlerini ifade etmektedirler.
Commodore 64'ün Can Simidi
1+1=10 (10 sembolünün 2 tabanlı sistemde 10 tabanlı 2 sayısını ifade ettiğini hatırlatalım). 1 and 1=1
VEYA İşlemi: OR
Bir önceki örnekte gördüğümüz iki cümleyi “ve” kelimesi ile birleştirebileceğimiz gibi “veya” kelimesi ile de birleştirebiliriz: “Yağmur yağıyor veya sokaklar ıslak”. Ancak gündelik dilde kullanılan “veya” kelimesi ile bir Boole cebiri işlemi olan “veya” arasında önemli bazı farklar vardır. Şöyle ki, gündelik dilde kullanılan “veya” genellikle “ya biri ya öteki ama ikisi birden değil” anlamına gelir. Dolayısıyla, cümlelerden her ikisinin birden doğru olması halinde, gündelik dilde bu cümlelerin “veya” kelimesi ile birleştirilmesinden elde edilen cümle yanlış olur (Bu bakımdan gündelik dildeki “veya” kelimesi, Boole cebirindeki dar veya işlemine tekabül eder. Bkz. bir sonraki bölüm). Halbuki Boole cebirinin OR kelimesi ile gösterilen “veya” işleminde, cümlelerden herhangi birinin ya da ikisinin birden doğru olması sonuç cümlesinin doğru olmasına yeter. O halde veya işleminin doğruluk değerleri tablosunu şöyle yazabiliriz:
A B A OR B 1 1 1 1 0 1 0 1 1 0 0 0 DAR VEYA İŞLEMİ: XOR
Daha önce de belirttiğimiz gibi Boole cebirindeki “dar veya” işlemi, aynen gündelik dildeki “veya” kelimesi gibidir. Yani “dar veya” işlemi ile birleştirilmiş iki cümleden müteşekkil cümle, bileşenlerinden ancak bir tanesi doğru ise doğrudur. O halde doğruluk değerleri tablosunu şöyle:
A B A XOR B 1 1 0 1 0 1 0 1 1 0 0 0
Daha sonra da göreceğimiz gibi birçok bilgisayar dilinde cümleleri “dar veya” işlemi ile birleştirme imkânı öngörülmemiştir. Ancak bu büyük bir problem çıkarmaz. Çünkü “dar veya” işlemi ile elde etmek istediğimiz sonucu diğer işlemleri birleştirerek elde edebiliriz. Bunun nasıl yapılabileceğini görmek için, bir Boole cebiri işlemi daha öğrenmemiz gerekiyor.
DEĞİL İşlemi: NOT
Diğer işlemlerin aksine, “değil” işlemi iki ya da daha fazla cümleyi birleştirmez, tek bir cümlenin doğruluk değerini tersine çevirir ya da zıtlar. Yani cümle doğru ise, kendisine “değil” işlemi uygulandığında sonuç yanlış olur, yok eğer cümle yanlışsa, değil işleminin sonucu doğru olur:
A NOT A 1 0 0 1
Karmaşık İşlemler
Şimdiye kadar ihtiyacımız olan bütün Boole cebiri işlemlerini gördük. Ama bunlar basit işlemlerdi. Yani iki temel bilgi alıp bunlar üzerine tek bir işlem uygulamakla yetiniyorduk. Ama aynen matematikte olduğu gibi Boole cebirinde de, kimi işlemlerin sonuçlarına yeni işlemler uygulayarak, işlemlerimizi karmaşıklaştırabiliriz. Ama yine aynen matematikte olduğu gibi burada da parantezlerin rolü çok mühimdir. Mesela şu iki karmaşık işlemi karşılaştıralım:
A B C 1 1 1 1 1 0 1 0 0 1 0 1 1 1 0 1 0 0 0 1 0 0 (A OR B) AND C (A OR (B AND C)) 1 1 0 1 0 1 1 1 1 1 1 0 0 0 0
Görüldüğü gibi, parantezlerin nasıl yerleştirildiğine bağlı olarak, işlemler çok farklı sonuçlar veriyor. Çünkü birinci durumda, önce A ve B cümleleri “veya” işlemi ile birleştiriliyor (A OR B), daha sonra bu işlemin sonucu sanki tek bir cümleymişçesine muamele görüyor ve “ve” işlemi ile C cümlesi ile birleştiriliyor ((A OR B) AND C), ikinci örnekte ise önce B ve C cümleleri “ve” işlemi ile birleştirilerek (B AND C) tek bir cümleye dönüştürülüyor. Sonra bu tek cümle gibi yorumlanan sonuç “veya” işlemiyle A cümlesi ile birleştiriliyor (A OR (B AND C)). Şimdi daha karmaşık bir örneği gözden geçirelim. Biraz önce, “dış veya” -XOR- işlemini görecek bir komutun birçok bilgisayar dilinde bulunmadığını, ancak bu işlemin vereceği sonucun, diğer işlemlerin birleştirilmesiyle elde edilebileceğini söylemiştik. Hatırlanacağı gibi XOR işleminin doğruluk değerleri tablosu şöyleydi:
A B A XOR B 1 1 0 0 1 1 1 0 1 0 0 0
Sonuç olarak aynı doğruluk değerleri dizisini şu işlem de verebilir: (A OR B) AND NOT (A AND B). Önce bunun gerçekten böyle olup olmadığını, söz konusu işlemin gerçekten XOR işlemiyle aynı sonucu verip vermediğini sınayalım:
A B A OR B 1 1 1 0 1 0 1 1 0 0 0 0 A AND B NOT (A AND B) 1 0 1 0 1 0
(A OR B) AND NOT (A AND B) 0
Görüldüğü gibi bileşenlerin doğruluk değerleri aynı olduğunda, (A OR B) AND NOT (A AND B) işleminin sonuçlarıyla, A XOR B işlemlerinin sonuçlarının da doğruluk değerleri aynı oluyor. Dolayısıyla bir işlemin yerine ötekini kullanabiliriz. İki işlem arasındaki özdeşliği yaklaşık bir Türkçe'ye çevirdiğimizde de görebiliriz: A veya B doğru olacak ama (ve) hem A hem B doğru olmayacak. 0