Mikroişlemciler · Ders Notu
Bölüm 2.2: 8086 Yazmaçlar
Bir assembly programının yaptığı işlerin çoğu, işlemcinin içindeki birkaç küçük bellek hücresinde olup biter. Bu hücrelere yazmaç denir. 8086'nın on dört yazmacı vardır ve her birinin adı bir görevi anlatır: AX akümülatördür, CX sayaçtır, SP yığının tepesini gösterir, IP sıradaki komutun yerini tutar. Not önce yazmacın ne olduğunu ve bellekten neden çok daha hızlı olduğunu açıklar. Ardından 8086'nın yazmaç kümesini gruplar hâlinde inceler: dört veri yazmacının 8 ve 16 bitlik kullanımı, komutların bu yazmaçları örtük olarak nasıl kullandığı, işaretçi ve indis yazmaçlarıyla yığına ve dizgilere erişim, kesim yazmaçları ve IP. Notun ikinci yarısı bayrak yazmacına ayrılmıştır. Altı durum bayrağının hangi sonuçta kurulduğu, ALU'nun bu bitleri donanımda nasıl ürettiği ve üç kontrol bayrağının işlemcinin çalışmasını nasıl değiştirdiği örneklerle gösterilir. Her kod parçası, yazmaçların, bayrakların ve belleğin her adımdaki değerleriyle satır satır izlenir.
Öğrenme çıktıları
- Yazmaç kümesini tanımak
- 8086'nın on dört yazmacını gruplarına göre saymak; hangilerinin 8 bitlik iki yarıya bölünebildiğini ve bir yarıya yazmanın öbür yarıyı neden değiştirmediğini açıklamak.
- Örtük görevleri bilmek
- MUL, DIV, LOOP, REP, IN, OUT, PUSH, POP ve dizgi komutlarının hangi yazmaçları komutta yazılmadan kullandığını söylemek.
- Yazmaç çiftlerini kullanmak
- CS:IP, SS:SP, SS:BP, DS:BX, DS:SI ve ES:DI çiftlerinin hangi erişimde kullanıldığını bilmek ve fiziksel adresi hesaplamak.
- Durum bayraklarını hesaplamak
- Bir toplama ya da çıkarmadan sonra CF, PF, AF, ZF, SF ve OF bayraklarını elle bulmak; CF ile OF arasındaki farkı işaretsiz ve işaretli yorumla açıklamak.
- Kontrol bayraklarını yönetmek
- DF, IF ve TF'nin işlemcinin davranışını nasıl değiştirdiğini açıklamak; CLD/STD, CLI/STI, LAHF/SAHF ve PUSHF/POPF ile bayrakları okumak ve yazmak.
- Kod izlemek
- Kısa bir 8086 programını yazmaçların, bayrakların, belleğin ve yığının her adımdaki durumunu tablo hâlinde yazarak izlemek.
2.2.1Yazmaç kavramı
2.2.1.1Yazmaç nedir?
Sayısal tasarım dersinde D tipi flip-flop'lar yan yana dizilip ortak bir saat sinyaline bağlandığında bir yazmaç elde edilir. Saatin yükselen kenarında girişteki bitler flip-flop'lara yüklenir ve bir sonraki yüklemeye kadar orada kalır. Mikroişlemcinin içindeki yazmaçlar da böyle kurulmuştur. Fark, sayılarında ve görevlerindedir: işlemci aynı anda birkaç düzine yazmacı kullanır ve her birine komutlar aracılığıyla erişir.
Yazmaç (register), işlemcinin içinde bulunan, çok hızlı ve küçük bir bellek hücresidir. 8086'nın yazmaçları 16 bit genişliğindedir. Programcının komutlarla doğrudan okuyup yazabildiği yazmaçların tümüne yazmaç kümesi (register set) denir; 8086'nın yazmaç kümesinde on dört yazmaç vardır.
İşlemcinin içinde programcının göremediği yazmaçlar da bulunur. Çözülmekte olan komutu tutan komut yazmacı, ALU'nun girişlerini besleyen geçici yazmaçlar ve önden getirme kuyruğu bunlardandır. Bu notta “yazmaç” denince programcının adıyla kullanabildiği on dört yazmaç anlaşılır.
Bir program çalışırken geçici veriler, bellek adresleri, döngü sayaçları ve işlem sonuçları yazmaçlarda tutulur. Şekil 2.2.1 işlemci ile bellek arasındaki ilişkiyi gösterir. Yazmaçlar işlemcinin içindedir ve ALU'ya kısa iç yollarla bağlıdır. Bellek ise işlemcinin dışında, adres ve veri yollarının öbür ucundadır.
2.2.1.2Yazmaç ve bellek: hız farkı
8086 dış bellekle yol çevrimleri (bus cycle) hâlinde konuşur. Bir yol çevrimi en az dört saat turundan (T1, T2, T3, T4) oluşur. T1'de adres yola konur, T2'de okuma ya da yazma sinyali verilir, T3'te veri yolda hazır olur, T4'te çevrim kapanır. Bellek yavaşsa araya bekleme turları da girer. 5 MHz'lik bir 8086'da bir saat turu 200 ns sürer; dolayısıyla tek bir bellek okuması en az 800 ns'dir.
Yazmaç erişimi ise dış yola hiç çıkmaz. Yazmacın içeriği aynı saat turu içinde ALU'ya ulaşır. Tablo 2.2.1, Intel'in 8086 kılavuzundaki saat turu sayılarından birkaç örnek verir. EA, etkin adresin hesaplanması için harcanan ek turdur. Değeri adres biçimine bağlıdır: [BX] gibi tek yazmaçlı bir adres için 5, [0500h] gibi doğrudan adres için 6, [BX+2] gibi yazmaç artı sabit için 9 turdur.
| Komut | İşlenenler | Saat turu | 5 MHz'de süre |
|---|---|---|---|
MOV AX, BX | yazmaç ← yazmaç | 2 | 0,4 µs |
MOV AX, [BX] | yazmaç ← bellek | 8 + 5 = 13 | 2,6 µs |
ADD AX, BX | yazmaç ← yazmaç + yazmaç | 3 | 0,6 µs |
ADD AX, [BX] | yazmaç ← yazmaç + bellek | 9 + 5 = 14 | 2,8 µs |
ADD [BX], AX | bellek ← bellek + yazmaç | 16 + 5 = 21 | 4,2 µs |
Tablo açık bir sonuç verir: aynı toplama, işlenenlerden biri bellekte olduğunda yaklaşık beş kat, sonuç belleğe yazıldığında yedi kat uzun sürer. ADD [BX], AX komutu belleği önce okur, sonra yazar; iki yol çevrimi harcar. Bu yüzden iyi yazılmış bir assembly programı sık kullanılan değerleri yazmaçlarda tutar ve belleğe yalnız gerektiğinde gider. Yüksek seviyeli dillerin derleyicileri de aynı ilkeyle çalışır: bir döngünün sayacını ve ara sonuçlarını mümkün olduğunca yazmaçlara yerleştirir.
Yazmaçların sayısı ise azdır. Her yazmaç işlemci yongasında yer kaplar. Komutun içinde de yazmacı seçen bir alan bulunur: 8086 komutlarında bu alan 3 bittir ve 23 = 8 yazmaçtan birini seçer. Yazmaç sayısını artırmak hem yongayı büyütür hem komutları uzatır. 1978'in teknolojisinde 8086'nın tasarımcıları sekiz genel amaçlı yazmaçla yetinmiştir.
2.2.1.3Bellekten belleğe işlem yoktur
8086'nın iki işlenenli komutlarında (MOV, ADD, SUB, AND, CMP …) işlenenlerden en az biri yazmaç ya da sabit olmalıdır. Bir bellek hücresini doğrudan başka bir bellek hücresine kopyalayan ya da iki bellek hücresini toplayan bir MOV veya ADD yoktur. Bu kural komut biçiminden gelir: bir komutun içinde yalnız bir bellek adresini tarif edecek yer vardır.
x DW 1
y DW 2
MOV x, y ; HATA: bellekten belleğe MOV yok
MOV AX, y ; doğrusu: önce yazmaca
MOV x, AX ; sonra belleğeAssembler ilk satırı kabul etmez ve bir hata iletisi verir. Doğru yol, değeri bir yazmaç üzerinden taşımaktır. Yazmaç burada bir aktarma istasyonu gibi çalışır. Bu kuralın tek istisnası dizgi komutlarıdır. MOVSB bir baytı DS:SI'dan ES:DI'ya doğrudan taşır. İki adresi de komutta yazmadan, örtük olarak SI ve DI yazmaçlarından alır.
Değişkenleri yüksek seviyeli dildeki gibi düşünüp MOV toplam, sayi ya da ADD sonuc, deger yazmak. İki işlenen de bellekte olduğu için assembler bu satırları kabul etmez. Değerlerden biri önce bir yazmaca alınır.
2.2.28086 yazmaç kümesi
2.2.2.1On dört yazmaç ve grupları
8086'nın on dört yazmacı görevlerine göre dört grupta toplanır (Şekil 2.2.2):
- Veri yazmaçları: AX, BX, CX, DX. Her biri 16 bittir ve 8 bitlik iki yarıya bölünebilir: AH/AL, BH/BL, CH/CL, DH/DL.
- İşaretçi ve indis yazmaçları: SP (yığın işaretçisi), BP (taban işaretçisi), SI (kaynak indisi), DI (hedef indisi). Bunlar bellek içinde bir konumu gösterir; 8 bitlik parçaları yoktur.
- Kesim yazmaçları: CS (kod), DS (veri), SS (yığın), ES (ek). Her biri bellekteki bir kesimin başlangıcını belirler.
- Özel yazmaçlar: IP (komut işaretçisi) ve FLAGS (bayrak yazmacı).
Sayım şöyledir: 4 veri + 4 işaretçi/indis + 4 kesim + IP + FLAGS = 14. Bu derste ilk sekiz yazmaç, yani AX, BX, CX, DX, SP, BP, SI ve DI, genel amaçlı yazmaçlar olarak sayılır. Bu sekizinin ortak özelliği, MOV, ADD, AND gibi komutlarda işlenen olarak serbestçe kullanılabilmeleridir. ADD SI, 4 ya da MOV BP, AX geçerli komutlardır. Kesim yazmaçları, IP ve FLAGS ise aritmetik komutlarda işlenen olamaz.
Yazmaç adlarındaki X harfi “genişletilmiş” anlamında kullanılmıştır. 8086'dan önceki 8080 işlemcisinin A, B, C, D gibi 8 bitlik yazmaçları vardı. 8086 bunları 16 bite genişletti ve AX, BX, CX, DX adlarını verdi; 8 bitlik yarılar H (yüksek) ve L (düşük) harfleriyle anıldı. Bu adlandırma 8080 programlarının 8086'ya kolay aktarılması için seçilmişti. Bugünkü x86 işlemcilerinde de sürer: 32 bitlik işlemcilerde EAX, 64 bitlik işlemcilerde RAX yazmacının en alt 16 biti hâlâ AX'tir.
2.2.2.2Yazmaçların BIU ve EU'daki yeri
8086 iki birimden oluşur: belleğe ve G/Ç'ye erişen yol arayüz birimi (BIU) ile komutları çözüp yürüten yürütme birimi (EU). On dört yazmaç bu iki birime paylaştırılmıştır ve bu paylaşım, yazmaçların görevlerini anlamayı kolaylaştırır.
| Birim | Yazmaçlar | Adres üretimindeki rolü |
|---|---|---|
| EU | AX, BX, CX, DX | veri; BX ayrıca bağıl konum tutabilir |
| EU | SP, BP, SI, DI | 16 bitlik bağıl konum |
| EU | FLAGS | yok; ALU sonucunu ve kontrol bitlerini tutar |
| BIU | CS, DS, SS, ES | kesim başlangıcı (paragraf numarası) |
| BIU | IP | kod kesimi içindeki bağıl konum |
EU yalnız 16 bitlik sayılarla çalışır. Bir bellek işleneni gerektiğinde etkin adresi, yani kesim içindeki bağıl konumu, BX, BP, SI, DI ve komuttaki sabitten hesaplar ve BIU'ya verir. 20 bitlik fiziksel adresi kesim yazmacıyla birleştiren BIU'dur. Kesim yazmaçlarının ve IP'nin BIU'da durmasının nedeni budur: BIU, EU'dan bağımsız olarak sıradaki komut baytlarını CS:IP'den önceden getirir ve kuyruğa koyar. Bu düzen, “hangi yazmaç adresin hangi parçasını verir?” sorusunun cevabını da belirler. Kesim yazmacı adresin üst kısmını, işaretçi ve indis yazmaçları kesim içindeki konumu verir.
2.2.2.3Genel amaçlı yazmaçlar ve örtük görevler
Genel amaçlı yazmaçlar adlarından anlaşılacağı gibi çoğu komutta birbirinin yerine kullanılabilir. ADD AX, BX yerine ADD SI, DX yazmak mümkündür. Bununla birlikte bazı komutlar belirli bir yazmacı, komutta adı geçmeden kullanır.
Örtük işlenen (implicit operand), komutun yazılışında görünmeyen ama komutun tanımı gereği okunan ya da yazılan işlenendir. MUL BX komutunda yalnız BX yazılır; çarpılan AX ve sonucun yüksek yarısını alan DX örtük işlenenlerdir.
Tablo 2.2.3 sekiz genel amaçlı yazmacın adlarını ve örtük görevlerini bir arada verir. Yazmaçların İngilizce adları görevlerinin kısaltmasıdır ve tabloyu akılda tutmayı kolaylaştırır.
| Yazmaç | Ad | 8 bitlik yarılar | Örtük kullanım (örnek) |
|---|---|---|---|
AX | akümülatör (accumulator) | AH, AL | MUL, DIV, IN, OUT, LODS/STOS |
BX | taban (base) | BH, BL | bellek adresleme [BX], XLAT |
CX | sayaç (counter) | CH, CL | LOOP, REP, kaydırma sayısı CL |
DX | veri (data) | DH, DL | MUL/DIV yüksek yarısı, port adresi |
SP | yığın işaretçisi (stack pointer) | — | PUSH, POP, CALL, RET |
BP | taban işaretçisi (base pointer) | — | yığın çerçevesi [BP+n] |
SI | kaynak indisi (source index) | — | dizgi kaynağı DS:SI |
DI | hedef indisi (destination index) | — | dizgi hedefi ES:DI |
Örtük görevler 8086'nın komut kodlamasını kısaltmak için seçilmiştir. LOOP komutu sayacın hangi yazmaçta olduğunu belirtmek zorunda kalsaydı, her LOOP'a bir yazmaç alanı eklenmesi gerekirdi. Sayaç her zaman CX olduğu için LOOP yalnız iki bayttır: işlem kodu ve atlama uzaklığı. Bedeli esnekliktir. Bir programda CX başka bir iş için kullanılıyorsa LOOP'tan önce saklanmalıdır. Örtük görevleri bilmeyen bir programcı, bir MUL komutunun DX'teki değeri sessizce sildiğini fark etmeyebilir.
2.2.2.48 ve 16 bitlik kullanım: AX = AH : AL
AX, BX, CX ve DX hem 16 bitlik tek bir yazmaç hem de 8 bitlik iki ayrı yazmaç olarak kullanılabilir. AX'in yüksek sıradaki 8 biti (bit 15–8) AH, düşük sıradaki 8 biti (bit 7–0) AL adını taşır. Bunlar ayrı donanım değildir; aynı 16 flip-flop'un iki yarısıdır. Bu yüzden şu eşitlik her an geçerlidir:
AX = AH × 256 + AL (onaltılık: AX = AH : AL)
Bir komut AL'ye yazdığında yalnız alt 8 flip-flop yüklenir; üst yarı yerinde kalır. Bu davranışı görmenin en iyi yolu kısa bir programı izlemektir. Kod, sunumdaki programın aynısıdır:
MOV AX, 1234h ; AH=12h, AL=34h
MOV AL, 0FFh ; yalnız alt yarı
ADD AL, 1 ; 8 bitlik toplama
MOV AX, 12FFh
ADD AX, 1 ; 16 bitlik toplama| Satır | Komut | AX | AH | AL | CF | ZF |
|---|---|---|---|---|---|---|
| 1 | MOV AX, 1234h | 1234h | 12h | 34h | 0 | 0 |
| 2 | MOV AL, 0FFh | 12FFh | 12h | FFh | 0 | 0 |
| 3 | ADD AL, 1 | 1200h | 12h | 00h | 1 | 1 |
| 4 | MOV AX, 12FFh | 12FFh | 12h | FFh | 1 | 1 |
| 5 | ADD AX, 1 | 1300h | 13h | 00h | 0 | 0 |
Birinci satır AX'e 1234h yazar. Onaltılık gösterimde her iki basamak bir bayttır; bu yüzden AH = 12h, AL = 34h olur. İkinci satır yalnız AL'yi değiştirir: AL = FFh, AH = 12h kalır, AX = 12FFh olur. Üçüncü satır en öğretici olanıdır. ADD AL, 1 8 bitlik bir toplamadır: FFh + 01h = 100h. Sonuç 9 bit tutar ama hedef 8 bittir. AL'ye alt 8 bit, yani 00h yazılır. Taşan 1 AH'ye eklenmez; elde bayrağına (CF) gider. Sonuç sıfır olduğu için sıfır bayrağı da kurulur (ZF = 1). AX artık 1200h'dir (Şekil 2.2.3).
ADD AL, 1 sonrasında AX. AL = FFh + 1 = 00h olur; elde AH'ye geçmez, CF'ye gider. AH = 12h yerinde kalır.Dördüncü satır AX'i yeniden 12FFh yapar. MOV bayrakları değiştirmediği için CF = 1 ve ZF = 1 korunur. Beşinci satır aynı “+1” işlemini bu kez 16 bit üzerinde yapar: 12FFh + 0001h = 1300h. Alt bayttan çıkan elde bu sefer AH'ye geçer ve 12h, 13h olur. 16 bitlik sonuç 16 bite sığdığı için CF = 0 olur. Sonuç sıfır olmadığından ZF de 0'dır.
Bu iki toplamanın farkı, ALU'nun hangi genişlikte çalıştığından gelir. İşlemci işlem genişliğini işlenenlerin adından anlar: AL 8 bitlik, AX 16 bitlik bir yazmaçtır. Komut kodunda bu bilgi tek bir bitle, W bitiyle taşınır: W = 0 bayt işlemi, W = 1 sözcük işlemi demektir. ADD AL, 1 komutu 04 01, ADD AX, 1 komutu 05 01 00 olarak kodlanır; işlem kodları yalnız en düşük bitte ayrılır.
BX = 12FFh iken INC BL çalışırsa BX kaç olur?
INC 8 bitlik BL üzerinde çalışır: FFh + 1 = 100h, BL'ye 00h yazılır. Elde BH'ye geçmez; BH = 12h kalır ve BX = 1200h olur. ZF = 1'dir. INC komutu elde bayrağını hiç değiştirmez, CF önceki değerini korur. CF önceden 1 yapılmışsa INC'ten sonra da 1 kalır.
8 bitlik yarılar bellekteki düşük bayt önce düzeniyle de ilişkilidir. MOV [0300h], AX komutu AX = 1234h iken çalışırsa 0300h adresine AL'deki 34h, 0301h adresine AH'deki 12h yazılır. Düşük bayt düşük adrese gider. Bellekten okurken de aynı kural geçerlidir: MOV AX, [0300h] AL'yi 0300h'den, AH'yi 0301h'den doldurur.
AL'ye yazıldığında AX'in tamamının değiştiğini sanmak. MOV AL, 5 AH'ye dokunmaz. AX daha önce 1234h idiyse komuttan sonra 1205h olur, 0005h olmaz. AX'in bütünüyle 5 olması isteniyorsa MOV AX, 5 yazılır.
Yalnız dört veri yazmacının yarıları vardır. MOV SL, 5 ya da MOV BPL, 0 gibi bir yazım 8086'da yoktur. SI'nın alt baytına erişmek isteyen bir program SI'yı önce AX'e kopyalar ve AL üzerinde çalışır.
2.2.3Veri yazmaçları
2.2.3.1AX: akümülatör
AX, 8086'nın en çok kullanılan yazmacıdır. Adını eski işlemcilerdeki akümülatörden (accumulator) alır. Akümülatörlü bir işlemcide her aritmetik işlem bir işleneni akümülatörden alır ve sonucu yine oraya yazar; sonuçlar orada “birikir”. 8086 bu zorunluluğu kaldırmıştır: ADD BX, CX akümülatöre dokunmadan çalışır. Yine de birçok komut AX'i ya da AL'yi örtük olarak kullanır:
MUL,IMUL,DIV,IDIV: çarpılan ve bölünen AX'te (ya da AL'de, DX:AX'te) durur, sonuç AX'e yazılır.INveOUT: porttan okunan veri AL'ye ya da AX'e gelir; porta yazılacak veri AL'de ya da AX'te bulunur.CBW: AL'deki işaretli baytı AX'e genişletir.DAAveDAS: AL'deki BCD sonucunu düzeltir.LODSBbaytı AL'ye yükler,STOSBAL'deki baytı belleğe yazar.
AX'in ayrıcalığı komut uzunluğunda da görülür. 8086'nın birçok komutunun akümülatöre özel kısa bir biçimi vardır. Tablo 2.2.5 assembler'ın ürettiği makine kodlarını yan yana koyar.
| Komut | Makine kodu | Uzunluk |
|---|---|---|
ADD AX, 1234h | 05 34 12 | 3 bayt |
ADD BX, 1234h | 81 C3 34 12 | 4 bayt |
MOV AX, [0300h] | A1 00 03 | 3 bayt |
MOV BX, [0300h] | 8B 1E 00 03 | 4 bayt |
Genel biçimde işlem kodundan sonra işlenenleri tarif eden bir bayt gelir (ModR/M baytı: C3, 1E). Akümülatör biçiminde hedefin AX olduğu işlem kodunun içinde saklıdır; bu bayta gerek kalmaz. Bir bayt az, hem bellekte yer hem de önden getirme kuyruğunda bir yol çevrimi kazancıdır. Bu yüzden derleyiciler ve deneyimli programcılar sık yapılan hesapları AX üzerinde kurar.
Sunumdaki kısa örnek AX'i kendisiyle toplar:
ADD AX, AX ; AX = AX + AX
MOV AX, 0005h
ADD AX, AX ; AX = 000Ah
MOV AH, 4Ch ; DOS: programı bitir
INT 21h| Komut | AX | Açıklama |
|---|---|---|
ADD AX, AX | 0000h | 0 + 0 = 0; ZF = 1, PF = 1 |
MOV AX, 0005h | 0005h | AX'e 5 yazılır |
ADD AX, AX | 000Ah | 5 + 5 = 10 = 000Ah; ZF = 0 |
MOV AH, 4Ch | 4C0Ah | yalnız AH değişir; AL = 0Ah kalır |
INT 21h | 4C0Ah | DOS 4Ch hizmeti: program biter, çıkış kodu AL = 10 |
Bir değeri kendisiyle toplamak 2 ile çarpmaktır. İkili sayıda bu, bütün bitlerin bir sola kaymasıdır: 0101b + 0101b = 1010b. Son iki satır da ilginçtir. DOS'un 4Ch hizmeti AH'de hizmet numarasını, AL'de programın çıkış kodunu bekler. MOV AH, 4Ch AL'ye dokunmadığı için program 10 çıkış koduyla biter. Çıkış kodunun 0 olması isteniyorsa MOV AX, 4C00h yazılır; bu tek komut hem AH'yi hem AL'yi doldurur.
2.2.3.2BX: taban yazmacı
BX de AX gibi 16 bittir ve BH ile BL olarak ikiye ayrılır. Onu öbür veri yazmaçlarından ayıran özellik, bellek adreslemede kullanılabilmesidir. Köşeli parantez içindeki bir yazmaç, yazmacın değerinin bir bağıl konum olarak kullanılacağını söyler. [BX], “BX'in gösterdiği bellek hücresi” demektir.
Taban yazmacı (base register), dolaylı adreslemede adresin taban kısmını tutan yazmaçtır. 8086'da taban yazmaçları BX ve BP'dir. Köşeli parantez içinde kullanılabilen yazmaçlar yalnız BX, BP, SI ve DI'dır; [AX], [CX], [DX] yazılamaz.
Bu kısıt da komut kodlamasından gelir. 8086'nın adresleme alanı (R/M alanı) 3 bitlik bir kodla sekiz adres biçiminden birini seçer: [BX+SI], [BX+DI], [BP+SI], [BP+DI], [SI], [DI], [BP] (ya da doğrudan adres) ve [BX]. Listede AX, CX ve DX yoktur. MOV AX, [CX] satırı bu yüzden assembler'dan hata alır. CX'teki bir adresi kullanmak gerekiyorsa önce BX'e kopyalanır: MOV BX, CX ve MOV AX, [BX].
Köşeli parantezin anlamı yeni başlayanların en sık karıştırdığı noktadır. Şekil 2.2.4 farkı gösterir.
MOV BL, [0500h] komutu DS:0500h adresindeki 25h baytını BL'ye kopyalar. BL'ye 500h sayısı yazılmaz; köşeli parantez “bu adresteki değer” anlamına gelir.MOV BL, [0500h] bellekten okur: DS:0500h adresinde 25h varsa BL = 25h olur. MOV BX, 0500h ise belleğe hiç gitmez; BX'e 0500h sayısını yazar. Birinci komut 4 bayttır ve bir yol çevrimi harcar. İkincisi 3 bayttır ve sayı komutun içindedir. Bu ayrım, sabitle bellek içeriği arasındaki temel ayrımdır. Bir sayıyı yazmaca koymak için köşeli parantez kullanılmaz.
Sunumdaki izleme programı BX'i bir işaretçi olarak kullanır. Bellekte 0500h–0503h adreslerinde 25h, 17h, 00h ve 9Ch baytları bulunsun.
MOV BX, 0500h ; BX = adres
MOV AL, [BX] ; AL = [0500h]
MOV DL, [0501h] ; doğrudan adres
ADD AL, DL ; AL = 25h + 17h
MOV [BX+2], AL ; [0502h] = AL| Satır | Komut | BX | AL | DL | 0500h–0503h |
|---|---|---|---|---|---|
| 1 | MOV BX, 0500h | 0500h | 00h | 00h | 25 17 00 9C |
| 2 | MOV AL, [BX] | 0500h | 25h | 00h | 25 17 00 9C |
| 3 | MOV DL, [0501h] | 0500h | 25h | 17h | 25 17 00 9C |
| 4 | ADD AL, DL | 0500h | 3Ch | 17h | 25 17 00 9C |
| 5 | MOV [BX+2], AL | 0500h | 3Ch | 17h | 25 17 3C 9C |
Birinci satır BX'e bir sayı yazar; bu sayı bundan sonra bir adres olarak kullanılacaktır. İkinci satırda [BX], BX'in gösterdiği bayttır: AL = 25h. Üçüncü satır aynı türden bir okumayı adresi komutun içine yazarak yapar ve DL = 17h olur. Dördüncü satır iki yazmacı toplar: 25h + 17h = 3Ch. Beşinci satırda [BX+2] etkin adresi 0500h + 2 = 0502h'dir; AL bu adrese yazılır. BX değişmez, değişen bellektir (Şekil 2.2.5).
[BX+2] ile 0502h adresine 3Ch yazılmıştır.Programdaki adresler bağıl konumdur. İşlemci bunları DS kesimi içinde arar, çünkü BX ile yapılan erişimlerin varsayılan kesimi DS'dir. emu8086 bir .COM programını DS = 0700h ile yükler. Bu durumda 0500h bağıl konumunun fiziksel adresi 0700h × 10h + 0500h = 07000h + 0500h = 07500h'dir. Beşinci satırda yazılan bayt emu8086'nın bellek penceresinde 07502h fiziksel adresinde görünür.
BX'in adres tutabilmesi onu dizi ve tablo işlemlerinde vazgeçilmez yapar. BX bir dizinin başını gösterirken [BX+SI] biçimi dizinin SI'ncı elemanına erişir; SI her turda artırılarak dizi gezilir. XLAT komutu da BX'i örtük kullanır: AL'yi BX'in gösterdiği tablonun indisi sayar ve AL ← [BX+AL] işini tek komutta yapar.
2.2.3.3CX: sayaç yazmacı
CX, CH ve CL olarak ikiye ayrılan 16 bitlik bir yazmaçtır. Döngü, tekrar ve sayma işlerinde kullanılır; adı da buradan gelir (count register). Üç komut CX'i örtük olarak kullanır:
LOOP etiket: önce CX ← CX − 1 yapar, sonra CX ≠ 0 ise etikete atlar. Bayrakları etkilemez.REPöneki: arkasındaki dizgi komutunu CX kez tekrarlar; her turda CX bir azalır.- Kaydırma ve döndürme komutları: sayı 1'den büyükse CL'de verilir:
SHL AX, CL.
LOOP etiket komutu şu iki adımı tek komutta yapar: (1) CX ← CX − 1; (2) CX ≠ 0 ise IP ← etiketin adresi, değilse bir sonraki komuta geçilir. Azaltma, sınamadan önce yapılır.
Sunumdaki program 5 + 4 + 3 + 2 + 1 toplamını hesaplar. CX hem sayaç hem de toplanan değerdir.
MOV CX, 5 ; sayaç
MOV AX, 0 ; toplam
TEKRAR: ADD AX, CX
LOOP TEKRAR ; CX--, ≠0 ise atla
MOV DX, AX ; sonuç| Adım | Komut | CX | AX | DX | Sonraki |
|---|---|---|---|---|---|
| 1 | MOV CX, 5 | 0005h | 0000h | 0000h | 2. satır |
| 2 | MOV AX, 0 | 0005h | 0000h | 0000h | TEKRAR |
| 3 | ADD AX, CX | 0005h | 0005h | 0000h | LOOP |
| 4 | LOOP TEKRAR | 0004h | 0005h | 0000h | atla |
| 5 | ADD AX, CX | 0004h | 0009h | 0000h | LOOP |
| 6 | LOOP TEKRAR | 0003h | 0009h | 0000h | atla |
| 7 | ADD AX, CX | 0003h | 000Ch | 0000h | LOOP |
| 8 | LOOP TEKRAR | 0002h | 000Ch | 0000h | atla |
| 9 | ADD AX, CX | 0002h | 000Eh | 0000h | LOOP |
| 10 | LOOP TEKRAR | 0001h | 000Eh | 0000h | atla |
| 11 | ADD AX, CX | 0001h | 000Fh | 0000h | LOOP |
| 12 | LOOP TEKRAR | 0000h | 000Fh | 0000h | atlamaz |
| 13 | MOV DX, AX | 0000h | 000Fh | 000Fh | — |
Döngü gövdesi CX = 5, 4, 3, 2, 1 değerleriyle beş kez çalışır. Beşinci LOOP CX'i 1'den 0'a indirir ve atlamaz; program döngüden çıkar. Gövde CX = 0 değeriyle hiç çalışmaz. Toplam 15 = 000Fh'dir. Gauss'un formülüyle sağlama: 5 × 6 / 2 = 15.
Döngünün süresi de kolayca hesaplanır. LOOP atladığında 17, atlamadığında 5 saat turu sürer; ADD AX, CX 3 turdur. Beş tur için 5 × 3 + 4 × 17 + 5 = 88 tur harcanır. Bu değer, gövde uzadıkça döngü denetiminin toplam süreye oranının nasıl küçüldüğünü de gösterir.
CX = 0 tuzağı
LOOP sınamadan önce azalttığı için CX = 0 ile döngüye girmek beklenmedik bir sonuç verir. İlk LOOP, 0000h − 1 = FFFFh hesaplar. FFFFh sıfır değildir; işlemci atlar. Döngü bundan sonra FFFFh, FFFEh, …, 0001h değerleriyle döner ve toplam tur sayısı 65 536 olur.
MOV CX, 0
MOV BX, 0
MOV DX, 0
D: ADD BX, 1 ; tur sayacı (alt sözcük)
ADC DX, 0 ; üst sözcük
LOOP DBu program tur sayısını 32 bitlik DX:BX çiftinde sayar. Program bittiğinde DX:BX = 0001:0000h = 65 536 olur. Tur sayısının 16 bite sığmadığına dikkat edilmelidir; sayaç yalnız BX olsaydı 65 536 tur sonunda yeniden 0'a dönerdi ve döngünün hiç çalışmadığı sanılırdı.
Bu tuzaktan korunmanın yolu JCXZ komutudur: “CX sıfırsa atla”. Döngüden hemen önce konur ve sayaç sıfırsa döngünün tamamını atlar.
MOV CX, 0
MOV AX, 0
JCXZ BOS ; CX = 0 ise döngüye hiç girme
D: ADD AX, CX
LOOP D
BOS: MOV DX, AXBu programda JCXZ'den doğrudan BOS etiketine geçilir; döngü gövdesi bir kez bile çalışmaz ve AX = DX = 0 kalır. JCXZ de LOOP gibi bayraklara bakmaz, doğrudan CX'i sınar.
CL ile kaydırma
8086'da kaydırma ve döndürme komutları sayıyı ya 1 olarak ya da CL'de alır. SHL AX, 4 yazımı 8086'da yoktur; 80186 ile gelmiştir. 8086 için aynı iş şöyle yazılır:
MOV AX, 0003h
MOV CL, 4
SHL AX, CL ; AX = 0030hDört bitlik sola kaydırma 24 = 16 ile çarpmaktır: 3 × 16 = 48 = 0030h. Onaltılık gösterimde bu, sayının sonuna bir 0 eklemek demektir. Aynı işlem kesim yazmacını fiziksel adrese çevirirken de karşımıza çıkar.
2.2.3.4DX: veri yazmacı
DX, DH ve DL olarak ikiye ayrılır. İki önemli örtük görevi vardır: geniş çarpma ve bölmede AX'in yüksek yarısını tutmak ve G/Ç port numarasını taşımak.
Çarpma. İki 16 bitlik sayının çarpımı 32 bite kadar çıkabilir: FFFFh × FFFFh = FFFE0001h. Sonuç tek bir 16 bitlik yazmaca sığmaz. 8086 bu yüzden 16 bitlik çarpmanın sonucunu iki yazmaca böler: yüksek 16 bit DX'e, düşük 16 bit AX'e yazılır. Bu çift DX:AX diye gösterilir (Şekil 2.2.6). 8 bitlik çarpmada ise sonuç 16 bittir ve tamamı AX'e sığar: MUL BL → AX = AL × BL. Bu durumda DX değişmez.
MUL BX: AX ile BX çarpılır, 32 bitlik sonuç DX:AX çiftine yazılır. DX yüksek, AX düşük yarıdır.Bölme. 16 bitlik bölmede bölünen 32 bitlik DX:AX'tir. Bölüm AX'e, kalan DX'e yazılır. Örneğin DX = 0012h, AX = 3405h, BX = 0100h iken DIV BX, 00123405h ÷ 0100h hesaplar. Bölüm 1234h, kalan 0005h'tir: AX = 1234h, DX = 0005h olur. Bölüm 16 bite sığmazsa işlemci tip 0 kesmesi (bölme hatası) üretir. Bu yüzden 16 bitlik bir sayıyı bölmeden önce DX sıfırlanır; DX'te artık bir değer kalmışsa bölünen beklenenden çok büyük olur.
Port numarası. IN ve OUT komutları port numarasını iki yolla alır. 0–255 arasındaki portlar komutun içine sabit olarak yazılabilir: IN AL, 60h. 255'ten büyük portlar için numara DX'e konur: OUT DX, AL. PC'de birinci seri port (COM1) 03F8h'den, yazıcı portu 0378h'den başlar; bunlara yalnız DX ile erişilir. DX'in 16 bit olması 65 536 portluk G/Ç adres alanını tümüyle kapsar.
DOS çağrıları. INT 21h hizmetlerinin birçoğu bir bellek adresini DX'te bekler. Ekrana dizgi yazan 09h hizmeti dizginin adresini DS:DX'ten alır: LEA DX, mesaj.
Sunumdaki program bu görevlerden ikisini birlikte gösterir:
MOV AX, 1234h
MOV BX, 0100h
MUL BX ; DX:AX = AX*BX
MOV AX, 00FFh
MOV BL, 2
MUL BL ; AX = AL*BL
MOV DX, 03F8h ; port adresi
OUT DX, AL ; porta yaz| Satır | Komut | AX | BX | DX | CF | OF | SF ZF AF PF |
|---|---|---|---|---|---|---|---|
| 1 | MOV AX, 1234h | 1234h | 0000h | 0000h | 0 | 0 | 0 0 0 0 |
| 2 | MOV BX, 0100h | 1234h | 0100h | 0000h | 0 | 0 | 0 0 0 0 |
| 3 | MUL BX | 3400h | 0100h | 0012h | 1 | 1 | ? ? ? ? |
| 4 | MOV AX, 00FFh | 00FFh | 0100h | 0012h | 1 | 1 | ? ? ? ? |
| 5 | MOV BL, 2 | 00FFh | 0102h | 0012h | 1 | 1 | ? ? ? ? |
| 6 | MUL BL | 01FEh | 0102h | 0012h | 1 | 1 | ? ? ? ? |
| 7 | MOV DX, 03F8h | 01FEh | 0102h | 03F8h | 1 | 1 | ? ? ? ? |
| 8 | OUT DX, AL | 01FEh | 0102h | 03F8h | 1 | 1 | ? ? ? ? |
Üçüncü satırda 1234h × 0100h = 00123400h hesaplanır. 0100h ile çarpmak onaltılık sayıyı iki basamak sola kaydırmaktır. 32 bitlik sonuç ikiye bölünür: DX = 0012h, AX = 3400h. MUL, sonucun yüksek yarısı sıfır değilse CF = OF = 1 yapar. Bu iki bayrak “sonuç alt yarıya sığmadı, DX'e de bakılmalı” anlamındadır. MUL'den sonra SF, ZF, AF ve PF tanımsızdır: Intel bu bayrakların değerini belirtmemiştir ve program onlara güvenmemelidir. Tablodaki “?” işareti bunu gösterir.
Beşinci satır yalnız BL'yi değiştirir. BX önceden 0100h olduğundan BH = 01h kalır ve BX = 0102h olur. Altıncı satır 8 bitlik çarpmadır: AL × BL = FFh × 02h = 255 × 2 = 510 = 01FEh. Sonuç AX'in tamamına yazılır; DX'teki 0012h olduğu gibi durur. Yüksek yarı AH = 01h sıfır olmadığı için yine CF = OF = 1'dir.
Yedinci satırda DX bu kez bir veri değil, bir port numarası tutar: 03F8h, birinci seri portun veri yazmacıdır. Sekizinci satır AL'deki FEh'yi bu porta gönderir. OUT, AX'in yalnız alt baytını kullanır; AH'deki 01h porta gitmez. Porta tek bir bayt, FEh yazılır.
MUL'ün sonucunun yalnız AX'te olduğunu sanıp DX'i başka bir iş için kullanmaya devam etmek. 16 bitlik bir MUL, DX'teki değeri her zaman siler; sonuç küçük olsa bile DX = 0 yazılır. DX'te saklanması gereken bir değer varsa MUL'den önce yığına itilir.
AL = 10h ve BL = 03h iken MUL BL sonrasında AX, CF ve OF nedir?
10h × 03h = 16 × 3 = 48 = 0030h. AX = 0030h olur. Yüksek yarı AH = 00h olduğu için sonuç AL'ye sığmıştır ve CF = OF = 0'dır. Program bu durumda yalnız AL'ye bakarak devam edebilir. CF = 1 olsaydı sonucun AX'in tamamında aranması gerekirdi.
2.2.4İşaretçi ve indis yazmaçları
SP, BP, SI ve DI yazmaçları 16 bittir ve 8 bitlik yarıları yoktur. Dördü de çoğunlukla bir veri değil, bir bağıl konum tutar: bellekteki bir hücrenin, ait olduğu kesimin başından itibaren uzaklığı. SP ve BP yığın kesiminde (SS), SI ve DI ise veri kesimlerinde (DS, ES) çalışır. Aritmetik komutlarda da kullanılabilirler: ADD SI, 2 bir diziyi gezerken indisi ilerletmenin olağan yoludur.
2.2.4.1SP: yığın işaretçisi
Yığın (stack), son giren ilk çıkar (LIFO) düzeninde çalışan bir bellek bölgesidir. Yığın işaretçisi (SP, stack pointer), yığının en üstündeki öğenin bağıl konumunu tutar. Yığının fiziksel adresi her zaman SS:SP çiftiyle verilir.
8086'nın yığını üç özelliğiyle tanınır. Birincisi, öğeler her zaman 16 bittir: PUSH AX geçerlidir, PUSH AL değildir. Assembler PUSH AL satırını hatalı sayar. İkincisi, yığın belleğin aşağısına doğru büyür; her PUSH daha düşük bir adrese yazar. Üçüncüsü, işlem sırası sabittir:
PUSH kaynak: önce SP ← SP − 2, sonra [SS:SP] ← kaynak. Düşük bayt SP'ye, yüksek bayt SP + 1'e yazılır.POP hedef: önce hedef ← [SS:SP], sonra SP ← SP + 2.
SP her zaman yığındaki son geçerli öğeyi gösterir. Yığına yeni bir öğe konacağı zaman önce yer açılır, sonra yazılır. POP, okuduğu hücreyi silmez; değer bellekte durur ama SP onun üstüne çıktığı için artık yığının parçası sayılmaz. Bir sonraki PUSH o hücrenin üzerine yazar.
Aşağıdaki programı izlemek bu kuralları somutlaştırır. Yığının boş olduğu ve SP'nin 0000h'den başladığı varsayılsın.
MOV AX, 1111h
PUSH AX
MOV BX, 2222h
PUSH BX
POP CX
POP DX| Komut | SP | Yığın (adres: değer) | CX | DX |
|---|---|---|---|---|
MOV AX, 1111h | 0000h | boş | 0000h | 0000h |
PUSH AX | FFFEh | FFFEh: 1111h | 0000h | 0000h |
MOV BX, 2222h | FFFEh | FFFEh: 1111h | 0000h | 0000h |
PUSH BX | FFFCh | FFFCh: 2222h, FFFEh: 1111h | 0000h | 0000h |
POP CX | FFFEh | FFFEh: 1111h | 2222h | 0000h |
POP DX | 0000h | boş | 2222h | 1111h |
İlk PUSH, SP'yi 0000h − 2 hesabıyla FFFEh yapar. 16 bitlik aritmetikte 0'dan aşağı inmek en üste sarmak demektir; bu yüzden SP = 0000h, 64 KB'lık yığın kesiminin tamamının kullanılabildiği bir başlangıçtır. 1111h değeri FFFEh–FFFFh baytlarına yazılır. İkinci PUSH, SP'yi FFFCh'ye indirir ve 2222h'yi oraya koyar. Şekil 2.2.7 bu andaki yığını gösterir. SP en üstteki öğeyi, yani en son itilen 2222h'yi gösterir.
POP'lar ters sırada çalışır. POP CX en üstteki 2222h'yi alır, SP FFFEh'ye çıkar. POP DX 1111h'yi alır, SP yeniden 0000h olur. Sonuçta CX = 2222h ve DX = 1111h'dir: değerler itildikleri sıranın tersiyle geri gelmiştir. LIFO düzeninin anlamı budur. Bir programda AX ve BX'i saklayıp geri almak için PUSH AX, PUSH BX ve ardından POP BX, POP AX yazılır; POP sırası PUSH sırasının tersidir.
“Yığın boşken SP = FFFEh” bilgisi genel bir kural değildir. DOS bir .COM programını yüklerken SP'yi FFFEh yapar ve FFFEh adresine 0000h sözcüğünü koyar. Böylece programın sonundaki RET bu sıfırı IP'ye alır ve programın başındaki DOS çıkış koduna döner. emu8086 da .COM programlarını bu düzenle başlatır. Aynı program bir .COM dosyası olarak çalıştırılsa SP önce FFFCh'ye, sonra FFFAh'ya inerdi; değerler ve sıralar değişmezdi.
SP'nin örtük kullanıcıları yalnız PUSH ve POP değildir. CALL dönüş adresini yığına iter, RET onu yığından alır. Bir kesme FLAGS, CS ve IP'yi iter; IRET üçünü geri alır. PUSHF ve POPF bayrak yazmacını taşır. Bütün bu komutlar SP'yi kendileri günceller. Programcı SP'ye doğrudan yazarsa (MOV SP, …) yığının konumunu değiştirmiş olur; bu yalnız program başında ya da yığın kesimini değiştirirken yapılır.
PUSH ve POP sayılarını eşitlememek. Bir prosedür içinde fazladan bir PUSH bırakılırsa RET dönüş adresi yerine o değeri IP'ye alır ve program rastgele bir adrese atlar. Bir koşullu dalda POP'un atlanması da aynı sonucu verir. Her çıkış yolunda yığın, girişteki hâline dönmelidir.
2.2.4.2BP: taban işaretçisi
BP (base pointer), yığın üzerindeki sabit bir konumu göstermek için kullanılır. BP ile yapılan bellek erişimleri varsayılan olarak SS kesimini kullanır: [BP+4], SS:BP+4 adresidir. BX ile yazılan [BX+4] ise DS kesimine gider. İki taban yazmacının farkı yalnız bu varsayılan kesimdir.
SP yığının tepesini gösterir ama prosedür içinde sürekli değişir: her PUSH ve POP onu kaydırır. Parametrelere SP'ye göre erişmek bu yüzden zahmetlidir; bir PUSH'tan sonra aynı parametrenin uzaklığı 2 bayt artar. BP bu sorunu çözer. Prosedürün başında SP'nin o anki değeri BP'ye kopyalanır. Bundan sonra SP ne kadar değişirse değişsin BP sabit kalır ve her parametre BP'den hep aynı uzaklıktadır.
MOV BX, 1234h
PUSH BX ; parametre
CALL TOPLA
MOV DX, AX ; sonuç
RET
TOPLA PROC
PUSH BP
MOV BP, SP ; çerçeve tabanı
MOV AX, [BP+4] ; 1. parametre
POP BP
RET 2
TOPLA ENDPProsedür gövdesi sunumdakinin aynısıdır. Çağıran kısım, parametreyi yığına iten ve sonucu DX'e alan üç satırla eklenmiştir. Program .COM olarak, SP = FFFEh ile başlar.
| Komut | SP | BP | AX | Yığın (adres: değer) |
|---|---|---|---|---|
MOV BX, 1234h | FFFEh | 0000h | 0000h | FFFE: 0000 |
PUSH BX | FFFCh | 0000h | 0000h | FFFC: 1234, FFFE: 0000 |
CALL TOPLA | FFFAh | 0000h | 0000h | FFFA: 0107, FFFC: 1234, … |
PUSH BP | FFF8h | 0000h | 0000h | FFF8: 0000, FFFA: 0107, FFFC: 1234, … |
MOV BP, SP | FFF8h | FFF8h | 0000h | aynı |
MOV AX, [BP+4] | FFF8h | FFF8h | 1234h | aynı |
POP BP | FFFAh | 0000h | 1234h | FFFA: 0107, FFFC: 1234, … |
RET 2 | FFFEh | 0000h | 1234h | FFFE: 0000 |
MOV DX, AX | FFFEh | 0000h | 1234h | FFFE: 0000 |
CALL, dönüş adresi olan 0107h'yi yığına iter. Bu, CALL'dan sonraki komutun, yani MOV DX, AX'in adresidir: program 0100h'den başlar, MOV BX, 1234h 3 bayt, PUSH BX 1 bayt, CALL 3 bayttır ve 0100h + 7 = 0107h eder. Prosedür girişindeki PUSH BP, çağıranın BP değerini saklar. MOV BP, SP ile BP = FFF8h olur ve çerçevenin tabanı sabitlenir. Artık çerçeve şöyle okunur:
[BP]= FFF8h: saklanan eski BP;[BP+2]= FFFAh: dönüş adresi 0107h;[BP+4]= FFFCh: birinci parametre 1234h.
MOV AX, [BP+4] parametreyi AX'e alır. POP BP çağıranın BP'sini geri yükler. RET 2 iki iş yapar: dönüş adresini IP'ye alır ve SP'ye ayrıca 2 ekleyerek yığındaki parametreyi atar. SP girişteki FFFEh değerine döner; yığın, çağrıdan önceki hâlindedir. Prosedürde fazladan PUSH yapılsaydı bile [BP+4] değişmezdi; BP'nin değeri budur.
TOPLA iki parametre alacak biçimde çağrılsın: önce PUSH a, sonra PUSH b. Prosedür içinde a ve b hangi adreslerdedir, dönüşte RET kaç bayt atmalıdır?
Son itilen b, dönüş adresinin hemen üstündedir: [BP+4] = b, ondan önce itilen a [BP+6]'dadır. İki parametre 4 bayt tutar; dönüş RET 4 ile yapılır. Parametre sayısı her arttığında ilk parametrenin uzaklığı 2 bayt artar, ama son parametre her zaman [BP+4]'tedir.
2.2.4.3SI ve DI: indis yazmaçları
SI (source index, kaynak indisi) ve DI (destination index, hedef indisi), adlarını dizgi komutlarındaki görevlerinden alır. Dizgi komutları bellekteki ardışık bayt ya da sözcükler üzerinde çalışır: kopyalama (MOVS), karşılaştırma (CMPS), tarama (SCAS), yükleme (LODS) ve saklama (STOS). Bu komutların işlenenleri komutta yazılmaz; örtüktür:
- Kaynak her zaman DS:SI'dır: MOVSB, CMPSB ve LODSB okumayı buradan yapar.
- Hedef her zaman ES:DI'dır: MOVSB, STOSB ve SCASB yazmayı ya da karşılaştırmayı burada yapar.
- Her adımdan sonra SI ve DI, DF bayrağına göre 1 (bayt) ya da 2 (sözcük) artar veya azalır.
Dizgi komutları dışında SI ve DI sıradan indis yazmaçlarıdır. MOV AL, [SI] ya da MOV [DI], AL yazıldığında ikisi de varsayılan olarak DS kesimini kullanır. ES:DI kuralı yalnız dizgi komutlarının hedefi içindir. Bu ayrım, DS ile ES farklı kesimleri gösterdiğinde önem kazanır.
İndis yazmacı (index register), bir dizi ya da dizgi içindeki konumu tutan yazmaçtır. 8086'nın indis yazmaçları SI ve DI'dır. Taban yazmacıyla birlikte kullanıldıklarında ([BX+SI], [BP+DI+5]) etkin adres, iki yazmacın ve sabitin toplamıdır.
Sunumdaki program “ABC” dizgisini 0200h'den 0300h'ye kopyalar. Bir .COM programında DS = ES olduğu için iki alan aynı kesimdedir.
CLD ; DF=0: artan adres
MOV SI, 0200h ; kaynak
MOV DI, 0300h ; hedef
MOV CX, 3 ; bayt sayısı
REP MOVSB ; ES:[DI] ← DS:[SI]| Adım | Komut | SI | DI | CX | DF | 0300h–0302h |
|---|---|---|---|---|---|---|
| 1 | CLD | 0000h | 0000h | 0000h | 0 | — — — |
| 2 | MOV SI, 0200h | 0200h | 0000h | 0000h | 0 | — — — |
| 3 | MOV DI, 0300h | 0200h | 0300h | 0000h | 0 | — — — |
| 4 | MOV CX, 3 | 0200h | 0300h | 0003h | 0 | — — — |
| 5 | REP MOVSB (1. tur) | 0201h | 0301h | 0002h | 0 | A — — |
| 6 | REP MOVSB (2. tur) | 0202h | 0302h | 0001h | 0 | A B — |
| 7 | REP MOVSB (3. tur) | 0203h | 0303h | 0000h | 0 | A B C |
CLD, DF'yi sıfırlar: SI ve DI her adımda artacaktır. SI kaynağın, DI hedefin başını gösterir; CX kaç bayt kopyalanacağını söyler. REP MOVSB her turda dört iş yapar: DS:SI'daki baytı ES:DI'ya kopyalar, SI'yı 1 artırır, DI'yı 1 artırır, CX'i 1 azaltır. CX sıfır olunca tekrar biter. Üç tur sonunda “ABC” hedefe kopyalanmıştır ve SI, DI kopyalanan alanın hemen arkasını gösterir (Şekil 2.2.8).
REP öneki CX'i, MOVSB SI ve DI'yı örtük olarak kullanır. Böylece tek bir iki baytlık komut (F3 A4) bütün bir döngünün işini görür. Aynı kopyayı MOV AL, [SI], MOV [DI], AL, INC SI, INC DI ve LOOP ile yazmak beş komut ve her turda daha fazla saat turu gerektirir. 8086 tasarımında yazmaçlara örtük görevler verilmesinin en açık kazancı burada görülür.
2.2.5Kesim yazmaçları ve IP
8086'nın adres yolu 20 bittir ve 1 MB'lık belleği adresler. Yazmaçları ise 16 bittir ve tek başlarına yalnız 64 KB'ı gösterebilir. İki uç, belleğin kesimlere bölünmesiyle birleştirilir: her bellek erişimi bir kesim yazmacı ile 16 bitlik bir bağıl konumdan oluşur. Bu bölüm kesimlemeyi yazmaçlar açısından ele alır: kesim yazmaçlarına nasıl yazılır, hangi yazmaç hangi kesimle çiftleşir ve IP nasıl ilerler.
2.2.5.1Kesim yazmaçlarına yazmak
Dört kesim yazmacı vardır: CS (kod kesimi), DS (veri kesimi), SS (yığın kesimi) ve ES (ek kesim). Her biri 16 bitlik bir paragraf numarası tutar. Kesimin başladığı fiziksel adres bu sayının 16 katıdır; kesim, oradan başlayarak en çok 64 KB uzanır.
Fiziksel adres (20 bit) = kesim yazmacı × 10h + bağıl konum. 10h (16) ile çarpmak, onaltılık sayının sonuna bir 0 eklemektir. DS = 2000h ve BX = 0500h ise [BX] erişiminin fiziksel adresi 20000h + 0500h = 20500h'dir.
Kesim yazmaçlarına yazmanın kuralları, öbür yazmaçlardan daha sıkıdır:
- Sabit yüklenemez.
MOV DS, 2000hyazılamaz; assembler bu satırı reddeder. Değer önce bir genel amaçlı yazmaca konur:MOV AX, 2000hveMOV DS, AX. - CS'ye MOV ile yazılamaz. CS'nin değişmesi programın başka bir yere atlaması demektir; bu iş uzak JMP, uzak CALL, RETF, INT ve IRET ile yapılır.
MOV CS, AXsatırı da reddedilir. - Kesimden kesime MOV yoktur.
MOV ES, DSyazılamaz. Ya bir yazmaç üzerinden (MOV AX, DS,MOV ES, AX) ya da yığın üzerinden (PUSH DS,POP ES) aktarılır. - Kesim yazmaçları aritmetik yapamaz.
ADD DS, 10hya daINC ESyoktur.
Bu kısıtların nedeni, kesim yazmaçlarının EU'da değil BIU'da bulunmasıdır. ALU'nun çıkışı doğrudan kesim yazmaçlarına bağlı değildir; kesim yazmaçlarına yalnız MOV ve POP aracılığıyla, bir yazmaç ya da bellek sözcüğünden veri gelir. Aşağıdaki kısa program bir veri kesimini kurar ve kullanır. 20500h fiziksel adresinde 5Ah baytı bulunsun.
MOV AX, 2000h
MOV DS, AX ; DS = 2000h: kesim 20000h'de başlar
MOV BX, 0500h
MOV AL, [BX] ; AL ← [20500h]
MOV [BX+1], AL ; [20501h] ← AL| Komut | AX | DS | BX | Fiziksel adres | 20500h–20501h |
|---|---|---|---|---|---|
MOV AX, 2000h | 2000h | 0700h | 0000h | — | 5A 00 |
MOV DS, AX | 2000h | 2000h | 0000h | — | 5A 00 |
MOV BX, 0500h | 2000h | 2000h | 0500h | — | 5A 00 |
MOV AL, [BX] | 205Ah | 2000h | 0500h | 20500h (okuma) | 5A 00 |
MOV [BX+1], AL | 205Ah | 2000h | 0500h | 20501h (yazma) | 5A 5A |
DS başlangıçta .COM yükleyicisinin verdiği 0700h'dir. İkinci satırdan sonra BX ile yapılan her erişim 20000h tabanına göre olur. AL'ye 5Ah okunduğunda AH'deki 20h yerinde kaldığı için AX = 205Ah olur; 8 bitlik yarıların bağımsızlığı burada da görülür.
2.2.5.2Yazmaç çiftleri
Bir bağıl konum tek başına bir adres değildir; hangi kesime ait olduğu da bilinmelidir. 8086 her erişim türü için bir kesim yazmacını varsayılan olarak seçer. Programcı bunu yazmadığı için bu çiftler de örtük görevlerin bir parçasıdır (Tablo 2.2.14).
| Çift | Kullanım | Örnek |
|---|---|---|
CS:IP | sıradaki komut | her komut getirme |
SS:SP | yığının üstü | PUSH, POP, CALL, RET, INT |
SS:BP | yığın çerçevesi | MOV AX, [BP+4] |
DS:BX, DS:SI, DS:DI | veri | MOV AL, [BX+SI] |
DS:SI → ES:DI | dizgi komutları | MOVSB, CMPSB |
Kural kısaca şöyle özetlenir: adres BP içeriyorsa kesim SS'dir, içermiyorsa DS'dir. [BP+SI] SS'yi, [BX+SI] DS'yi kullanır. Dizgi komutlarının hedefi her zaman ES'dir. Varsayılan kesim, komutun önüne bir kesim öneki eklenerek değiştirilebilir: MOV AL, ES:[BX] veriyi ES kesiminden okur. Komut getirme (CS:IP) ve yığın işlemleri (SS:SP) için önek kullanılamaz. Dizgi hedefi ES:DI de değiştirilemez; yalnız kaynak DS:SI'nın kesimi önekle başka bir kesime yöneltilebilir.
DS = 1000h, SS = 3000h, SI = 0020h ve BP = 0020h iken MOV AL, [SI] ve MOV AL, [BP] hangi fiziksel adresleri okur?
[SI] BP içermez; kesim DS'dir: 1000h × 10h + 0020h = 10000h + 0020h = 10020h. [BP] ise SS kesimini kullanır: 30000h + 0020h = 30020h. İki komutun bağıl konumu aynıdır ama fiziksel adresleri 128 KB uzaktadır.
Bir diziye BP ile erişmeye çalışmak. MOV BP, OFFSET dizi ve MOV AL, [BP] yazılırsa erişim SS kesimine gider. .COM programlarında DS = SS olduğu için hata gizli kalır; DS ile SS'nin farklı olduğu bir .EXE programında yanlış bellek okunur. Veri dizileri için BX, SI ya da DI kullanılır.
2.2.5.3IP: komut işaretçisi
Komut işaretçisi (IP, instruction pointer), kod kesimi içinde yürütülecek sıradaki komutun bağıl konumunu tutar. Sıradaki komutun fiziksel adresi CS × 10h + IP'dir. Başka işlemcilerde bu yazmaç program sayacı (PC) adını taşır.
Bir komut yürütülürken IP, komutun uzunluğu kadar ilerletilir ve bir sonraki komutu gösterir. 8086 komutları 1 ile 6 bayt arasındadır; bu yüzden IP düzenli adımlarla artmaz. Tablo 2.2.4'teki AX = AH : AL programının adresleri buna iyi bir örnektir (Tablo 2.2.15).
| Komutun adresi | Komut | Makine kodu | Uzunluk | Sonraki IP |
|---|---|---|---|---|
| 0100h | MOV AX, 1234h | B8 34 12 | 3 | 0103h |
| 0103h | MOV AL, 0FFh | B0 FF | 2 | 0105h |
| 0105h | ADD AL, 1 | 04 01 | 2 | 0107h |
| 0107h | MOV AX, 12FFh | B8 FF 12 | 3 | 010Ah |
| 010Ah | ADD AX, 1 | 05 01 00 | 3 | 010Dh |
16 bitlik bir sabit taşıyan komutlar 3 bayt, 8 bitlik sabit taşıyanlar 2 bayttır. Makine kodundaki sabitin düşük baytı önce gelir: 1234h, 34 12 olarak yazılmıştır.
IP'yi sıranın dışına çıkaran komutlar şunlardır: JMP ve koşullu atlamalar, LOOP, CALL, RET, INT ve IRET. IP'ye MOV ile yazılamaz ve IP'den MOV ile okunamaz; IP, programcının adıyla eriştiği bir işlenen değildir. IP'nin değerini öğrenmenin dolaylı bir yolu vardır: CALL komutu dönüş adresini yığına iter ve bir POP ile bu değer bir yazmaca alınabilir.
BIU'nun önden getirme düzeni nedeniyle, işlemcinin içinde bir an için kuyruktaki baytlar ile IP'nin gösterdiği adres farklı olabilir. Programcı açısından bu fark görünmez; IP her zaman yürütülecek sıradaki komutun adresini verir. Bir atlama olduğunda kuyruk boşaltılır ve getirme yeni CS:IP'den yeniden başlar.
2.2.6Bayrak yazmacı ve durum bayrakları
2.2.6.1Bayrak yazmacının yapısı
Bir toplama yapıldığında işlemci yalnız sonucu üretmez; sonuç hakkında birkaç bilgiyi de kaydeder. Sonuç sıfır mı? Negatif mi? 8 bite sığdı mı? Bu bilgiler tek bitlik göstergelerdir ve bayrak (flag) adını taşır. 8086 bayraklarını 16 bitlik bayrak yazmacında (FLAGS) toplar. Bu yazmaca durum yazmacı (status register) da denir.
Yazmacın 16 bitinden yalnız 9'u kullanılır (Şekil 2.2.9). Bunlar iki gruba ayrılır:
Durum bayrakları (status flags) CF, PF, AF, ZF, SF ve OF'dir. Aritmetik ve mantık komutlarından sonra işlemci bunları sonuca göre kendisi kurar (1) ya da temizler (0). Kontrol bayrakları (control flags) DF, IF ve TF'dir. Sonuçtan etkilenmezler; programcı onları ayarlayarak işlemcinin çalışma biçimini değiştirir.
Durum bayraklarının asıl kullanıcısı koşullu atlama komutlarıdır. JZ ZF'ye, JC CF'ye, JS SF'ye, JO OF'ye bakar; JG ve JL gibi işaretli karşılaştırmalar SF ile OF'yi birlikte değerlendirir. Yüksek seviyeli bir dildeki her if ve her döngü koşulu, assembly'de bir karşılaştırma ve ardından bayraklara bakan bir atlama hâline gelir. Bu yüzden bayrakların ne zaman ve nasıl kurulduğunu bilmek, 8086 programlamanın temelidir.
Bayrakların bit sırası rastgele değildir. Alt bayttaki beş bayrak (SF, ZF, AF, PF, CF), 8080 işlemcisinin bayrak baytıyla aynı yerlerdedir. Bu düzen, 8080 programlarının çevrilmesini kolaylaştırmak için korunmuştur ve LAHF/SAHF komutlarının varlık nedenidir. OF ve kontrol bayrakları 8086 ile eklenmiş ve üst bayta konmuştur.
2.2.6.2Bayraklar donanımda nasıl üretilir?
Durum bayrakları ALU'nun çıkışından birkaç basit kapıyla üretilir. Sayısal tasarım bilgisiyle bu devreyi kurmak zor değildir. Bir n bitlik toplayıcıda ri sonucun i'nci biti, ci de i'nci bite giren elde olsun; cn en yüksek bitten çıkan eldedir. 8 bitlik işlemde n = 8'dir.
| Bayrak | Mantık ifadesi | Devre |
|---|---|---|
| CF | c8 (çıkarmada ödünç) | son tam toplayıcının elde çıkışı |
| AF | c4 | bit 3'ten bit 4'e geçen elde |
| ZF | ¬(r7 + r6 + … + r0) | 8 girişli NOR kapısı |
| SF | r7 | en yüksek bitin kopyası (kapı yok) |
| PF | ¬(r7 ⊕ r6 ⊕ … ⊕ r0) | XOR ağacı ve çıkışta değil kapısı |
| OF | c7 ⊕ c8 | en yüksek bite giren ve çıkan eldenin XOR'u |
16 bitlik işlemde CF için c16, SF için r15, OF için c15 ⊕ c16 kullanılır; ZF 16 bitin hepsine bakar. AF ve PF ise 16 bitlik işlemde de yalnız düşük bayttan üretilir. Tabloda dikkat çeken nokta, bayrakların hepsinin aynı anda ve sonuçla birlikte üretilmesidir. İşlemci “işaretli mi, işaretsiz mi?” diye sormaz; her toplamadan sonra altı bayrağın hepsini birden kurar. Hangisinin anlamlı olduğuna program karar verir.
Çıkarma, ALU'da ikiye tümleyenle toplama olarak yapılır: A − B = A + ¬B + 1. Bu toplamada en yüksek bitten elde çıkması, ödünç alınmadığı anlamına gelir. 8086, çıkarmada CF'yi bu eldenin tersi olarak kurar; böylece CF = 1 her zaman “ödünç alındı” demektir. AF için de aynı ters çevirme yapılır.
2.2.6.3SF: işaret bayrağı
İşaret bayrağı (SF, sign flag), sonucun en anlamlı bitinin (MSB) kopyasıdır. 8 bitlik işlemde bit 7, 16 bitlik işlemde bit 15'tir. İkiye tümleyen gösteriminde MSB işaret bitidir: 1 ise sayı negatif, 0 ise pozitif ya da sıfırdır. SF bu yüzden sonucun, işaretli olarak yorumlandığında negatif olup olmadığını söyler.
| Sonuç (8 bit) | İkili | İşaretli değer | MSB | SF |
|---|---|---|---|---|
35h | 0011 0101 | +53 | 0 | 0 |
7Fh | 0111 1111 | +127 | 0 | 0 |
80h | 1000 0000 | −128 | 1 | 1 |
C2h | 1100 0010 | −62 | 1 | 1 |
FFh | 1111 1111 | −1 | 1 | 1 |
Kural kısadır: 00h–7Fh arasındaki sonuçlarda SF = 0, 80h–FFh arasındakilerde SF = 1'dir. 16 bitte sınır 7FFFh ile 8000h arasındadır. İşlemci sayının işaretli mi işaretsiz mi olduğunu bilmez. C2h baytı işaretsiz olarak 194, işaretli olarak −62'dir; iki yorumda da SF = 1 olur. SF'yi “negatif” diye okumak programcının seçimidir ve yalnız işaretli sayılarla çalışırken anlamlıdır.
SF'nin gerçek matematiksel sonucun işaretini verdiğini sanmak. 70h + 10h = 80h işleminde iki pozitif sayı toplanmıştır ve gerçek sonuç +128'dir. Yine de SF = 1 olur, çünkü 8 bitlik sonucun MSB'si 1'dir. Sonuç işaretli aralığa sığmamıştır; bunu OF gösterir. SF yalnız OF = 0 iken doğru işareti verir.
2.2.6.4ZF ve PF: sıfır ve eşlik bayrakları
Sıfır bayrağı (ZF, zero flag), sonucun bütün bitleri sıfırsa 1 olur; aksi hâlde 0'dır. Bayrağın değeri ile sonucun değeri ters görünür: ZF = 1 “sonuç sıfır” demektir. Bu karışıklık bayrağı bir soru gibi okuyarak giderilir: “sonuç sıfır mı?” Evet ise 1.
ZF en çok eşitlik sınamasında kullanılır. CMP AX, BX, AX − BX'i hesaplar ama sonucu hiçbir yere yazmaz; yalnız bayrakları kurar. AX = BX ise fark sıfırdır ve ZF = 1 olur. Ardından gelen JE (eşitse atla, JZ ile aynı komut) ZF'ye bakar. AX = BX = 1234h iken CMP sonrasında ZF = 1, PF = 1 ve diğer durum bayrakları 0'dır.
Bir yazmacı sıfırlamanın üç yolu ZF açısından ilginç bir karşılaştırma verir. SUB AL, AL ve XOR AL, AL sonucu sıfır yaptıkları için ZF = 1, CF = 0 yapar. MOV AL, 0 ise bayraklara hiç dokunmaz; önceki işlemden kalan bayraklar durur. Bir koşullu atlamadan hemen önce yazmaç sıfırlanacaksa bu fark önem taşır.
Eşlik bayrağı (PF, parity flag), sonucun düşük 8 bitindeki 1'lerin sayısı çift ise 1, tek ise 0 olur. 16 bitlik işlemlerde de yalnız düşük bayta bakılır. Şekil 2.2.10 iki örnek verir.
Sıfır sayısında hiç 1 yoktur; sıfır da çift sayı olduğundan 00h için PF = 1'dir. PF'nin tarihsel kullanımı seri iletişimde hata denetimiydi. Gönderilen her karaktere bir eşlik biti eklenir ve alıcı aldığı baytın eşliğini sınar; tek bitlik bir bozulma eşliği değiştirir ve fark edilir. Günümüz programlarında PF nadiren kullanılır, ama 8086'nın her aritmetik ve mantık işlemi onu kurmaya devam eder. JP ve JNP komutları bu bayrağa bakar.
AX = 7FF8h iken ADD AX, 0009h sonrasında ZF ve PF nedir?
7FF8h + 0009h = 8001h. Sonuç sıfır değildir: ZF = 0. PF için yalnız düşük bayt 01h = 0000 0001b'ye bakılır. İçinde bir tane 1 vardır, tek sayıdır: PF = 0. Üst bayt 80h'deki 1 hesaba katılmaz.
2.2.6.5AF: yardımcı elde bayrağı
Yardımcı elde bayrağı (AF, auxiliary carry flag), işlem sırasında bit 3'ten bit 4'e elde (toplamada) ya da ödünç (çıkarmada) olursa 1, olmazsa 0 olur. Başka bir deyişle, düşük nibble'dan (4 bitlik gruptan) üst nibble'a bir taşma olup olmadığını gösterir.
Bu bayrak ikili kodlu onluk (BCD) aritmetiğe özgüdür. BCD'de her onluk basamak ayrı bir nibble'da tutulur: 17 sayısı 0001 0111 = 17h olarak yazılır. BCD sayılar ikili toplayıcıyla toplandığında sonuç her zaman geçerli bir BCD olmaz. Şekil 2.2.11'deki örnekte 08h + 09h = 11h çıkar; doğru BCD cevap ise 17'dir. Düşük nibble 16'yı geçtiği için 10'luk sistemin 6'sı “kaybolmuştur”. AF = 1, bu kaybın olduğunu söyler. DAA (toplamadan sonra onluk düzeltme) komutu AF'ye bakar ve düşük nibble'a 6 ekler: 11h + 06h = 17h. DAA'dan sonra AL = 17h olur.
AF'nin bir özelliği, programcının ona doğrudan ulaşamamasıdır. AF'yi sınayan bir koşullu atlama komutu yoktur. AF'yi kuran ya da temizleyen özel bir komut da yoktur. Yalnız DAA, DAS, AAA ve AAS onu kullanır. AF yine de okunabilir ve yazılabilir: LAHF ve PUSHF onu öbür bayraklarla birlikte bir yazmaca ya da yığına kopyalar; SAHF ve POPF geri yazar.
05h − 07h ve 9Ch + 64h işlemlerinde AF kaçtır?
05h − 07h'de düşük nibble'lar 5 − 7'dir; 5, 7'den küçük olduğu için bit 4'ten ödünç alınır: AF = 1. 9Ch + 64h'de düşük nibble'lar C + 4 = 12 + 4 = 16'dır; 15'i aştığı için bit 4'e elde geçer: AF = 1.
2.2.6.6CF: elde bayrağı
Elde bayrağı (CF, carry flag), n bitlik bir toplamada sonuç n bite sığmazsa, yani en yüksek bitten dışarı bir elde çıkarsa 1 olur. Çıkarmada küçük bir sayıdan büyük bir sayı çıkarılır ve ödünç alınırsa da CF = 1 olur. CF, işaretsiz sayılar için taşma göstergesidir.
| İşlem (8 bit) | İşaretsiz yorum | Sonuç | CF |
|---|---|---|---|
FFh + 01h | 255 + 1 = 256 > 255 | 00h | 1 |
80h + 80h | 128 + 128 = 256 > 255 | 00h | 1 |
70h + 10h | 112 + 16 = 128 ≤ 255 | 80h | 0 |
05h − 07h | 5 − 7 < 0 (ödünç) | FEh | 1 |
07h − 05h | 7 − 5 = 2 | 02h | 0 |
05h − 07h örneğinde sonuç FEh'dir. İşaretsiz yorumda bu 254'tür ve gerçek sonuç −2 değildir; CF = 1 bunu bildirir. Aynı FEh işaretli yorumda −2'dir ve doğrudur. Bu yüzden işaretli bir çıkarmada CF = 1 olması bir hata değildir; hata olup olmadığını OF söyler.
CF'nin öbür bayraklardan farklı birkaç özelliği vardır:
- Programcı değiştirebilir.
STCCF'yi 1,CLC0 yapar,CMCtersine çevirir. Durum bayrakları içinde doğrudan kurulup silinebilen tek bayrak CF'dir. - INC ve DEC CF'ye dokunmaz. AL = FFh iken
ADD AL, 1CF = 1 yapar;INC ALise aynı sonucu üretir ama CF'yi önceki değerinde bırakır. Bu tasarım, bir döngü sayacının INC/DEC ile değiştirilmesinin çok baytlı bir toplamanın eldesini bozmaması içindir. - Kaydırma ve döndürme komutlarında dışarı atılan son bit CF'ye gider.
SHL AL, 1, AL = 81h iken AL = 02h ve CF = 1 verir. - Çok sözcüklü aritmetik.
ADC(elde ile topla) CF'yi toplamaya ekler,SBB(ödünç ile çıkar) CF'yi çıkarır. Böylece 16 bitlik bir işlemci 32 ya da 64 bitlik sayıları parça parça toplayabilir.
32 bitlik toplama ADC'nin en bilinen kullanımıdır. DX:AX = 0001FFFFh'ye 1 eklenecek olsun:
MOV AX, 0FFFFh ; alt sözcük
MOV DX, 0001h ; üst sözcük
ADD AX, 1 ; AX = 0000h, CF = 1
ADC DX, 0 ; DX = 0001h + 0 + CF = 0002hAlt sözcükte FFFFh + 1 = 10000h çıkar; AX = 0000h olur ve taşan 1 CF'de bekler. ADC üst sözcüğe hem 0'ı hem CF'yi ekler: DX = 0002h. Sonuç DX:AX = 00020000h'dir. Bir ondalık toplamada elde nasıl sol basamağa taşınıyorsa, burada da CF sözcükten sözcüğe taşınmıştır.
2.2.6.7OF: taşma bayrağı
Taşma bayrağı (OF, overflow flag), sonuç işaretli olarak yorumlandığında kullanılan bit sayısına sığmıyorsa 1 olur. 8 bitte işaretli aralık −128 … +127, 16 bitte −32 768 … +32 767'dir. CF işaretsiz taşmayı, OF işaretli taşmayı gösterir.
Toplama için OF'yi bulmanın iki eşdeğer yolu vardır:
- İşaret kuralı. İki pozitif sayı toplanıp negatif bir sonuç, ya da iki negatif sayı toplanıp pozitif bir sonuç çıkarsa OF = 1'dir. Farklı işaretli iki sayının toplamı hiçbir zaman taşmaz, çünkü sonuç iki sayının arasında kalır.
- Elde kuralı. MSB'ye giren elde ile MSB'den çıkan elde farklıysa OF = 1'dir (Tablo 2.2.16'daki c7 ⊕ c8).
Çıkarmada işaret kuralı şöyle uyarlanır: pozitiften negatif çıkarılıp negatif sonuç, ya da negatiften pozitif çıkarılıp pozitif sonuç bulunursa OF = 1'dir. Örneğin 80h − 01h = 7Fh işleminde −128 − 1 = −129 hesaplanmak istenmiş, sonuç +127 çıkmıştır: OF = 1. Aynı işaretli iki sayının farkı ise hiçbir zaman taşmaz.
Tablo 2.2.19 CF ile OF'nin dört olası birleşimini birer örnekle verir. Her satır hem işaretsiz hem işaretli olarak okunmalıdır.
| İşlem | İşaretsiz yorum | İşaretli yorum | Sonuç | CF | OF |
|---|---|---|---|---|---|
05h + 03h | 5 + 3 = 8 | 5 + 3 = 8 | 08h | 0 | 0 |
7Fh + 01h | 127 + 1 = 128 | 127 + 1 ≠ −128 | 80h | 0 | 1 |
FFh + 01h | 255 + 1 ≠ 0 | −1 + 1 = 0 | 00h | 1 | 0 |
80h + 80h | 128 + 128 ≠ 0 | −128 + (−128) ≠ 0 | 00h | 1 | 1 |
Satırlar elde kuralıyla da sağlanabilir. 7Fh + 01h'de alt yedi bitin hepsi 1'dir; toplamada bit 7'ye elde girer (c7 = 1) ama bit 7'den elde çıkmaz (c8 = 0). İkisi farklıdır: OF = 1. FFh + 01h'de hem c7 hem c8 1'dir: OF = 0, CF = 1. 80h + 80h'de bit 7'ye elde girmez (c7 = 0) ama iki 1'in toplamı bit 7'den elde çıkarır (c8 = 1): OF = 1, CF = 1.
CF ile OF birbirinden bağımsızdır; tabloda dört birleşimin dördü de görülür. İşlemci ikisini de her zaman hesaplar. Program işaretsiz sayılarla çalışıyorsa CF'ye (ve JA, JB gibi atlamalara), işaretli sayılarla çalışıyorsa OF'ye (ve JG, JL, JO gibi atlamalara) bakar.
MOV AL, 0C8h ve ADD AL, 64h sonrasında AL ve altı durum bayrağı nedir?
C8h + 64h = 12Ch. AL'ye alt bayt 2Ch yazılır, taşan 1 CF'ye gider: CF = 1. İşaretli yorumda C8h = −56, 64h = +100'dür; −56 + 100 = 44 = 2Ch sonucu doğrudur, farklı işaretli sayıların toplamı taşmaz: OF = 0. 2Ch = 0010 1100b; MSB 0 olduğu için SF = 0, sonuç sıfır olmadığı için ZF = 0'dır. Düşük baytta üç tane 1 vardır: PF = 0. Düşük nibble'lar 8 + 4 = 12'dir, 15'i aşmaz: AF = 0.
“Taşma” kelimesini her iki bayrak için kullanmak ve CF = 1'i işaretli bir hesapta hata saymak. −1 + 1 işleminde (FFh + 01h) CF = 1 olur ama işaretli sonuç 0 tamamen doğrudur. İşaretli bir hesabın doğru olup olmadığını yalnız OF söyler.
INC ve DEC de OF'yi kurar. AL = 7Fh iken INC AL, AL = 80h yapar; +127 + 1 işaretli aralığı aşmıştır ve OF = 1, SF = 1 olur. NEG komutunda tek taşma durumu en küçük negatif sayıdır: NEG AL, AL = 80h iken yine 80h üretir, çünkü +128 8 bitte gösterilemez; bu durumda OF = 1'dir.
2.2.7Kontrol bayrakları
DF, IF ve TF bir işlemin sonucunu anlatmaz; işlemcinin bundan sonra nasıl çalışacağını belirler. Aritmetik komutlar bu bayraklara dokunmaz. Programcı onları özel komutlarla ya da bayrak yazmacını yığın üzerinden değiştirerek ayarlar.
2.2.7.1DF: yön bayrağı
Yön bayrağı (DF, direction flag), dizgi komutlarının belleği hangi yönde gezeceğini belirler (Şekil 2.2.12):
- DF = 0: düşük adresten yükseğe doğru; her adımda SI ve DI artar.
CLDkomutu DF'yi 0 yapar. - DF = 1: yüksek adresten düşüğe doğru; her adımda SI ve DI azalır.
STDkomutu DF'yi 1 yapar.
Adım büyüklüğü komutun genişliğine bağlıdır: bayt komutlarında (MOVSB, LODSB …) 1, sözcük komutlarında (MOVSW, LODSW …) 2'dir. DF yalnız dizgi komutlarını etkiler. MOV AL, [SI] gibi sıradan bir komut SI'yı hiç değiştirmez.
Bir diziyi baştan sona işlemek için DF = 0 yeterlidir ve çoğu program bunu kullanır. Bu nedenle DF'nin değerine güvenmek yerine her dizgi işleminden önce CLD ya da STD yazmak iyi bir alışkanlıktır. Başka bir yordam DF'yi 1 bırakmışsa, CLD yazmayan bir kopyalama döngüsü belleği ters yönde gezer.
Geriye doğru gezinin gerçek gerekçesi örtüşen alanlardır. Kaynak ve hedef bellekte üst üste biniyorsa kopyalama yönü sonucu belirler. Hedef kaynağın ilerisindeyse (daha yüksek adresteyse) kopyalama sondan başa yapılmalıdır. Aksi hâlde kaynağın henüz okunmamış baytları, hedefe yazılan baytlarla ezilir.
2.2.7.2Kod izleme: DF = 1 ile örtüşen kopyalama
Sunumdaki program bellekteki “KLMN” dizgisini bir bayt sağa kaydırır. Kaynak dizi..dizi+3, hedef dizi+1..dizi+4'tür; iki alan üç baytta örtüşür.
org 100h
mov si, offset dizi+3 ; kaynak
mov di, offset dizi+4 ; hedef
mov cx, 4
std ; DF = 1
rep movsb
cld
ret
dizi db 'KLMN', 0Program 0100h'den başlar. Komutlar 3 + 3 + 3 + 1 + 2 + 1 + 1 = 14 bayt tutar; bu yüzden dizi 010Eh adresine yerleşir. 'K' 010Eh'de, 'L' 010Fh'de, 'M' 0110h'da, 'N' 0111h'de, sonlandırıcı 0 da 0112h'dadır. SI kaynağın son baytını (dizi+3 = 0111h), DI hedefin son baytını (dizi+4 = 0112h) gösterecek biçimde kurulur.
std sonrasında, kopyalamadan önce: SI = 0111h ('N'), DI = 0112h, CX = 4, DF = 1.| Satır | Komut | SI | DI | CX | DF | 010Eh … 0112h |
|---|---|---|---|---|---|---|
| 2 | mov si, offset dizi+3 | 0111h | 0000h | 0 | 0 | K L M N · |
| 3 | mov di, offset dizi+4 | 0111h | 0112h | 0 | 0 | K L M N · |
| 4 | mov cx, 4 | 0111h | 0112h | 4 | 0 | K L M N · |
| 5 | std | 0111h | 0112h | 4 | 1 | K L M N · |
| 6 | rep movsb (1. tur) | 0110h | 0111h | 3 | 1 | K L M N N |
| 6 | rep movsb (2. tur) | 010Fh | 0110h | 2 | 1 | K L M M N |
| 6 | rep movsb (3. tur) | 010Eh | 010Fh | 1 | 1 | K L L M N |
| 6 | rep movsb (4. tur) | 010Dh | 010Eh | 0 | 1 | K K L M N |
| 7 | cld | 010Dh | 010Eh | 0 | 0 | K K L M N |
Birinci turda 0111h'deki 'N', 0112h'ye kopyalanır. Sonlandırıcı sıfırın üzerine yazılır ama bu bayt kaynağın parçası değildir. İkinci turda 0110h'deki 'M', 0111h'ye yazılır. 0111h'deki 'N' bir önceki turda zaten okunmuş ve taşınmıştı; üzerine yazılmasının zararı yoktur. Her tur, bir sonraki turda okunacak baytı değil, önceki turda okunmuş olanı ezer. Dördüncü turdan sonra bellekte “KKLMN” vardır: dizgi bir sağa kaymış, baştaki 'K' yerinde kalmıştır (Şekil 2.2.14). Son satırdaki cld, DF'yi programın geri kalanının beklediği değere döndürür.
Aynı kaydırma ileri yönde, CLD ile denensin: SI = dizi, DI = dizi+1, CX = 4. Birinci tur 'K'yi 010Fh'ye yazar ve 'L'yi ezer. İkinci tur 010Fh'den okur, ama orada artık 'K' vardır; 'K' 0110h'ya yazılır. Her tur bir önceki turun yazdığı 'K'yi okur. Sonuç “KKKKK”dir. Kural şöyle özetlenir: hedef kaynağın ilerisindeyse geriye doğru (STD), gerisindeyse ileriye doğru (CLD) kopyalanır.
STD ile geriye kopyalarken SI ve DI'yı alanların başına kurmak. DF = 1 iken dizgi komutu adresleri azaltır; SI ve DI alanların son baytını göstermelidir. Sözcük kopyalamada (MOVSW) son sözcüğün adresi, yani son bayttan bir önceki adres verilir.
2.2.7.3IF: kesme bayrağı
8086'nın INTR adında bir giriş ucu vardır. Klavye, zamanlayıcı ya da seri port gibi bir çevre birimi işlemciden hizmet istediğinde bu ucu etkinleştirir. İşlemci o anki komutu bitirir, programı keser ve bir kesme hizmet yordamı (ISR) çalıştırır. Kesme bayrağı (IF, interrupt flag) bu isteklerin kabul edilip edilmeyeceğini belirler:
- IF = 1: INTR'den gelen maskelenebilir kesme istekleri kabul edilir.
STIIF'yi 1 yapar. - IF = 0: bu istekler göz ardı edilir, yani maskelenir.
CLIIF'yi 0 yapar.
IF her şeyi engellemez. NMI ucundan gelen maskelenemez kesme IF'den etkilenmez; güç kesintisi ya da bellek eşlik hatası gibi ertelenemeyecek olaylar için ayrılmıştır. INT n komutuyla programın kendisinin başlattığı yazılım kesmeleri de IF'ye bakmaz. IF yalnız dışarıdan, INTR ucundan gelen istekleri süzer.
Bir programın kesmeleri neden kapatmak isteyebileceğini yığın örneği iyi gösterir. Yığını taşımak için SS ve SP'nin ikisi de değiştirilir. SS değiştikten sonra, SP değişmeden önce bir kesme gelirse işlemci FLAGS, CS ve IP'yi yarı yarıya kurulmuş, yanlış bir yığına iter. Bu iki komut kesilmemesi gereken bir bütündür:
CLI ; kesmeleri kapat
MOV AX, 9000h
MOV SS, AX ; yeni yığın kesimi
MOV SP, 0FFFEh ; yeni yığın tepesi
STI ; kesmeleri yeniden aç8086 bu tehlikeye karşı kendisi de bir önlem alır: bir kesim yazmacına yazan komuttan sonra, bir sonraki komut bitene kadar kesme kabul etmez. MOV SS, AX'in hemen arkasına MOV SP, … yazılırsa iki komut kesintisiz çalışır. Yine de CLI/STI çifti niyeti açıkça gösterir. Kesmeleri uzun süre kapalı tutmak ise sakıncalıdır: klavye tuşları kaybolabilir, saat geri kalabilir. CLI ile STI arasındaki kod olabildiğince kısa tutulur.
İşlemci bir kesmeyi kabul ettiğinde IF'yi kendisi 0 yapar; böylece ISR, aynı türden yeni bir kesmeyle hemen kesilmez. Yordam sonundaki IRET, yığından eski FLAGS'i geri yükler ve IF eski değerine, yani 1'e döner.
2.2.7.4TF: tuzak bayrağı
Tuzak bayrağı (TF, trap flag), yonga içi hata ayıklama için konmuştur. TF = 1 iken işlemci tek adım (single step) kipinde çalışır: her komut bittikten sonra kendiliğinden bir tip 1 kesmesi (INT 1) üretir (Şekil 2.2.15).
Akış şöyledir. Program bir komutu yürütür. TF = 1 olduğu için işlemci FLAGS, CS ve IP'yi yığına iter, TF ile IF'yi temizler ve tip 1 kesme vektörünün gösterdiği yordama atlar. Bu yordam bir hata ayıklayıcının parçasıdır: yazmaçları ekrana yazar, kullanıcının bir tuşa basmasını bekler. Yordam çalışırken TF = 0 olduğundan kendisi adım adım yürütülmez. Sonundaki IRET yığından FLAGS'i alır; TF yeniden 1 olur ve programın bir sonraki komutu yürütülür. Ardından döngü yinelenir. DOS'un DEBUG programındaki T (trace) komutu ve emu8086'nın “single step” düğmesi bu düzeni kullanır.
TF'yi doğrudan kuran ya da silen bir komut yoktur. CF için STC/CLC, DF için STD/CLD, IF için STI/CLI vardır; TF için yoktur. TF, bayrak yazmacının tamamı yığın üzerinden değiştirilerek kurulur. TF bayrak yazmacının bit 8'idir ve maskesi 0100h'dir; yöntemin adım adım izlemesi Tablo 2.2.26'dadır.
| Bayrak | Bit | 1 iken | 0 iken | Kuran / silen komut |
|---|---|---|---|---|
| DF | 10 | dizgi adresleri azalır | dizgi adresleri artar | STD / CLD |
| IF | 9 | INTR kesmeleri kabul edilir | INTR kesmeleri maskelenir | STI / CLI |
| TF | 8 | her komuttan sonra INT 1 | normal çalışma | yok; PUSHF/POPF ile |
2.2.8Bayrakları değiştiren komutlar
2.2.8.1Komutların bayraklara etkisi
Bayraklar iki yolla değişir: bazı komutlar onları doğrudan kurar ya da siler, aritmetik ve mantık komutları ise yan etki olarak günceller. Tablo 2.2.22 doğrudan komutları verir.
| Komut | Etki | Komut | Etki |
|---|---|---|---|
CLC | CF = 0 | CLD | DF = 0 |
STC | CF = 1 | STD | DF = 1 |
CMC | CF = ¬CF | CLI / STI | IF = 0 / 1 |
LAHF | AH ← SF ZF – AF – PF – CF | SAHF | AH → düşük bayrak baytı |
PUSHF | FLAGS'i yığına koy | POPF | yığından FLAGS'e yükle |
Komut gruplarının durum bayraklarına etkisi ise Tablo 2.2.23'te toplanmıştır. Tablo, bir koşullu atlamadan önce hangi komutun bayrakları en son değiştirdiğini bulmaya yarar.
| Komut grubu | Bayraklara etkisi |
|---|---|
| ADD, ADC, SUB, SBB, CMP, NEG | CF, PF, AF, ZF, SF, OF hepsi güncellenir |
| INC, DEC | CF hariç hepsi güncellenir |
| AND, OR, XOR, TEST | CF = OF = 0; PF, ZF, SF güncellenir; AF tanımsız |
| MUL, IMUL | CF = OF = sonucun yüksek yarısı anlamlı mı; SF, ZF, AF, PF tanımsız |
| DIV, IDIV | altı durum bayrağı da tanımsız |
| SHL, SHR, SAR | CF = dışarı atılan son bit; PF, ZF, SF güncellenir |
| ROL, ROR, RCL, RCR | yalnız CF ve OF değişir |
| MOV, PUSH, POP, XCHG, LEA, LOOP, JMP, Jcc | bayrakları etkilemez |
“Tanımsız” sözcüğü, Intel'in bayrağın o komuttan sonraki değerini belgelemediği anlamına gelir. Gerçek işlemci bir değer bırakır ama bu değer işlemci modeline göre değişebilir. Doğru bir program tanımsız bayrağa bakmaz. Bir başka ince nokta TEST ve CMP'dir: ikisi de AND ve SUB gibi hesap yapar ama sonucu yazmaz, yalnız bayrakları kurar. Örneğin TEST AL, 80h AL'nin bit 7'sini sınar. AL = B5h iken sonuç 80h olur, ZF = 0 ve SF = 1 çıkar; AL değişmez.
2.2.8.2Kod izleme: LAHF, SAHF ve CMC
LAHF (load AH from flags), bayrak yazmacının düşük baytını AH'ye kopyalar. SAHF (store AH into flags) ters yönde çalışır: AH'yi bayrak yazmacının düşük baytına yazar. Düşük baytın düzeni şöyledir:
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| Bayrak | SF | ZF | – | AF | – | PF | – | CF |
| Değer | SF | ZF | 0 | AF | 0 | PF | 1 | CF |
Bit 1 8086'da her zaman 1 okunur; bit 3 ve bit 5 ise 0'dır. OF, DF, IF ve TF üst baytta olduğu için LAHF ve SAHF onlara erişmez. Sunumdaki program bu iki komutu ve CF'yi değiştiren üç komutu birlikte izler:
org 100h
mov al, 0FFh
add al, 1 ; CF ZF AF PF = 1
lahf ; AH ← bayraklar
cmc ; CF tersle
clc ; CF = 0
stc ; CF = 1
mov ah, 0C1h ; 1100 0001b
sahf ; bayraklar ← AH
ret| Satır | Komut | AH | SF | ZF | AF | PF | CF |
|---|---|---|---|---|---|---|---|
| 2 | mov al, 0FFh | 00h | 0 | 0 | 0 | 0 | 0 |
| 3 | add al, 1 | 00h | 0 | 1 | 1 | 1 | 1 |
| 4 | lahf | 57h | 0 | 1 | 1 | 1 | 1 |
| 5 | cmc | 57h | 0 | 1 | 1 | 1 | 0 |
| 6 | clc | 57h | 0 | 1 | 1 | 1 | 0 |
| 7 | stc | 57h | 0 | 1 | 1 | 1 | 1 |
| 8 | mov ah, 0C1h | C1h | 0 | 1 | 1 | 1 | 1 |
| 9 | sahf | C1h | 1 | 1 | 0 | 0 | 1 |
Üçüncü satırda FFh + 1 = 00h hesaplanır. Dört bayrak birden kurulur: sonuç 8 bite sığmadı (CF = 1), sonuç sıfır (ZF = 1), düşük nibble F + 1 taştı (AF = 1), 00h'de sıfır tane 1 var ve sıfır çifttir (PF = 1). SF = 0'dır, çünkü 00h'nin MSB'si 0'dır.
Dördüncü satırda LAHF bu durumu AH'ye kopyalar. Bitler soldan sağa yazılırsa SF = 0, ZF = 1, 0, AF = 1, 0, PF = 1, 1, CF = 1 elde edilir: 0101 0111b = 57h (Şekil 2.2.16). Bit 1'in sabit 1 olduğu unutulursa 55h bulunur; bu sık bir hesap hatasıdır.
lahf sonrasında bayrak baytı ve AH. AH = 57h = 0101 0111b; bit 1 her zaman 1'dir.Beşinci satırda CMC, CF'yi 1'den 0'a çevirir. Altıncı satırdaki CLC CF'yi 0 yapar; CF zaten 0 olduğu için görünür bir değişiklik olmaz. Yedinci satırda STC, CF'yi 1 yapar. Bu üç komut yalnız CF'ye dokunur; ZF, AF ve PF'nin değişmediği tabloda açıkça görülür. Bu komutlar, örneğin bir yordamın başarılı ya da başarısız döndüğünü CF ile bildirmesinde ve ADC/SBB zincirinin başında eldeyi hazırlamakta kullanılır.
Sekizinci satır AH'ye C1h = 1100 0001b yazar. Bu bit dizisi bayrak baytının düzeninde okunursa bit 7 (SF), bit 6 (ZF) ve bit 0 (CF) 1'dir; AF (bit 4) ve PF (bit 2) 0'dır. Dokuzuncu satırda SAHF bu baytı bayraklara yazar: SF = 1, ZF = 1, CF = 1, AF = 0, PF = 0 olur (Şekil 2.2.17). SF ile ZF'nin birlikte 1 olması normal bir işlemin üretemeyeceği bir durumdur: sonuç hem sıfır hem negatif olamaz. SAHF bayrakları sonuçtan bağımsız olarak istenen değere getirir. OF, DF, IF ve TF bu komuttan etkilenmez.
sahf sonrasında: AH = C1h bayrak baytına yazılmıştır; SF = ZF = CF = 1, AF = PF = 0.MOV AL, 5, CMP AL, 5 ve LAHF sonrasında AH kaçtır?
CMP 5 − 5 = 0 hesaplar: ZF = 1, sonuç 00h olduğundan PF = 1, ödünç olmadığından CF = 0, nibble ödüncü olmadığından AF = 0, MSB 0 olduğundan SF = 0. Bayrak baytı SF ZF 0 AF 0 PF 1 CF = 0 1 0 0 0 1 1 0 = 0100 0110b = 46h'dir.
2.2.8.3PUSHF ve POPF ile bütün bayraklar
LAHF ve SAHF yalnız düşük bayta erişir. Bayrak yazmacının 16 bitinin tamamı PUSHF ile yığına itilir ve POPF ile yığından geri alınır. İki komut arasında yığındaki sözcük bir yazmaca alınıp istenen bit değiştirilebilir. Doğrudan komutu olmayan TF'yi kurmanın tek yolu budur. Aşağıdaki program aynı yöntemi DF üzerinde gösterir; DF bit 10'dur ve maskesi 0400h'dir.
PUSHF ; FLAGS → yığın
POP AX ; AX = FLAGS
OR AX, 0400h ; bit 10 (DF) ← 1
PUSH AX
POPF ; FLAGS ← AX
PUSHF ; sağlama:
POP BX ; BX = yeni FLAGS| Komut | AX | BX | SP | DF | Yığının tepesi |
|---|---|---|---|---|---|
PUSHF | 0000h | 0000h | FFFCh | 0 | F202h |
POP AX | F202h | 0000h | FFFEh | 0 | 0000h |
OR AX, 0400h | F602h | 0000h | FFFEh | 0 | 0000h |
PUSH AX | F602h | 0000h | FFFCh | 0 | F602h |
POPF | F602h | 0000h | FFFEh | 1 | 0000h |
PUSHF | F602h | 0000h | FFFCh | 1 | F602h |
POP BX | F602h | F602h | FFFEh | 1 | 0000h |
Başlangıçta IF = 1, öbür bayraklar 0'dır. PUSHF'in ittiği sözcük F202h'dir. Bunun açılımı öğreticidir: F000h kısmı 12–15 bitleridir ve 8086'da bu bitler her zaman 1 okunur; 0200h bit 9'dur, yani IF = 1; 0002h bit 1'dir ve daima 1'dir. OR AX, 0400h yalnız bit 10'u 1 yapar, öbür bitleri korur: F602h. POPF bu değeri bayrak yazmacına yükler ve DF = 1 olur. Son iki satır sağlamadır: bayraklar yeniden okunduğunda BX = F602h çıkar.
TF için aynı kod OR AX, 0100h ile yazılır. Bir biti silmek için OR yerine AND ve ters maske kullanılır: AND AX, 0FEFFh TF'yi, AND AX, 0FBFFh DF'yi siler. Yığın üzerinden çalışmanın bir yararı da öbür bayrakları korumasıdır. Değişiklik bittiğinde SP girişteki değerine döner; PUSH ve POP sayıları eşittir.
PUSHF/POPF'in bir başka kullanımı bayrakları bir yordam boyunca saklamaktır. Bir yordam başında PUSHF, sonunda POPF yaparsa, içeride hangi aritmetik işlem yapılırsa yapılsın çağıran programın bayrakları bozulmaz. Kesmeler de aynı ilkeyle çalışır: işlemci kesmeye girerken FLAGS'i yığına iter, IRET onu geri alır.
2.2.9Sık yapılan hatalar
Assembler, yazmaçlar ve bayraklarla ilgili hataların çoğunu yakalayamaz; program çalışır ama yanlış sonuç verir. Aşağıdaki hatalar yeni başlayanların programlarında en sık görülenlerdir.
8 bitlik eldenin komşu yarıya geçtiğini sanmak. AX = 12FFh iken ADD AL, 1 sonucu 1300h değil 1200h'dir. 8 bitlik işlemin eldesi CF'ye gider. Elde AH'ye aktarılmak isteniyorsa ardından ADC AH, 0 yazılır ya da işlem baştan 16 bit üzerinde yapılır.
Köşeli parantezi unutmak ya da gereksiz yere koymak. MOV AX, BX BX'in değerini kopyalar; MOV AX, [BX] BX'in gösterdiği bellek sözcüğünü okur. MOV SI, [dizi] dizinin adresini değil ilk sözcüğünü SI'ya yükler; adres için MOV SI, OFFSET dizi ya da LEA SI, dizi yazılır.
Örtük işlenenleri hesaba katmamak. CX bir döngünün sayacıyken döngü içinde SHL AX, CL için CL'yi değiştirmek, ya da DX'te bir değer saklarken 16 bitlik MUL veya DIV kullanmak. Komutların hangi yazmaçları gizlice değiştirdiği Tablo 2.2.3'te toplanmıştır; çakışan yazmaç yığına itilip sonra geri alınır.
DIV'den önce DX'i sıfırlamamak. AX'teki 16 bitlik bir sayıyı BX'e bölmek için yalnız DIV BX yazılırsa bölünen DX:AX olur. DX'te önceki bir işlemden kalan değer varsa bölüm 16 bite sığmayabilir ve işlemci bölme hatası kesmesi üretir. İşaretsiz bölmeden önce MOV DX, 0, işaretli bölmeden önce CWD yazılır.
MOV'un bayrakları değiştirdiğini sanmak. MOV AX, 0 ve ardından JZ yazmak, AX'in sıfır olduğunu sınamaz; JZ, MOV'dan önceki son aritmetik işlemin ZF'sine bakar. Bir yazmacın sıfır olup olmadığı CMP AX, 0, TEST AX, AX ya da OR AX, AX ile sınanır.
CF ile OF'yi karıştırmak. İşaretsiz sayılarla çalışan bir programda taşmayı OF ile, işaretli sayılarla çalışanda CF ile sınamak. 7Fh + 01h işleminde CF = 0'dır ama işaretli sonuç yanlıştır; FFh + 01h işleminde CF = 1'dir ama işaretli sonuç doğrudur.
LAHF bayt hesabında bit 1'i unutmak. LAHF'in ürettiği baytta bit 1 her zaman 1'dir. Bayraklar SF = 0, ZF = 1, AF = 1, PF = 1, CF = 1 iken AH = 57h olur, 55h değil.
Kesim yazmacına sabit yazmak ya da kesimi BP ile karıştırmak. MOV DS, @data yazılamaz; önce AX'e alınır. [BP+SI] erişimi DS'de değil SS'de yapılır.
2.2.10Alıştırmalar ve çözümleri
Sunumdaki beş alıştırma burada ara adımlarıyla çözülmüştür. Sonuçlar emu8086'da çalıştırılarak sağlanabilir.
1. AX = 3A7Fh iken MOV AL, 0 ve ardından INC AH çalışınca AX kaç olur?
MOV AL, 0 yalnız düşük baytı değiştirir: AH = 3Ah kalır, AL = 00h olur ve AX = 3A00h'dir. INC AH yalnız yüksek baytı artırır: 3Ah + 1 = 3Bh. Sonuç AX = 3B00h'dir. İki komut da öbür yarıya dokunmaz.
2. MOV AL, 9Ch / ADD AL, 64h sonrası AL, CF, ZF, SF, OF, PF değerlerini bulun.
Önce ikili toplama yapılır: 1001 1100 + 0110 0100 = 1 0000 0000. Sonuç 100h'dir; AL'ye alt bayt 00h yazılır ve taşan 1 CF'ye gider: CF = 1. Sonuç sıfır olduğu için ZF = 1, MSB 0 olduğu için SF = 0'dır. İşaretli yorumda 9Ch = −100, 64h = +100'dür; −100 + 100 = 0 doğrudur ve farklı işaretli iki sayının toplamı taşmaz: OF = 0. 00h'de sıfır tane 1 vardır, çifttir: PF = 1. Düşük nibble'lar C + 4 = 16'dır; bit 3'ten elde çıkar: AF = 1.
3. MOV AL, 50h / ADD AL, 50h: CF ve OF kaçtır? Neden farklılar?
50h + 50h = A0h. İşaretsiz yorumda 80 + 80 = 160'tır ve 255'ten küçüktür; sonuç 8 bite sığar: CF = 0. İşaretli yorumda iki pozitif sayı, +80 ile +80 toplanmıştır; +160 işaretli aralığın üst sınırı +127'yi aşar. A0h'nin MSB'si 1 olduğu için sonuç −96 gibi görünür. İki pozitifin toplamı negatif çıkmıştır: OF = 1 (SF de 1'dir). İki bayrak farklı soruların cevabıdır: CF işaretsiz, OF işaretli taşmayı bildirir.
4. SP = 0100h iken 3 PUSH ve 1 POP sonrası SP kaçtır?
Her PUSH SP'den 2 çıkarır, her POP 2 ekler: 0100h − 3 × 2 + 2 = 0100h − 6 + 2 = 0100h − 4 = 00FCh. Ara değerler 00FEh, 00FCh, 00FAh ve POP'tan sonra 00FCh'dir. Yığında iki öğe kalmıştır.
5. MOV CX, 0 ile başlayan bir LOOP döngüsü kaç kez döner?
LOOP önce azaltır, sonra sınar. İlk LOOP 0 − 1 = FFFFh hesaplar ve atlar. Döngü CX = FFFFh'den 1'e kadar inerken 65 535 kez daha döner; ilk turla birlikte toplam 65 536 kez çalışır. Tur sayısı 32 bitlik DX:BX çiftinde sayılırsa 0001:0000h = 65 536 çıkar. Bu durumu önlemek için döngüden önce JCXZ konur.
2.2.11Özet
- Yazmaç, işlemcinin içindeki çok hızlı ve küçük bir bellek hücresidir. Yazmaç erişimi dış yola çıkmaz; bellek erişimi en az dört saat turluk bir yol çevrimi sürer. 8086'da bellekten belleğe MOV ya da ADD yoktur.
- 8086'nın 14 yazmacı vardır: 4 veri yazmacı (AX, BX, CX, DX), 4 işaretçi ve indis yazmacı (SP, BP, SI, DI), 4 kesim yazmacı (CS, DS, SS, ES), IP ve FLAGS. Hepsi 16 bittir; yalnız AX, BX, CX, DX 8 bitlik iki yarıya bölünür. Bir yarıya yazmak öbür yarıyı değiştirmez ve 8 bitlik işlemin eldesi komşu yarıya değil CF'ye gider.
- Bazı komutlar yazmaçları örtük kullanır: MUL/DIV → AX ve DX; LOOP/REP → CX; kaydırma sayısı → CL; IN/OUT → AL/AX ve 255'ten büyük port için DX; PUSH/POP/CALL/RET → SP; dizgi komutları → DS:SI ve ES:DI.
- Köşeli parantez içinde yalnız BX, BP, SI ve DI kullanılabilir. BP içeren adresler SS'yi, öbürleri DS'yi kullanır.
- SP yığının tepesini gösterir. PUSH önce SP'yi 2 azaltır, sonra yazar; POP önce okur, sonra SP'yi 2 artırır. BP prosedür boyunca sabit kalarak parametrelere
[BP+4]gibi sabit uzaklıklarla erişmeyi sağlar. - Kesim yazmaçlarına sabit yüklenemez, CS'ye MOV ile yazılamaz. Fiziksel adres = kesim × 10h + bağıl konum. IP sıradaki komutun bağıl konumudur; komut uzunluğu kadar ilerler, MOV ile yazılamaz.
- Bayrak yazmacının 9 biti kullanılır. Durum bayrakları: CF (işaretsiz taşma, ödünç), PF (düşük baytta çift sayıda 1), AF (bit 3'ten bit 4'e elde), ZF (sonuç sıfır), SF (sonucun MSB'si), OF (işaretli taşma). Kontrol bayrakları: DF (dizgi yönü), IF (INTR kesmelerine izin), TF (tek adım).
- Donanımda CF son eldedir, ZF bir NOR kapısıdır, SF MSB'nin kopyasıdır, PF bir XOR ağacının tersidir, OF MSB'ye giren ve çıkan eldenin XOR'udur.
- CLC/STC/CMC CF'yi, CLD/STD DF'yi, CLI/STI IF'yi değiştirir. LAHF/SAHF bayrakların düşük baytını AH ile değiş tokuş eder; PUSHF/POPF bayrak yazmacının tamamını yığına taşır. TF yalnız PUSHF/POPF ile değiştirilebilir.
- MOV, PUSH, POP, XCHG, LEA, LOOP ve atlama komutları bayrakları etkilemez; INC ve DEC CF'ye dokunmaz; MUL ve DIV sonrasında bazı bayraklar tanımsızdır.
2.2.12Kavram listesi
Terimlerin İngilizce karşılıkları ve öteki bölümlerdeki kullanımları terim sözlüğünde bulunur.
- yazmaç register
- yazmaç kümesi register set
- genel amaçlı yazmaç general-purpose register
- akümülatör accumulator
- taban yazmacı base register
- sayaç yazmacı counter register
- yüksek / düşük bayt high / low byte
- örtük işlenen implicit operand
- işaretçi yazmacı pointer register
- yığın işaretçisi (SP) stack pointer
- taban işaretçisi (BP) base pointer
- indis yazmacı index register
- kaynak indisi (SI) / hedef indisi (DI) source / destination index
- yığın stack
- yığın çerçevesi stack frame
- kesim yazmacı segment register
- bağıl konum offset
- fiziksel adres physical address
- varsayılan kesim default segment
- komut işaretçisi (IP) instruction pointer
- bayrak flag
- bayrak yazmacı (FLAGS) flags register
- durum bayrağı status flag
- kontrol bayrağı control flag
- elde bayrağı (CF) carry flag
- eşlik bayrağı (PF) parity flag
- yardımcı elde bayrağı (AF) auxiliary carry flag
- sıfır bayrağı (ZF) zero flag
- işaret bayrağı (SF) sign flag
- taşma bayrağı (OF) overflow flag
- yön bayrağı (DF) direction flag
- kesme bayrağı (IF) interrupt flag
- tuzak bayrağı (TF) trap flag
- tek adım single-step
- maskelenebilir / maskelenemez kesme maskable / non-maskable interrupt
- dizgi komutu string instruction
2.2.13Kendini Dene
BX = 12FFh ve CF = 1 iken
INC BLçalışıyor. BX, ZF ve CF ne olur?Cevap
INC 8 bitlik BL üzerinde çalışır: FFh + 1 = 00h, BH değişmez ve BX = 1200h olur. Sonuç sıfır olduğundan ZF = 1'dir. INC CF'ye dokunmadığı için CF = 1 kalır; 1 olmasının nedeni bu INC değil, önceki durumdur.
AX = 0000h iken
MOV AL, 0F0h,ADD AL, 10hveADC AH, 0çalışıyor. AX ve CF kaçtır?Cevap
F0h + 10h = 100h: AL = 00h, CF = 1. ADC AH'ye 0 ile birlikte CF'yi ekler: AH = 00h + 0 + 1 = 01h. AX = 0100h olur. ADC'nin kendi toplamında elde çıkmadığı için son durumda CF = 0'dır. İki komut birlikte, 8 bitlik eldeyi üst bayta taşıyarak 16 bitlik bir toplama yapmıştır.
Bir .COM programının başında (SP = FFFEh) AX = ABCDh iken
PUSH AXçalışıyor. SP kaç olur, hangi adrese hangi bayt yazılır?Cevap
PUSH önce SP'yi 2 azaltır: SP = FFFCh. Sözcük düşük bayt önce yazılır: SS:FFFCh ← CDh, SS:FFFDh ← ABh. FFFEh'deki 0000h sözcüğü yerinde kalır.
XOR AX, AXileMOV AX, 0arasında AX açısından fark var mıdır? Bayraklar açısından fark nedir?Cevap
İkisinde de AX = 0000h olur. XOR bayrakları sonuca göre kurar: ZF = 1, PF = 1, SF = 0, CF = OF = 0 (AF tanımsız). MOV ise hiçbir bayrağı değiştirmez; önceki işlemden kalan bayraklar korunur. XOR ayrıca 2 bayt,
MOV AX, 03 bayttır.MOV AX, 0FFFFh,MOV BX, 0002h,MUL BXsonrasında DX, AX, CF ve OF nedir?Cevap
FFFFh × 2 = 1FFFEh (65 535 × 2 = 131 070). 32 bitlik sonuç 0001FFFEh'dir: DX = 0001h, AX = FFFEh. Yüksek yarı sıfır olmadığı için CF = OF = 1'dir. SF, ZF, AF ve PF tanımsızdır.
8 bitlik
80h − 01hve7Fh − FFhişlemlerinde CF ve OF nedir?Cevap
80h − 01h = 7Fh. İşaretsiz 128 − 1 = 127 doğrudur, ödünç yok: CF = 0. İşaretli −128 − 1 = −129 sığmaz; sonuç +127 çıkmıştır: OF = 1. 7Fh − FFh = 80h. İşaretsiz 127 − 255 negatiftir, ödünç alınır: CF = 1. İşaretli +127 − (−1) = +128 sığmaz: OF = 1.
DF = 1 iken SI = 0205h ve 0204h, 0205h adreslerinde 11h ve 22h varsa iki
LODSB'den sonra AL ve SI nedir?Cevap
Birinci LODSB DS:0205h'deki 22h'yi AL'ye alır, SI = 0204h olur. İkinci LODSB 0204h'deki 11h'yi alır, SI = 0203h olur. Sonuç AL = 11h, SI = 0203h'dir.
MOV AL, 7Fhve ardındanINC AL: AL, SF, OF ve CF ne olur?Cevap
AL = 80h olur. MSB 1 olduğu için SF = 1'dir. +127 + 1 = +128 işaretli aralığa sığmaz: OF = 1. INC CF'yi değiştirmez; CF önceki değerini (bu programda 0) korur.
Bir kesme hizmet yordamı başında kesmeler neden kendiliğinden kapalıdır? NMI de bu sırada engellenir mi?
Cevap
İşlemci bir kesmeyi kabul ederken FLAGS'i yığına iter ve ardından IF ile TF'yi temizler. IF = 0 olduğu için INTR'den gelen yeni istekler yordamı hemen kesemez; yordam isterse STI ile kesmeleri açar. IRET eski FLAGS'i geri yükler. NMI, IF'den etkilenmediği için bu sırada da kabul edilir.
2.2.14İleri okuma
- Intel, The 8086 Family User's Manual (1979), Bölüm 2: yazmaç modeli, bayrakların tam tanımı ve her komutun saat turu sayısı bu kaynaktadır; Tablo 2.2.1'deki değerler buradan alınmıştır.
- B. B. Brey, The Intel Microprocessors (8. baskı), Bölüm 2 ve 3: programlama modeli, adresleme biçimleri ve varsayılan kesimler, 8086'dan güncel x86'ya genişlemeyle birlikte.
- M. A. Mazidi, J. G. Mazidi, The x86 PC: Assembly Language, Design, and Interfacing (5. baskı), Bölüm 1: yazmaçlar, bayrak yazmacı ve bayrakların elle hesaplandığı çok sayıda örnek.
- M. Morris Mano, Digital Design: toplayıcılar, eşlik üreteçleri ve yazmaçların kapı düzeyinde tasarımı; bayrak devrelerinin sayısal tasarım temeli için.
2.2.15Kaynaklar
- Intel, The 8086 Family User's Manual, 1979.
- Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009.
- Muhammad Ali Mazidi, Janice G. Mazidi, The x86 PC: Assembly Language, Design, and Interfacing, 5. baskı, 2010.
- emu8086 belgeleri: 8086 instructions ve Addressing modes bölümleri.
- Mehmet Bodur, Mikroişlemcilere Giriş: Assembler ile Yazılım ve Arayüz.
İlgili sayfalar: Bölüm 2.2 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.