Giriş/çıkış temelleri: memory-mapped I/O ve polling
Klavye ve ekranın LC-3'e nasıl bağlandığı: KBSR/KBDR/DSR/DDR register'ları, ready bit, polling döngüsü, interrupt farkı ve TRAP servis rutinlerine giriş.
Bir bilgisayar sadece hesap yapsaydı işe yaramazdı: veriyi bir yerden almalı, sonucu bir yere vermeli. von Neumann modelinin input ve output parçaları bu işi yapar. Bu sayfada LC-3’ün klavye ve ekranla nasıl konuştuğunu öğreneceksin. Mekanizma basit: aygıtın iki register’ı var, biri “hazır mısın?” sorusunun cevabını, diğeri karakterin kendisini tutar.
Bu konu 7-8. haftanın LC-3 komutlarını (LDI, STI, BR) kullanır. Henüz o komutları görmediysen bu sayfayı onlardan sonra tekrar oku; burada her komutun ne yaptığını tek cümleyle açıklıyoruz.
Aygıt register’ları
Her I/O aygıtının en az iki register’ı vardır:
- Data register: aktarılan veri (karakter).
- Status register: aygıtın durumu, özellikle “hazır” bilgisi.
LC-3’te klavyenin ve ekranın register’ları:
| Register | Adres | İçerik |
|---|---|---|
| KBSR (keyboard status) | xFE00 | bit 15: yeni karakter var mı |
| KBDR (keyboard data) | xFE02 | bit 7:0: basılan karakterin ASCII kodu |
| DSR (display status) | xFE04 | bit 15: ekran yeni karakter almaya hazır mı |
| DDR (display data) | xFE06 | bit 7:0: ekrana yazılacak karakter |
Memory-mapped I/O
Bazı işlemcilerde I/O için özel komutlar vardır (IN, OUT gibi). LC-3 bunu yapmaz: aygıt register’larına bellek adresi verilir. İşlemci onlara normal bellek gibi yükleme ve saklama komutlarıyla erişir. Bellek arayüzü adresin xFE00 bölgesinde olduğunu görünce isteği belleğe değil aygıta yönlendirir. Buna memory-mapped I/O denir.
Sonuç: “klavyeden oku” = “xFE02 adresini oku”. “Ekrana yaz” = “xFE06 adresine yaz”.
Neden LDI ve STI?
LD ve ST komutları PC’nin yakınındaki bir adrese erişebilir: PC + offset, offset 9 bit, yani -256..+255. Programın x3000 civarında. xFE00 çok uzak. Çözüm: aygıtın adresini programın yanına bir pointer olarak koy ve dolaylı (indirect) eriş.
KBSRP .FILL xFE00 ; KBSR'nin adresi programın içinde
...
LDI R1, KBSRP ; R1 ← M[M[KBSRP]] = M[xFE00] = KBSR
LDI R1, KBSRP iki adımda okur: önce KBSRP’deki değeri (xFE00) alır, sonra o adresin içeriğini R1’e koyar. STI aynı şeyi yazma için yapar.
Senkronizasyon: ready bit
Klavye asenkrondur: kullanıcının ne zaman tuşa basacağı belli değil. İşlemci ise saniyede milyarlarca komut çalıştırır. İkisinin uyumlu çalışması için bir el sıkışma (handshake) gerekir. Bunu ready bit sağlar.
Klavye tarafı:
- Kullanıcı tuşa basar. Klavye ASCII kodu KBDR’ye koyar ve KBSR[15] = 1 yapar.
- İşlemci KBDR’yi okur. Okuma anında KBSR[15] otomatik olarak 0 olur.
- KBSR[15] = 0 iken klavye yeni karakteri KBDR’ye yazmaz; böylece okunmamış karakter ezilmez.
Ekran tarafı:
- DSR[15] = 1 ise ekran hazır.
- İşlemci DDR’ye karakteri yazar. DSR[15] = 0 olur.
- Ekran karakteri bastıktan sonra DSR[15] = 1 olur.
İPUCU · Neden BRzp? Ready bit bit 15, yani 16 bitlik sayının işaret biti. Hazırsa register’a yüklenen değer negatiftir (N). Hazır değilse 0 ya da pozitif (Z ya da P). “Hazır olmadığı sürece bekle” =
BRzpile başa dön.
Polling
İşlemcinin ready bit’ini bir döngüde tekrar tekrar okumasına polling denir.
Klavyeden bir karakter oku:
POLL LDI R1, KBSRP ; R1 ← KBSR
BRzp POLL ; bit 15 = 0 → hazır değil, tekrar sor
LDI R0, KBDRP ; R0 ← KBDR (karakter)
Ekrana bir karakter yaz:
POLL2 LDI R1, DSRP ; R1 ← DSR
BRzp POLL2 ; ekran meşgul, bekle
STI R0, DDRP ; DDR ← R0
Çözümlü örnek: echo
Klavyeden bir karakter oku ve ekrana yaz.
.ORIG x3000
START LDI R1, KBSRP
BRzp START
LDI R0, KBDRP
ECHO LDI R1, DSRP
BRzp ECHO
STI R0, DDRP
HALT
KBSRP .FILL xFE00
KBDRP .FILL xFE02
DSRP .FILL xFE04
DDRP .FILL xFE06
.END
Kullanıcı k yazınca:
k
Program bitince R0 = x006B (‘k’ ASCII kodu), R1 = x8000 (son okunan DSR, hazır), condition code N.
Polling ile interrupt farkı
| Polling | Interrupt-driven | |
|---|---|---|
| Kim sorar? | İşlemci sürekli ready bit’ini okur | Aygıt hazır olunca işlemciye sinyal gönderir |
| Bekleme sırasında işlemci | Döngüde boşa döner | Başka program çalıştırır |
| Verim | Düşük (çoğu döngü boşuna) | Yüksek |
| Basitlik | Çok basit | Kesme mekanizması gerekir |
Bir insan saniyede belki 5 tuşa basar, işlemci o sürede milyarlarca komut çalıştırabilir. Polling bu zamanın neredeyse tamamını boşa harcar. Interrupt’ta işlemci kendi işini yapar, aygıt hazır olunca kısa bir servis rutini çalışır ve işlemci kaldığı yere döner.
TRAP: işletim sistemine bırak
Her programda bu polling döngülerini yazmak hem zahmetli hem tehlikeli (bir kullanıcı programının aygıt register’larıyla oynamasını istemezsin). LC-3’te bu iş işletim sistemindeki servis rutinlerine bırakılır. Kullanıcı programı TRAP komutuyla bunları çağırır.
| TRAP | Takma ad | Ne yapar? |
|---|---|---|
| x20 | GETC | Bir karakter oku, R0’a koy (ekrana yazmaz) |
| x21 | OUT | R0[7:0]‘daki karakteri ekrana yaz |
| x22 | PUTS | R0’daki adresten başlayan string’i x0000’a kadar yaz |
| x23 | IN | İstem yaz, bir karakter oku, ekrana da yaz, R0’a koy |
| x25 | HALT | Makineyi durdur |
TRAP x23 çalışınca:
- Dönüş adresi saklanır: R7 ← PC (zaten artmış PC).
- PC ← M[x0023]. x0000-x00FF arası trap vector table’dır; her satırda bir servis rutininin başlangıç adresi var.
- Servis rutini kendi polling döngüsüyle işini yapar.
- Sonunda
RET(=JMP R7) ile kullanıcı programına döner.
Çözümlü örnek: klavyeden bir rakam oku, iki katını yaz.
GETC ; R0 ← '3' = x33
LD R1, NEG30 ; R1 ← -48
ADD R2, R0, R1 ; R2 ← 3 (sayı)
ADD R0, R2, R2 ; R0 ← 6
LD R1, POS30 ; R1 ← 48
ADD R0, R0, R1 ; R0 ← '6'
OUT
HALT
NEG30 .FILL #-48
POS30 .FILL #48
Kullanıcı 3 yazarsa:
6
TUZAK · R7’yi ezmek. TRAP, dönüş adresini R7’ye yazar. Programında R7’de önemli bir değer tutuyorsan TRAP’ten sonra kaybolur. Önce başka yere kaydet.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Ready bit bit 0 | Bit 15 (işaret biti) |
| Hazırken değer pozitif | Bit 15 = 1 → negatif, N |
| Bekleme döngüsü BRn ile | Hazır olmadığı sürece dön: BRzp |
LD R1, xFE00 çalışır | offset9 o kadar uzağa ulaşmaz; LDI + .FILL |
| KBDR okumak KBSR’yi değiştirmez | Okuyunca KBSR[15] = 0 olur |
| DSR’ye bakmadan DDR’ye yaz | Ekran meşgulse karakter kaybolur |
| Polling işlemciyi serbest bırakır | İşlemci döngüde boşa döner |
| TRAP dönüş adresini yığına atar | R7’ye yazar |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is memory-mapped I/O?
Cevabı göster
Aygıt register'ları (KBSR, KBDR, DSR, DDR) bellek adreslerine yerleştirilir. İşlemci ayrı I/O komutları kullanmaz; aynı LD/ST ailesiyle (LDI, STI) bu adreslere erişir. Bellek arayüzü adres xFE00 ve üstüne gelince isteği belleğe değil aygıta yönlendirir.
Give the LC-3 memory-mapped addresses of KBSR, KBDR, DSR and DDR.
Cevabı göster
Klavye: status xFE00, data xFE02. Ekran: status xFE04, data xFE06. Status register'larında hazır bitini, data register'larında karakteri (bitler [7:0]) bulursun.
Which bit of KBSR tells the processor that a new character is available?
Cevabı göster
KBSR[15] ready bit'idir. Tuşa basılınca klavye karakteri KBDR'ye koyar ve KBSR[15] = 1 yapar. İşlemci KBDR'yi okuyunca bu bit otomatik olarak 0'lanır.
A polling loop is: POLL LDI R1, KBSRP followed by BRzp POLL. Why is BRzp the right branch?
Cevabı göster
LDI ile KBSR R1'e yüklenir ve condition code'lar ayarlanır. Bit 15 = 1 ise 16 bitlik değer 2's complement'te negatiftir (N). Hazır değilse değer 0 ya da pozitif (Z/P). BRzp, hazır olmadığı sürece başa döner; hazır olunca düşüp sonraki komuta geçer.
Why does the program use LDI R1, KBSRP (with KBSRP .FILL xFE00) instead of LD R1, xFE00?
Cevabı göster
LD'nin adresi PC + offset9, yani PC'nin -256..+255 yakınında. x3001'den xFE00'a uzaklık 52735, sığmaz. Bu yüzden adres yakındaki bir konuma .FILL ile yazılır ve LDI ile dolaylı (indirect) okunur.
When the keyboard is ready and KBSR = x8000, what condition code is set by LDI R1, KBSRP?
Cevabı göster
x8000 = 1000 0000 0000 0000, MSB 1 → negatif. Yüklenen değere göre N = 1, Z = P = 0. Hazır değilken KBSR = x0000 → Z.
Why must the program check DSR[15] before writing a character into DDR?
Cevabı göster
İşlemci ekrandan çok daha hızlıdır. DDR'ye yazılan karakter ekrana basılırken DSR[15] = 0 olur. Bitmeden yeni karakter yazarsan öncekinin üstüne yazar, karakter kaybolur. DSR[15] = 1 olunca yazmak güvenlidir.
What is the difference between polling and interrupt-driven I/O?
Cevabı göster
Polling'de işlemci bir döngüde ready bit'ini sürekli okur, bu sürede başka iş yapamaz (zaman kaybı). Interrupt-driven I/O'da işlemci kendi işine devam eder; aygıt hazır olunca işlemciyi keser, işlemci kısa bir servis rutini çalıştırıp kaldığı yere döner.
Is keyboard input synchronous or asynchronous? Why?
Cevabı göster
Kullanıcı tuşlara işlemcinin saatine göre değil, kendi hızında basar. Karakterin ne zaman geleceği bilinmez. Bu yüzden ready bit gibi bir el sıkışma (handshake) mekanizması gerekir.
The program below reads two characters and prints each one plus 1. If the user types HI, what is printed?
AND R2, R2, #0
ADD R2, R2, #2
AGAIN LDI R1, KBSRP
BRzp AGAIN
LDI R0, KBDRP
ADD R0, R0, #1
WAIT LDI R1, DSRP
BRzp WAIT
STI R0, DDRP
ADD R2, R2, #-1
BRp AGAIN
HALTCevabı göster
R2 sayaç (2). Her turda bir karakter okunur, ASCII kodu 1 artırılır, ekrana yazılır. 'H' = x48 → x49 = 'I'. 'I' = x49 → x4A = 'J'. R2 0 olunca BRp düşer, HALT.
In the program above, why is the counter decremented right before BRp AGAIN and not earlier?
Cevabı göster
Condition code'ları register'a yazan son komut belirler. ADD R2, R2, #-1 hemen önce olduğu için BRp, R2'nin yeni değerine bakar. Araya LDI R1 gibi bir yükleme girseydi BRp R1'e göre karar verirdi ve döngü bozulurdu.
A user program executes TRAP x23. Where does the processor find the starting address of the service routine?
Cevabı göster
TRAP komutu 8 bitlik trapvect'i 16 bite sıfırla genişletir ve o adresteki değeri PC'ye yükler: PC ← M[x0023]. x0000-x00FF arası trap vector table'dır. Ayrıca dönüş adresi R7'ye kaydedilir (R7 ← artmış PC).
Doğrulama: Polling programları ve TRAP örneği kendi LC-3 simülatörümüzde (klavye ve ekran register'ları xFE00-xFE06 adreslerine eşlenmiş olarak) çalıştırıldı; çıktılar, register değerleri ve condition code'lar oradan alındı (verify/SEN2201/06.mjs).