Kısım I · Makine · Bölüm 7

Makine Dili ve Assembly

Komutların bitlerden okunur adlara yolculuğu (Machine code and assembly)

Neden burada · Neye dayanıyor

Neden burada: İşlemcinin komut getirip çalıştırdığını gördük. Şimdi o komutun neye benzediğine bakacağız. Bir bit dizisinin nasıl “topla” veya “başka adrese git” olabildiğini görmeden, insanın yazdığı kodu makine koduna çeviren derleyicinin niçin var olduğunu tam yerine koyamayız.

Neye dayanıyor: Bölüm 6.2'den getir–çöz–çalıştır ve program sayacını, Bölüm 6.4'ten ISA sözleşmesini kullanacağız. Hesabı Bölüm 4.5'in ALU'su yapacak; değerler Bölüm 5.4'teki gibi adresle seçilecek. İkilik yazımı yeniden öğrenmeyeceğiz; gerekirse Bölüm 2.1'e dön.

  1. Kat 5Programlama dilleri ve derleyiciİnsanın daha büyük düşünce parçalarını makineye indiren araçlar. Bölüm 9.
  2. Kat 4Makine dili ve assemblyAynı makine işlemlerinin bitlerle ve okunabilir sembollerle yazılması.
  3. Kat 3İşlemci ve bellekKomutu yürüten donanım; Bölüm 6.
  4. Kat 0–2Bit, kapılar, hesap ve hafıza devreleriKomutun yaptığı her eylemin fiziksel alt katları.

Bölüm sonunda küçük bir programı satır satır takip edebileceksin: bir sabit yazmaca nasıl alınır, RAM'deki içerik nasıl okunur, sonuç nasıl saklanır, koşul program sayacını nasıl değiştirir? Bir assembly programcısı olman gerekmiyor. Hedef, “bir satır kodun altında neler var?” sorusuna artık kendi gözünle gördüğün bir cevap verebilmen.

7.1 Makine kodu: işlem kodu ve işlenen

Makine kodu (machine code), belirli bir işlemcinin komut setine göre anlam kazanan komut bitleridir. Bir komutun bir bölümü hangi işin yapılacağını, başka bölümleri kullanılacak değerleri, yazmaçları, hedefi veya akış kararını belirleyebilir. Bit dizisinin komut olarak ne anlama geldiğini CPU'nun ISA sözleşmesi söyler; yalnızca görünüşüne bakıp evrensel anlam çıkaramazsın.1

İşlem kodu (opcode), komutun işlem türünü belirleyen alanıdır. İşlenen (operand) ise işlemin kullandığı değer, yazmaç veya bellek konumu gibi unsurdur. Bir toplamada “topla” işin türü, kullanılacak iki sayı işlenenlerdir. Ama gerçek ISA'larda “burada hep dört opcode biti, şurada hep iki adres” diye tek şema yoktur. Bazı bilgiler komutta açık yazılır, bazıları komutun tanımı gereği örtüktür.1

Bu bölümün küçük makinesi: Hack

Nand2Tetris'in Hack makinesi (Hack computer), öğretim için tasarlanmış 16 bitlik makinedir. Adı burada bilgisayara izinsiz girme anlamında kullanılmıyor; belirli bir öğretim bilgisayarının özel adıdır. Hack'te iki temel komut biçimi vardır. İkisi de 16 bit uzunluğundadır; her biri komut belleğinde bir konum kaplar.1

Örneğin 2 sabitini A'ya koyan komutun bitleri 0000000000000010'dır. İlk bit “A komutu” biçimini seçer; kalan bitler 2 sayısını temsil eder. Aynı 16 bit, veri olarak okunursa yalnızca 2 sayısıdır. Komut belleğinden getirildiğinde ise “A'ya bu sabiti yükle” eylemini tetikler. Bölüm 2'nin “anlam bağlamdadır” ilkesi donanım–yazılım sınırında tekrar karşımıza çıktı.1

Bir komutun iç alanları

A komutu:  0 vvvvvvvvvvvvvvv
             └─ 15 bit sabit değer

C komutu:  111 a cccccc ddd jjj
               │   │    │   └─ atlama koşulu
               │   │    └───── sonuç hangi hedefe yazılsın?
               │   └────────── ALU'nun altı kontrol biti
               └────────────── ikinci girdi A mı, RAM[A] mı?

Yazımdaki boşluklar yalnızca göz için var. C komutunun comp denen hesap alanı, a ile altı kontrol bitini kapsar; dest hedefi, jump atlamayı belirler. Böylece Bölüm 4'te gördüğün ALU kontrol bitlerinin programdan nereye bağlandığını görebiliyorsun. Komut yalnızca bir toplama sonucu değil, parçaların ne yapacağını belirleyen kontrol bilgisidir.12

Şemayı ezberlemek zorunda değilsin. Anlaman gereken şu: “komut çözme”, bit alanlarının ilgili devre girişlerine bağlanmasıdır. İnsan açısından okunması güç bir 16 bit dizisi, donanım açısından zaten çeşitli seçim hatlarını belirleyen bilgidir. Sonraki alt bölüm bu aynı dizilerin insana daha okunabilir nasıl yazıldığını gösterecek.

7.2 Assembly ve assembler: aynı işin sembollü yazımı

Assembly (assembly language), makine işlemlerini bit dizileri yerine okunabilir sembollerle yazmamızı sağlayan düşük seviyeli dildir. Assembler (assembler) ise bu sembollü metni hedef makinenin komut bitlerine dönüştüren programdır. Nand2Tetris'in ayrımı nettir: bir tarafta assembly'deki adlar ve semboller, diğer tarafta CPU'nun yürüteceği ikilik komutlar vardır.13

insanın yazdığı metin       çeviren program       CPU'ya giden komut
@2                    ──►  assembler       ──►  0000000000000010

@2, Hack assembly'sinde “A yazmacına 2 koy” demektir. CPU metindeki @ karakterini ve 2 karakterini okuyarak çalışmıyor. Assembler bunları önce komut bitlerine dönüştürüyor. Klavyeyle yazdığın dosyada @2'nin bir metin olmasıyla, yüklenen komutun 16 bitlik sayısal temsil olması ayrı katlardır.1

Üç ad: A, D ve M

Bu bölümde bütün Hack örneklerini okumak için üç gösterim yeterli:

GösterimAnlamıÖrnek
ACPU içindeki A yazmacının değeriA = 2 ise yazmaçta 2 var
DCPU içindeki veri yazmacının değeriD = 20 ise çalışma değeri 20
MA'nın seçtiği veri belleği konumunun içeriği: RAM[A]A = 2 iken M, RAM[2]'dir

M, CPU içindeki üçüncü bir çalışma yazmacı değildir. Bellekte A ile seçilmiş yer için kullanılan addır. A değişince M'nin gösterdiği yer değişir. Örneğin @16 ardından D=M yazarsan RAM[16]'daki değeri D'ye alırsın; @16 ardından D=A yazarsan 16 sabitini D'ye alırsın. İki programın ilk satırı aynı, ikinci satırın kullandığı kaynak farklıdır.1

Bu ayrımı kurduğunda @2 satırının neden tek başına “RAM[2]'yi oku” olmadığını da görürsün. Yalnızca A'ya 2 yerleştirir. Sonraki komut A'yı sabit veri, veri adresi veya atlama hedefi olarak kullanabilir. Hangi rolün etkin olduğunu komutun bütünü söyler.1

Hesap, hedef ve atlama

Hack'in C komutu sembollerle hedef=hesap;atlama biçimindedir. Örneğin D=D+A, “D ile A'yı topla, sonucu D'de sakla” demektir. Bu komutta atlama alanı yoktur. 0;JMP ise ALU'da 0 üretir ve koşulsuz olarak A'nın gösterdiği komut adresine atlar; hedef alanı yoktur. Bazı alanların yazılmaması, onların donanımda bilinmez olduğu anlamına gelmez; bit karşılıklarında uygun boş/etkisiz seçim kullanılır.1

D=D+A'daki eşittir işaretini matematiksel denklem gibi okuma. Bu bir durum değişikliği tarifidir: eski D ve A'dan yeni sonuç hesaplanır; sonra D güncellenir. Dahası, Hack'in desteklediği hesap adları sınırlı bir listedir. Herhangi bir cebirsel ifade yazıp assembler'ın onu çözeceğini bekleyemezsin. Örneğin D=D*A, bu makinenin tek komutluk çarpması değildir; Hack'in yerleşik çarpma komutu yoktur.1

Etiketler komut değildir

Etiket (label), programdaki bir komut konumuna verdiğin sembolik addır. Hack'te (SON) satırı, sonraki komutun adresini SON adıyla tanımlar. Assembler bu adı adres sayısına dönüştürür. Etiketin kendisi CPU'nun çalıştıracağı ek bir komut üretmez ve komut belleğinde bir yer tüketmez. Yorumlar ve boş satırlar da yürütülecek komut değildir.1

Bu yüzden metnin satır numarası ile komut adresi her zaman aynı değildir. Dosyanın başına açıklama eklemek, bütün komutları başka hesaplar yapmaya dönüştürmez. Assembler gerçek komutları sayar, etiketlerin adreslerini bulur ve sembolleri çözerek bitleri üretir. Hack'te .asm sembollü kaynak dosyanın, .hack ise araçların yüklediği ikilik komut gösterimini içeren dosyanın uzantısıdır.1

“Her assembly satırı bir komut” yalnızca kaba bir sezgidir

Basit Hack komutları bire bir bit desenine gider. Ama etiket, yorum ve boşluklar komut üretmez. Gerçek assembler'lar ek yönergeler veya birden fazla komuta açılan kolaylıklar da sunabilir. Dolayısıyla sembollü dosyanın her metin satırını CPU'nun bir adımı sayma. Buradaki doğrusal ilişki, yalnızca gösterdiğimiz gerçek A ve C komutları içindir.1

7.3 Yükle/sakla, aritmetik, atlama ve dallanma

Farklı ISA'ların yazımı değişse de iş aileleri tanıdıktır: veriyi getir veya sakla, değer üzerinde hesap yap, bir sonraki komutun yerini değiştir. Bu küçük ailelerin birleşimi, ilk bölümdeki koşul ve döngü gibi üst kat yapılarını kurar. Burada genel iş adlarıyla Hack'in özel yazımını yan yana koyacağız; Hack'te komutun adının mutlaka LOAD olması gerekmez.1

Yükle ve sakla: bitleri hesap alanına getir

Yükle/sakla (load/store), bellekteki değeri yazmaca alma ve yazmaçtaki değeri belleğe yazma işlerini anlatır. Bir yükleme, sayıyı aritmetik olarak değiştirmek zorunda değildir; aynı bit deseninin başka saklama alanına aktarılmasıdır. Önce konum seçilir, sonra aktarım yapılır.1

@16
D=M
@17
M=D
  1. @16: A'ya 16 koy; böylece sonraki M kullanımı RAM[16]'yı gösterebilir.
  2. D=M: RAM[16]'daki içeriği D'ye al. Kaynak RAM konumunu silmez.
  3. @17: A'yı 17 yap. D'deki alınmış değer korunur.
  4. M=D: D'nin değerini şimdi seçilmiş olan RAM[17]'ye yaz.

Bu program, RAM[16]'daki içeriği RAM[17]'ye kopyalar. Belleğin kendisi hiçbir “16'dan 17'ye geçiş” kararı vermedi; CPU'nun art arda yürüttüğü komutlar bu yolu oluşturdu. Verinin bir yerde durması ile kopyalanması, farklı işlerdir.

Aritmetik: saklanan eski değerden yeni değer üret

Aritmetik komut, ALU'dan bir hesap ister. Hack'te D=D+A D ile A'yı, D=D+M D ile seçilmiş RAM içeriğini toplar. İkinci komut, Hack'in hesap sırasında M değerini kullanabilmesine dayanır; bundan bütün ISA'ların aritmetik komutlarının doğrudan RAM kullanacağı sonucunu çıkarma. Bölüm 6'nın RISC/CISC ayrımı tam bu noktada farklı düzenlerin varlığını hatırlatıyordu.1

ALU sonucu yine belirli genişlikte bit desenidir. Bölüm 4'teki taşma ve ikiye tümleyen sınırları assembly yazınca ortadan kalkmaz. Bu bölümün toplama örneğinde küçük pozitif sayılar seçiyoruz; sonucu sığdırma meselesini gizlice “assembly daha kesin hesaplar” diye çözmüş olmuyoruz. Sınırları önceki bölümden taşımaya devam ediyoruz.

Atlama ve dallanma: komut adresini değiştir

Atlama (jump), normal sıradaki komut yerine başka adresten devam etmektir. Dallanma (branch), burada özellikle bir koşula bağlı akış seçimini anlatır. Bu kelimeler bazı belgelerde daha geniş ve örtüşen anlamlarda da kullanılabilir. Biz “koşulsuz atlama” ile “koşullu dallanma” diyerek ayrımı açık tutacağız.1

@SON
D;JEQ

İlk satır A'ya SON etiketinin komut adresini koyar. İkinci satır D değerini ALU çıkışı olarak kullanır; sonuç sıfırsa JEQ seçimi atlamayı gerçekleştirir. D sıfır değilse program normal sıradaki komuta geçer. Koşulun veri değeri D'de, hedef adres A'dadır; aynı bit dizisini hem veri hem adres diye karıştırmıyoruz.1

Hack yazımıALU sonucuna göre koşulSonraki PC
D;JEQD sıfırsaKoşul doğruysa A, değilse normal devam
D;JGTD pozitifseKoşul doğruysa A, değilse normal devam
D;JLTD negatifseKoşul doğruysa A, değilse normal devam
0;JMPKoşulsuzA

Bu karar, Bölüm 4'ün ALU'sundaki “sonuç sıfır mı?” ve “negatif mi?” çıkışlarına dayanabilir. Bölüm 3'ün Boole mantığı, hangi koşulda PC'nin başka adres kabul edeceğini belirler. Yani üst kattaki bir if, en altta bir sayı sonucu ve bir sonraki adresin seçilmesiyle kurulabilir.2

Bir if'in iskeleti

@16
D=M
@SON
D;JEQ
@17
M=1
(SON)
@SON
0;JMP

İlk iki satır RAM[16]'daki değeri D'ye alır. Üçüncü satır SON adresini A'ya koyar. Dördüncü satır, değer sıfırsa SON'a atlar; böylece @17 ve M=1 satırları atlanır. Değer sıfır değilse bu iki satır RAM[17]'ye 1 yazar. Sondaki etiket ve iki komut, burada işi bitirdikten sonra aynı noktada kalan bekleme döngüsüdür; bilgisayarın gücünü kesmez. Hack'in öğretim programlarında işi “bitirme” yöntemi olarak bu kalıp kullanılır.1

Bu örneğin başında RAM[17]'yi sıfırlamadık. Bu yüzden koşul yanlışsa “RAM[17] kesin 0 olur” diyemeyiz; önce ne varsa o kalır. Assembly okurken yapılmayan işi yapılmış varsaymamak önemlidir. Üst kattaki koşul örneğini tam kurmak istiyorsan başlangıç durumunu veya öteki dalın yazmasını da belirtmelisin.

Döngü: geriye atlayan bir yol

(DONGU)
@16
D=M
@SON
D;JEQ
@16
M=M-1
@DONGU
0;JMP
(SON)
@SON
0;JMP

Bu program için RAM[16]'da başlangıçta küçük, pozitif bir sayaç bulunduğunu varsayalım. Etiket komut değildir. @16 ve D=M sayacı alır; @SON ve D;JEQ sıfırsa çıkar. Sonraki @16 aynı veri konumunu yeniden seçer, M=M-1 bir azaltır. @DONGU başlangıç komutunun adresini yükler, 0;JMP oraya döner. SON'daki iki komut ise bekler. Başlangıç değeri 3 seçildiğinde veri konumundaki azalış 3 → 2 → 1 → 0'dır.

Neden ikinci kez @16 var? Çünkü koşul için @SON yazdığımızda A artık veri adresi değil, komut adresi tutuyordu. Azaltma yapmadan önce veri adresini geri seçmezsek M yanlış konumu gösterir. Bu küçük ayrıntı, düşük seviyede çok şeyin açıkça yönetilmesi gerektiğini gösterir. Sonraki bölümde yüksek seviyeli dilde yalnızca while yazabilmemizin değerini buradan hatırlayacaksın.

7.4 Küçük örnek: iki sayıyı toplayan Hack programı

Şimdi koşul ve döngü ayrıntısını bir yana bırakıp çok küçük bir programı baştan sona görelim: 2 ile 3'ü topla ve sonucu RAM[0]'a yaz. Bu, Nand2Tetris Project 5'in Add.hack sınamasında da kullanılan iştir. Biz hem sembollü metni hem bitlerini hem de her adımın değiştirdiği durumu yan yana açacağız.34

@2
D=A
@3
D=D+A
@0
M=D
(SON)
@SON
0;JMP

Altı hesap/aktarım adımı

  1. @2: A yazmacı 2 olur. Bu bir sabit yüklemedir; RAM[2]'nin içeriği okunmaz.
  2. D=A: A'daki 2, D'ye kopyalanır. Şimdi ilk toplanan D'dedir.
  3. @3: A yazmacı 3 olur. D'deki 2 değişmez.
  4. D=D+A: ALU 2 + 3 hesaplar; D yeni değer olarak 5 kabul eder.
  5. @0: A yazmacı 0 olur. Bu kez A'yı sonuç konumunun adresi olarak kullanacağız.
  6. M=D: A'nın seçtiği RAM[0], D'deki 5'i alır. D'nin kendisi silinmez.

(SON) etiketinin ardından @SON A'ya 6 komut adresini yükler; 0;JMP tekrar 6 adresine döner. İlk altı komutun adresleri 0–5'tir; etiket yer tüketmediği için SON 6'yı gösterir. Döngü 6 ve 7 komutları arasında sürer. Bu bekleme, sonucu değiştirmeden tutmak için eklenmiştir; evrensel bir “programı kapat” komutu değildir.1

Yürütülen komut adresiKomutSonrasında ASonrasında DRAM[0]
0@22Önceki değerÖnceki değer
1D=A22Önceki değer
2@332Önceki değer
3D=D+A35Önceki değer
4@005Önceki değer
5M=D055

Tablo başlangıçta her yerin sıfır olduğunu varsaymıyor. Program D'yi kullanmadan önce kendisi dolduruyor ve RAM[0]'ı sonuç anında üzerine yazarak belirliyor. Bir programın başlangıç durumundan hangi bilgileri gerçekten kullandığını ayırmak için bu alışkanlık faydalıdır. Çıktının 5 olması, RAM'in başlangıç içeriğinin 0 olmasına bağlı değildir.

Aynı programın komut bitleri

AdresAssembly16 bitlik komut
0@20000000000000010
1D=A1110110000010000
2@30000000000000011
3D=D+A1110000010010000
4@00000000000000000
5M=D1110001100001000
6@SON → @60000000000000110
70;JMP1110101010000111

Örneğin D=D+A satırını 111 | 0 | 000010 | 010 | 000 diye ayırabilirsin. Başlangıç C komutudur; a=0 A'yı seçer; 000010 ALU toplamasını seçer; 010 sonucu D'ye yönlendirir; 000 atlama istemez. Bitlerin rastgele bir şiiri değil, belirli kontrol alanları olduğunu artık kendi gözünle görebiliyorsun.12

Bu sekiz komut, yüklenmiş komut belleğinde 8 × 16 = 128 bit, yani 16 byte'lık komut bilgisi demektir. Ama Nand2Tetris'in .hack dosyası bu bitleri satır başına on altı adet ASCII 0/1 karakteriyle saklayan metin biçimidir. Dolayısıyla diskteki .hack dosyasının tam 16 byte olduğunu söylemiyoruz. Bit bilgisinin genişliği ile onun metin dosyasında gösterimi ayrı ölçülerdir.1

Bu bölümdeki komut bitleri, etiket adresleri ve küçük programların durum geçişleri Python'da Hack'in yayımlanmış komut alanlarına göre kontrol edildi. Bu bir gerçek CPU hız ölçümü veya bütün bir işletim sisteminin sınaması değildir. Ama gösterdiğimiz 2 + 3 toplamının, koşul örneğinin ve sayaç döngüsünün anlatılan başlangıç koşullarında aynı son durumlara gittiğini denetlememizi sağlar.

Uygulamamızda: Bölüm 4'teki puan satırını aşağı indir

Gerçek prototipini görmedik. Yalnızca öğretim için puanın RAM[16]'da tutulduğunu ve 10 puan eklemek istediğimizi varsayalım. Başlangıç puanı 20 olsun:

@16
D=M
@10
D=D+A
@16
M=D

İlk satır puanın yerini seçer. İkinci satır eski puanı D'ye alır. Üçüncü satır A'ya 10 sabitini koyar. Dördüncü satır toplamı D'ye yazar. Beşinci satır sonuç konumunu tekrar seçer; çünkü A artık 10 tutuyordu. Son satır yeni puan 30'u RAM[16]'ya yazar. Böylece Bölüm 4'ün hesabı ile Bölüm 5'in saklaması, Bölüm 6'nın komut akışı içinde birleşir.

Bu, JavaScript motorunun her bilgisayarda tam bu altı Hack komutunu ürettiği iddiası değildir. Hack senin cihazının ISA'sı değil; kavramları açık görmek için seçtiğimiz makinedir. Gerçek dil araçları başka yazmaçlar ve komutlar kullanabilir, bazı adımları birleştirebilir. Değişmeyen fikir, eski bilgiden yeni sonuç üretip sonraki kullanım için temsil etmektir.

7.5 Neden çoğu uygulama assembly ile yazılmıyor; nerede hâlâ yaşıyor?

Az önce iki küçük sayıyı toplamak için bile sabiti yükledik, eski değeri koruduk, başka sabit yükledik, topladık, sonuç adresini seçtik ve sakladık. Bu adımlar öğretim için değerlidir; ama bütün bir hukuk uygulamasının soru seçimini, puan hesabını, kullanıcılarını ve ekranlarını bu düzeyde yazmak çok fazla ayrıntı yönetmek demektir. Nand2Tetris'in Project 4 girişindeki gerekçe de budur: düşük seviyeli program yazmak, yüksek seviyeli dillerin neyi kolaylaştırdığını görünür kılar.3

Aynı fikir için gereksiz yere çok ayrıntı

Yüksek seviyeli dilde “puanı artır” veya “sorular boyunca dolaş” diye daha büyük düşünce parçaları kurarsın. Bunları hangi yazmaçlarla ve hangi atlamalarla gerçekleştireceğini çeviri araçları düzenler. Kaynak programın amacıyla belirli bir işlemcinin küçük mekanik adımları arasında bir kat oluşur. Bu katın ayrıntısını Bölüm 9: C ve derleyici'de kuracağız; burada yalnızca hangi sorunu çözdüğünü görüyoruz.13

İkinci neden taşınabilirliktir. Hack assembly'sindeki @16 ve D=M, ARM veya x86-64 için aynı program metni değildir. Farklı makineler farklı yazmaçlar ve komut biçimleri sunar. Bir üst dilin kaynak metni de her ortamda değişmeden çalışacak diye garanti yoktur; ama işlemin bütün düşük seviyeli adımlarını elle her hedef için yeniden yazma yükü büyük ölçüde araçlara bırakılabilir.1

Elle yazılmaması, ortadan kalkması değildir

Bir uygulama geliştiricisi assembly yazmasa bile üretilen makine komutları vardır; araçlar bunları okunabilir assembly olarak gösterebilir. Ayrıca bazı performansa duyarlı veya donanımın belirli komutlarına doğrudan erişim gerektiren küçük parçalar hâlâ elle yazılabilir. GCC belgelerindeki asm düzeneği, C içine assembly komutları katmanın resmî bir örneğidir. Belgeler bunun zaman açısından kritik işler veya C'den kolayca erişilmeyen komutlar için kullanılabileceğini belirtir.5

Buradan “assembly yazarsam her zaman daha hızlı olur” sonucu çıkmaz. GCC'nin aynı belgesi, derleyiciyle assembly arasındaki veri giriş–çıkışlarının ve yan etkilerin doğru bildirilmesi gerektiğini ayrıntılı anlatır. Düşük seviyeye inmek, kontrol kazandırırken sorumluluğu da artırır. Bu kitabın hedefi, agent'ın ürettiği kodu mümkün olduğunca aşağıdan yazdırmak değil; hangi katın hangi ayrıntıyı taşıdığını bilmektir.

Hata ayıklarken okunabilir komutları görmek de işe yarayabilir: programın nerede kaldığını ve hangi değeri okuduğunu makine düzeyinde inceleyebilirsin. Nand2Tetris'in CPU emülatörü aynı programı ikilik veya sembollü görünümde sunar. Sembollü görünüm, donanımın gizlice İngilizce bildiği anlamına gelmez; araç insanın görebilmesi için aynı komutlara adlarını geri gösterir.3

Bölüm 1 ve 6 ile bağlantı

Bölüm 1'de “komut, programın bir adımıdır” demiştik; artık o adımın bir bit deseni ve saklanan durum üzerinde etkisi olduğunu gördük. Bölüm 6'da ISA'yı sözleşme diye tanımladık; bu bölümdeki komut biçimleri o sözleşmenin küçük bir örneği. Assembler komutu çalıştırmaz, karşılığını üretir; CPU o karşılığı yürütür. İnsanın yazdığı metin, çeviren araç ve yürüten donanım üç ayrı roldür.

Bu bölümde neler kuruldu

Makine komutları da bit dizileridir; işlem kodu türü, işlenenler kullanılan bilgileri belirtir. Assembly bu işlemlerin sembollü yazımı, assembler onları bitlere çeviren programdır. Hack'te A ve D CPU yazmaçlarıdır; M, RAM[A]'nın adıdır. Yükle/sakla veri taşır, aritmetik hesap üretir, atlama ve dallanma PC'yi değiştirerek koşul ve döngü kurar. 2 ile 3'ü toplayan programı hem bit hem durum geçişi olarak okuduk. Yüksek seviyeli diller bu küçük işleri gizleyen bir kat sağlar; assembly bilgisi ise alt katı görmenin aracı olarak kalır.

Terimler sözlükte, Bölüm 7 başlığı altında. Dile geçmeden önce Bölüm 8'de tek bir “bellek” kutusunun içinde neden farklı hızlarda depolar bulunduğunu göreceğiz.

Birincil okuma

Nisan ve Schocken, Chapter 4: Machine Language; özellikle 4.1.2 Languages, 4.1.3 Commands ve 4.2.1–4.2.4 Hack Machine Language Specification. Derinleşmek için Project 4: Machine Language Programming'in “Background” ve “Tools” metinleri; sonra Bottom-Up CS, Chapter 3: Computer Architecture'de yükleme, hesap ve dallanma anlatımı.

Takıldığın yeri yapay zekâ öğretmenine sor. Ondan 2 + 3 örneğinin sayıları yerine 4 ve 6 koymasını iste; her adımın ardından A, D ve RAM[0]'ı önce sen yaz. Sonra D=A'yı D=M ile değiştirdiğinde cevabın neden artık başlangıç belleğine bağlı olduğunu tartışın.

Bölüm sonu soruları

Özellikle adres ile içeriği ve araç ile CPU'yu ayır.

1. Bir bit dizisinin hangi makine komutu olduğunu ne belirler?
Bitlerin komut anlamı ISA'ya bağlıdır; aynı görünüm her işlemci ailesinde aynı işi yapmak zorunda değildir. Bkz. 7.1.
2. Assembler'ın bu bölümdeki temel görevi hangisidir?
Assembler çevirir; CPU yürütür. Bir araçta iki özellik birlikte bulunsa bile roller ayrıdır. Bkz. 7.2.
3. Hack'te A = 16 iken M neyi gösterir?
M, RAM[A]'nın içeriğidir. A 16 ise seçilen veri konumu RAM[16]'dır; M ayrı CPU yazmacı değildir. Bkz. 7.2.
4. Tek başına @2 komutu ne yapar?
@2 bir A komutudur; sabit yükler. RAM[2]'nin okunması için sonraki komutta M kullanılmalıdır. Bkz. 7.2.
5. D;JEQ, önceden A'ya yüklenen hedefe ne zaman atlar?
JEQ, bu komutta ALU'dan geçirilen D sonucu sıfırsa atlama ister; değilse sıradaki komuta geçilir. Bkz. 7.3.
6. Bir etiket satırı, örneğin (SON), komut adreslerini nasıl etkiler?
Etiket sonraki komut konumunu adlandırır; kendi başına makine komutu üretmez. Bu yüzden metin satırı sayısı komut sayısı değildir. Bkz. 7.2.
7. 2 + 3 örneğindeki @0 ardından M=D satırı ne yapar?
@0 A'yı hedef adres yapar; M=D D'deki 5'i bu adrese yazar. D silinmez. Bkz. 7.4.
8. Çoğu uygulamayı assembly yerine daha üst bir dille yazmanın gerekçesi nedir?
Üst dil küçük makine adımlarını araçlara devreder; altta bellek ve işlemci komutları yine vardır. Bkz. 7.5.
9. @16, D=M, @10, D=D+A, @16, M=D dizisini kendi cümlelerinle açıkla. İkinci @16 neden gereklidir? Başlangıç puanı 20 ise sonuç ne olur?
Örnek cevap

RAM[16] seçilir ve içeriği D'ye alınır; A'ya 10 koyulur; ALU toplamı D'ye yazar. A artık 16 olmadığı için sonuç adresini @16 ile yeniden seçmek gerekir; M=D toplamı oraya yazar. 20 + 10 = 30. Bkz. 7.4.

10. Sayaç döngüsündeki “sıfırsa SON'a git” adımını ve sona giderken kullanılan bekleme döngüsünü ayır. Birisi koşullu, öbürü koşulsuz akış değişikliğini nasıl kuruyor?
Örnek cevap

@SON hedefi A'ya koyar; D;JEQ sayacın D'ye alınmış değeri sıfırsa PC'yi bu hedefe değiştirir. Koşul sağlanmazsa azaltma ve başa dönüş komutları yürür. SON'da ise @SON ve 0;JMP her zaman aynı hedefe döner; artık sayacı sınamaz ve makineyi kapatmaz. Bkz. 7.3.

Kaynaklar

  1. Nisan ve Schocken, The Elements of Computing Systems, Chapter 4: Machine Language, 4.1–4.3: opcode/operand, assembly/assembler, yükleme–saklama, akış, Hack A/D/M, A/C komut alanları, etiketler, dosya biçimleri ve basit ISA'nın sınırları.
  2. Nisan ve Schocken, Chapter 5: Computer Architecture, 5.2 ve 5.3.1: komut alanlarının ALU, yazmaçlar ve PC'ye bağlanması, zr/ng ile atlama kararı.
  3. Nand2Tetris, Project 4: Machine Language Programming, “Background” ve “Tools”: sembollü programın ikilik karşılığa çevrilmesi, CPU emülatörünün iki görünümü ve düşük seviyeli program yazmanın amacı.
  4. Nand2Tetris, Project 5: Computer Architecture, “Testing the Computer chip”, Add.hack: 2 ve 3'ün toplanıp RAM[0]'a yazılması. Bu bölümün satır açıklamaları, puan, koşul ve sayaç örnekleri öğretim için hazırlanmış; sayılar, komut bitleri ve durum geçişleri Python ile doğrulanmıştır.
  5. GCC resmî belgeleri, Extended Asm — Assembler Instructions with C Expression Operands, “Remarks”, “Input Operands”, “Output Operands” ve “Clobbers and Scratch Registers”: C ile assembly'nin birlikte kullanılması, özel komutlara erişim ve derleyiciye karşı sorumluluklar.
  6. Ian Wienand, Computer Science from the Bottom Up, Chapter 3: Computer Architecture, başlangıçtaki yükle–hesapla–sakla örneği ve dallanma açıklaması; ek okuma.