LC-3 assembly dili: direktifler, iki geçişli assembler, sembol tablosu
Etiketler, .ORIG/.FILL/.BLKW/.STRINGZ/.END, assembler'ın iki geçişi ve sembol tablosu; döngü, string işleme, alt program (JSR/RET) içeren tam programlar ve çıktıları.
7-8. haftada makine kodunu elle yazdın: offset’leri saydın, bitleri yerleştirdin. Bu hem yavaş hem hataya açık. Assembly dili bu işi bir programa, assembler’a bırakır. Sen komutları isimleriyle (ADD, LD), adresleri etiketlerle (LOOP, COUNT) yazarsın; assembler adresleri hesaplar ve 0-1 dizisini üretir.
Bu hafta sınav soruları genelde şöyle: bir programın sembol tablosunu kur, bir satırın makine kodunu sembol tablosundan bul, programın ekrana ne yazdığını ya da bellekte neyi değiştirdiğini söyle, assembler hatasını bul.
Bir satırın yapısı
LOOP ADD R1, R1, #-1 ; sayaç azalt
| Alan | Örnek | Zorunlu mu? |
|---|---|---|
| Etiket (label) | LOOP | hayır |
| Opcode ya da pseudo-op | ADD | evet |
| Operandlar | R1, R1, #-1 | komuta göre |
| Yorum | ; sayaç azalt | hayır |
Sayılar: # ondalık (#-1, #48), x onaltılık (x3000, xFE00). Etiket bir adresin adıdır: LOOP, o satırın bellekteki adresini temsil eder.
TUZAK · opcode’u etiket yapmak.
STR,ADD,BR,R3gibi isimler etiket olamaz. Assembler satırın ilk kelimesi opcode ise onu komut sanar. “String” içinSTRyerineTEXTya daMSGyaz.
Pseudo-op’lar (assembler directives)
Pseudo-op çalışma zamanında çalışan bir komut değildir; assembler’a verilen bir talimattır. Hepsi nokta ile başlar.
| Direktif | Ne yapar? | Kaç kelime? |
|---|---|---|
.ORIG x3000 | Programın başlangıç adresi | 0 |
.FILL #4 | Bu konuma 4 değerini koy | 1 |
.FILL LOOP | Bu konuma LOOP’un adresini koy | 1 |
.BLKW 3 | 3 kelimelik boş yer ayır | 3 |
.STRINGZ "Hi" | Her karakter bir kelime, sonda x0000 | uzunluk + 1 = 3 |
.END | Kaynak kodun sonu | 0 |
.END programın durduğu yer değildir. Program HALT ile durur; .END sadece assembler’a “bundan sonrası kaynak değil” der.
İki geçişli assembler
Sorun: LD R1, COUNT satırına geldiğinde COUNT daha aşağıda tanımlanıyor olabilir. Offset’i hesaplamak için COUNT’un adresi lazım. Buna ileri referans (forward reference) denir.
Çözüm: programı iki kez oku.
- Birinci geçiş: hiçbir şeyi çevirme. Bir konum sayacı (location counter) tut: .ORIG ile başlat, her komut ve .FILL için 1, .BLKW n için n, .STRINGZ için uzunluk + 1 artır. Etiketli her satırın adresini sembol tablosuna yaz.
- İkinci geçiş: her satırı makine koduna çevir. Bir etiket kullanıldığında adresini sembol tablosundan al, offset’i hesapla.
Çözümlü örnek: sembol tablosu
.ORIG x3000
LEA R0, MSG ; x3000
PUTS ; x3001
LD R1, COUNT ; x3002
LOOP ADD R1, R1, #-1 ; x3003
BRp LOOP ; x3004
HALT ; x3005
COUNT .FILL #4 ; x3006
MSG .STRINGZ "Hello" ; x3007 - x300C (6 kelime)
BUF .BLKW 3 ; x300D - x300F
END1 .FILL x0041 ; x3010
.END
| Sembol | Adres |
|---|---|
| LOOP | x3003 |
| COUNT | x3006 |
| MSG | x3007 |
| BUF | x300D |
| END1 | x3010 |
İkinci geçişin ürettiği kodun bir kısmı:
x3000 xE006 LEA R0, MSG offset = x3007 - x3001 = 6
x3001 xF022 PUTS
x3002 x2203 LD R1, COUNT offset = x3006 - x3003 = 3
x3003 x127F ADD R1, R1, #-1
x3004 x03FE BRp LOOP offset = x3003 - x3005 = -2
x3005 xF025 HALT
x3006 x0004
x3007 x0048 x0065 x006C x006C x006F x0000 "Hello"
Program çalışınca ekrana Hello yazar, sonra R1’i 4’ten 0’a kadar sayar ve durur.
Assembler hataları
| Satır | Hata |
|---|---|
ADD R1, R1, #20 | imm5 -16..15 dışında |
LD R1, FAR (FAR 300 kelime ötede) | PCoffset9 -256..255 dışında |
BRz DONEE | tanımsız etiket (yazım hatası) |
LOOP iki kez tanımlı | aynı etiket iki adres |
STR .FILL #1 | opcode etiket olarak kullanılamaz |
Programlama kalıpları
Bir karakteri aramak
Karakteri karşılaştırmanın yolu: negatifini bir sabite koy, topla, sonuç sıfırsa eşittir.
.ORIG x3000
AND R2, R2, #0 ; R2 = sayaç
LEA R1, TEXT ; R1 = string pointer
LD R4, NEGT ; R4 = -'t'
NEXT LDR R3, R1, #0 ; R3 = *R1
BRz DONE ; null → bitti
ADD R5, R3, R4 ; R5 = karakter - 't'
BRnp SKIP ; eşit değil → atla
ADD R2, R2, #1 ; eşit → say
SKIP ADD R1, R1, #1 ; sonraki karakter
BRnzp NEXT
DONE LD R0, ZERO
ADD R0, R0, R2 ; sayıyı ASCII rakama çevir
OUT
HALT
NEGT .FILL #-116 ; 't' = 116
ZERO .FILL x30 ; '0'
TEXT .STRINGZ "test it"
.END
3
Sayaç 9’u geçerse bu yöntem tek rakam basamaz; sınav soruları bu yüzden küçük sayılarla gelir.
Klavyeden oku, işle, yaz
.ORIG x3000
LD R2, NEGLF ; R2 = -10 (satır sonu)
READ GETC ; R0 = karakter
ADD R3, R0, R2
BRz STOP ; Enter → dur
LD R4, MASK
AND R0, R0, R4 ; büyük harfe çevir
OUT
BRnzp READ
STOP HALT
NEGLF .FILL #-10
MASK .FILL xDF
.END
Girdi bau ve Enter:
BAU
Alt programlar: JSR ve RET
Aynı kodu birçok yerde kullanmak için alt program (subroutine) yazılır.
JSR ETIKET: R7 ← PC (dönüş adresi), PC ← ETIKET. Offset 11 bit (-1024..1023).JSRR R3: aynı şey, hedef register’da.RET: PC ← R7.
.ORIG x3000
LD R2, TIMES
AGAIN JSR PRINT
ADD R2, R2, #-1
BRp AGAIN
HALT
PRINT ST R7, SAVE7 ; dönüş adresini sakla
LEA R0, TXT
PUTS ; TRAP: R7'yi ezer!
LD R7, SAVE7 ; dönüş adresini geri al
RET
TIMES .FILL #3
SAVE7 .BLKW 1
TXT .STRINGZ "ab"
.END
ababab
TUZAK · R7’yi kaydetmemek. Alt programın içinde bir TRAP ya da başka bir JSR varsa R7 ezilir. Kaydetmezsen RET yanlış yere döner. Ayrıca alt programın bozduğu register’ları da kaydetmek iyi alışkanlıktır (callee-save). C’de bu iş çalışma yığınıyla (runtime stack) yapılır (12. hafta).
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
.STRINGZ "Hi" 2 kelime | 3: sonda x0000 var |
.BLKW 3 1 kelime | 3 kelime |
.END programı durdurur | HALT durdurur; .END kaynak sonu |
.ORIG bir kelime kaplar | Hiç kaplamaz |
| Tek geçiş yeter | İleri referanslar için iki geçiş |
| Offset = etiket - satır adresi | etiket - (satır adresi + 1) |
ADD R1, R1, #20 geçerli | imm5 en fazla 15 |
| TRAP R7’yi korur | R7’ye dönüş adresi yazar |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
How many memory locations does MSG .STRINGZ "Hello" occupy?
Cevabı göster
Her karakter bir kelimeye (16 bit) yazılır: H, e, l, l, o = 5. Sonuna bir x0000 (null terminator) eklenir. Toplam 6. Genel kural: uzunluk + 1.
Build the symbol table for this program.
.ORIG x3000
LEA R0, MSG
PUTS
LD R1, COUNT
LOOP ADD R1, R1, #-1
BRp LOOP
HALT
COUNT .FILL #4
MSG .STRINGZ "Hello"
BUF .BLKW 3
END1 .FILL x0041
.ENDCevabı göster
Konum sayacı x3000'den başlar, her komut ve .FILL 1 yer kaplar. LEA x3000, PUTS x3001, LD x3002, LOOP x3003, BRp x3004, HALT x3005, COUNT x3006, MSG x3007.
.STRINGZ "Hello" 6 yer: x3007-x300C. BUF x300D, .BLKW 3 → x300D-x300F. END1 x3010.
Using the symbol table above, what is the machine code of LD R1, COUNT at address x3002?
Cevabı göster
offset = COUNT - (x3002 + 1) = x3006 - x3003 = 3. 0010 001 000000011 = x2203. Assembler bu hesabı ikinci geçişte sembol tablosuyla yapar.
Why does the LC-3 assembler make two passes over the program?
Cevabı göster
LD R1, COUNT satırına gelindiğinde COUNT henüz tanımlanmamış olabilir (aşağıda). Birinci geçiş sadece adresleri sayar ve sembol tablosunu kurar. İkinci geçiş her satırı makine koduna çevirir; artık her etiketin adresi bellidir.
What is the difference between an instruction such as ADD and a pseudo-op such as .FILL?
Cevabı göster
Pseudo-op (assembler directive) çalışma zamanında çalışmaz, bir opcode'u yoktur. Assembler'a ne yapacağını söyler: .ORIG başlangıç adresi, .FILL bir değer koy, .BLKW yer ayır, .STRINGZ string koy, .END kaynak bitti.
Which of these lines generate no memory words at all: .ORIG x3000, .FILL #0, .BLKW 1, .END, HALT?
Cevabı göster
.ORIG sadece konum sayacını ayarlar, .END sadece kaynağın bittiğini söyler. .FILL #0 ve .BLKW 1 birer kelime (x0000) ayırır, HALT bir komut (xF025) üretir.
The assembler reports an error for ADD R1, R1, #20. Why, and how can you add 20 to R1?
Cevabı göster
imm5 5 bitlik 2's complement'tir, 20 sığmaz. Çözüm: ADD R1, R1, #10 iki kez, ya da TWENTY .FILL #20 tanımla, LD R2, TWENTY, ADD R1, R1, R2.
What is printed by this program?
.ORIG x3000
AND R2, R2, #0
LEA R1, TEXT
LD R4, NEGT
NEXT LDR R3, R1, #0
BRz DONE
ADD R5, R3, R4
BRnp SKIP
ADD R2, R2, #1
SKIP ADD R1, R1, #1
BRnzp NEXT
DONE LD R0, ZERO
ADD R0, R0, R2
OUT
HALT
NEGT .FILL #-116
ZERO .FILL x30
TEXT .STRINGZ "test it"
.ENDCevabı göster
-116 = -'t' (ASCII 't' = 116). Her karakter için R5 = karakter - 't'; sıfırsa 't' bulundu, R2 artar. Null (0) gelince döngü biter.
"test it" içinde 3 tane 't' var. R0 = x30 + 3 = x33 = '3', OUT basar.
What is the sum stored at TOTAL?
AND R0, R0, #0
LEA R1, NUMS
LD R2, N
LOOP LDR R3, R1, #0
ADD R0, R0, R3
ADD R1, R1, #1
ADD R2, R2, #-1
BRp LOOP
ST R0, TOTAL
HALT
N .FILL #4
NUMS .FILL #12
.FILL #-5
.FILL #30
.FILL #-7
TOTAL .BLKW 1Cevabı göster
Sayaçlı döngü 4 elemanı toplar: 12 + (-5) + 30 + (-7) = 30. R1 pointer (LEA ile dizinin başı), her turda bir sonraki kelimeye ilerler.
What is printed by this program? Why does PRINT save R7?
LD R2, TIMES
AGAIN JSR PRINT
ADD R2, R2, #-1
BRp AGAIN
HALT
PRINT ST R7, SAVE7
LEA R0, TXT
PUTS
LD R7, SAVE7
RET
TIMES .FILL #3
SAVE7 .BLKW 1
TXT .STRINGZ "ab"Cevabı göster
JSR dönüş adresini R7'ye yazar. PRINT içindeki PUTS bir TRAP'tir ve o da R7'yi ezer. R7 kaydedilmeseydi RET yanlış yere (PUTS'tan sonraki satıra) dönerdi ve program sonsuz döngüye girerdi. Kaydedilip geri yüklendiği için alt program 3 kez çağrılır: ababab.
A program reads characters with GETC until the newline character (ASCII 10), ANDs each with xDF and prints it. What is printed for the input bau followed by Enter?
Cevabı göster
AND xDF bit 5'i temizler: küçük harf büyük harfe döner. 'b' x62 → x42 'B', 'a' x61 → x41 'A', 'u' x75 → x55 'U'. Satır sonu (10) gelince döngü biter. Dikkat: rakamlar da bu maskeden etkilenir ('3' x33 → x13), bu yüzden sadece harflerde kullanılır.
Why can't you name a label STR or ADD in LC-3 assembly?
Cevabı göster
Assembler bir satırın ilk kelimesine bakar: opcode ya da pseudo-op ise komut, değilse etikettir. STR ve ADD opcode olduğu için etiket olarak okunmaz ve satır yanlış çözülür. Etiketler harfle başlar, opcode, register adı (R0-R7) ya da sayı olamaz.
Doğrulama: Tüm sembol tabloları, makine kodları, assembler hataları ve program çıktıları kendi iki geçişli LC-3 assembler + simülatörümüzle üretildi (verify/SEN2201/lc3.mjs, verify/SEN2201/10.mjs).