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.

Tanım 2.2.1

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.

CPUAXBX…≈ 1 turBellekveri yolu4+ tur
Şekil 2.2.1: Yazmaçlar işlemcinin içinde, bellek dışındadır. Bir yazmaç erişimi işlemcinin iç yolunda biter; bellek erişimi dış veri yolu üzerinden en az dört saat turu sürer.

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.

Tablo 2.2.1: Aynı işin yazmaçla ve bellekle yapılmasının saat turu karşılığı (8086 kılavuzu).
KomutİşlenenlerSaat turu5 MHz'de süre
MOV AX, BXyazmaç ← yazmaç20,4 µs
MOV AX, [BX]yazmaç ← bellek8 + 5 = 132,6 µs
ADD AX, BXyazmaç ← yazmaç + yazmaç30,6 µs
ADD AX, [BX]yazmaç ← yazmaç + bellek9 + 5 = 142,8 µs
ADD [BX], AXbellek ← bellek + yazmaç16 + 5 = 214,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ğe

Assembler 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.

Sık yapılan hata

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ı).
Genel amaçlı (veri) yazmaçları15870AXAHALakümülatörBXBHBLtabanCXCHCLsayaçDXDHDLveriİşaretçi ve indis yazmaçlarıSPyığın işaretçisiBPtaban işaretçisiSIkaynak indisiDIhedef indisiKesim (segment) yazmaçlarıCSkodDSveriSSyığınESekÖzel yazmaçlarIPkomut işaretçisiFLAGSbayrak yazmacı
Şekil 2.2.2: 8086 yazmaç kümesi. Dört veri yazmacı 8 bitlik yarılarıyla, dört işaretçi ve indis yazmacı, dört kesim yazmacı, IP ve FLAGS. Hepsi 16 bittir; yalnız AX, BX, CX ve DX ikiye bölünebilir.

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.

Tablo 2.2.2: Yazmaçların bulunduğu birim ve adres üretimindeki rolü.
BirimYazmaçlarAdres üretimindeki rolü
EUAX, BX, CX, DXveri; BX ayrıca bağıl konum tutabilir
EUSP, BP, SI, DI16 bitlik bağıl konum
EUFLAGSyok; ALU sonucunu ve kontrol bitlerini tutar
BIUCS, DS, SS, ESkesim başlangıcı (paragraf numarası)
BIUIPkod 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.

Tanım 2.2.2

Ö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.

Tablo 2.2.3: Genel amaçlı yazmaçlar ve örtük kullanımları.
YazmaçAd8 bitlik yarılarÖrtük kullanım (örnek)
AXakümülatör (accumulator)AH, ALMUL, DIV, IN, OUT, LODS/STOS
BXtaban (base)BH, BLbellek adresleme [BX], XLAT
CXsayaç (counter)CH, CLLOOP, REP, kaydırma sayısı CL
DXveri (data)DH, DLMUL/DIV yüksek yarısı, port adresi
SPyığın işaretçisi (stack pointer)—PUSH, POP, CALL, RET
BPtaban işaretçisi (base pointer)—yığın çerçevesi [BP+n]
SIkaynak indisi (source index)—dizgi kaynağı DS:SI
DIhedef 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
Tablo 2.2.4: AX = AH : AL programının izlemesi (her satır yürütüldükten sonraki durum).
SatırKomutAXAHALCFZF
1MOV AX, 1234h1234h12h34h00
2MOV AL, 0FFh12FFh12hFFh00
3ADD AL, 11200h12h00h11
4MOV AX, 12FFh12FFh12hFFh11
5ADD AX, 11300h13h00h00

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).

AXAH = 12hAL = 00h0001 00100000 0000150
Ş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.

Örnek 2.2.1

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.

Sık yapılan hata

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.
  • IN ve OUT: 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. DAA ve DAS: AL'deki BCD sonucunu düzeltir.
  • LODSB baytı AL'ye yükler, STOSB AL'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.

Tablo 2.2.5: Akümülatörlü ve akümülatörsüz komutların makine kodu.
KomutMakine koduUzunluk
ADD AX, 1234h05 34 123 bayt
ADD BX, 1234h81 C3 34 124 bayt
MOV AX, [0300h]A1 00 033 bayt
MOV BX, [0300h]8B 1E 00 034 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
Tablo 2.2.6: AX'i kendisiyle toplayan programın izlemesi (.COM programı, başlangıçta AX = 0).
KomutAXAçıklama
ADD AX, AX0000h0 + 0 = 0; ZF = 1, PF = 1
MOV AX, 0005h0005hAX'e 5 yazılır
ADD AX, AX000Ah5 + 5 = 10 = 000Ah; ZF = 0
MOV AH, 4Ch4C0Ahyalnız AH değişir; AL = 0Ah kalır
INT 21h4C0AhDOS 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.

Tanım 2.2.3

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.

DS kesimi3Ah04FFh25h0500h17h0501h9Ch0502hBL = 25hMOV BL, [0500h]
Şekil 2.2.4: 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
Tablo 2.2.7: BX ile dolaylı erişimin izlemesi. Bellek sütunu 0500h–0503h baytlarıdır.
SatırKomutBXALDL0500h–0503h
1MOV BX, 0500h0500h00h00h25 17 00 9C
2MOV AL, [BX]0500h25h00h25 17 00 9C
3MOV DL, [0501h]0500h25h17h25 17 00 9C
4ADD AL, DL0500h3Ch17h25 17 00 9C
5MOV [BX+2], AL0500h3Ch17h25 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).

25h0500h17h0501h3Ch0502h9Ch0503hBX
Şekil 2.2.5: Programın sonunda bellek. BX hâlâ 0500h'yi gösterir; [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.
Tanım 2.2.4

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ç
Tablo 2.2.8: CX ve LOOP ile 5 + 4 + 3 + 2 + 1 toplamının izlemesi.
AdımKomutCXAXDXSonraki
1MOV CX, 50005h0000h0000h2. satır
2MOV AX, 00005h0000h0000hTEKRAR
3ADD AX, CX0005h0005h0000hLOOP
4LOOP TEKRAR0004h0005h0000hatla
5ADD AX, CX0004h0009h0000hLOOP
6LOOP TEKRAR0003h0009h0000hatla
7ADD AX, CX0003h000Ch0000hLOOP
8LOOP TEKRAR0002h000Ch0000hatla
9ADD AX, CX0002h000Eh0000hLOOP
10LOOP TEKRAR0001h000Eh0000hatla
11ADD AX, CX0001h000Fh0000hLOOP
12LOOP TEKRAR0000h000Fh0000hatlamaz
13MOV DX, AX0000h000Fh000Fh—

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 D

Bu 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, AX

Bu 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 = 0030h

Dö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 (16 bit × 16 bit)AX×BXDX (yüksek)AX (düşük)32 bitlik çarpım
Şekil 2.2.6: 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
Tablo 2.2.9: MUL ve OUT programının izlemesi. “?” MUL sonrasında tanımsız olan bayrakları gösterir.
SatırKomutAXBXDXCFOFSF ZF AF PF
1MOV AX, 1234h1234h0000h0000h000 0 0 0
2MOV BX, 0100h1234h0100h0000h000 0 0 0
3MUL BX3400h0100h0012h11? ? ? ?
4MOV AX, 00FFh00FFh0100h0012h11? ? ? ?
5MOV BL, 200FFh0102h0012h11? ? ? ?
6MUL BL01FEh0102h0012h11? ? ? ?
7MOV DX, 03F8h01FEh0102h03F8h11? ? ? ?
8OUT DX, AL01FEh0102h03F8h11? ? ? ?

Üçü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.

Sık yapılan hata

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.

Örnek 2.2.2

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

Tanım 2.2.5

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
Tablo 2.2.10: PUSH ve POP izlemesi (başlangıçta SP = 0000h). Yığın sütunu SP'den yukarı doğru öğeleri verir.
KomutSPYığın (adres: değer)CXDX
MOV AX, 1111h0000hboş0000h0000h
PUSH AXFFFEhFFFEh: 1111h0000h0000h
MOV BX, 2222hFFFEhFFFEh: 1111h0000h0000h
PUSH BXFFFChFFFCh: 2222h, FFFEh: 1111h0000h0000h
POP CXFFFEhFFFEh: 1111h2222h0000h
POP DX0000hboş2222h1111h

İ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.

SS kesimiFFF6hFFF8hFFFAhFFFCh2222hFFFEh1111hSPbüyüme yönü
Şekil 2.2.7: İki PUSH'tan sonra yığın kesimi: FFFEh'de 1111h, FFFCh'de 2222h. SP = FFFCh en üstü gösterir. Yığın düşük adreslere doğru büyür.

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.

Sık yapılan hata

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 ENDP

Prosedü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.

Tablo 2.2.11: Yığın çerçevesinin izlemesi. Yığın sütunu SP'den yukarı doğrudur.
KomutSPBPAXYığın (adres: değer)
MOV BX, 1234hFFFEh0000h0000hFFFE: 0000
PUSH BXFFFCh0000h0000hFFFC: 1234, FFFE: 0000
CALL TOPLAFFFAh0000h0000hFFFA: 0107, FFFC: 1234, …
PUSH BPFFF8h0000h0000hFFF8: 0000, FFFA: 0107, FFFC: 1234, …
MOV BP, SPFFF8hFFF8h0000haynı
MOV AX, [BP+4]FFF8hFFF8h1234haynı
POP BPFFFAh0000h1234hFFFA: 0107, FFFC: 1234, …
RET 2FFFEh0000h1234hFFFE: 0000
MOV DX, AXFFFEh0000h1234hFFFE: 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.

Örnek 2.2.3

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.

Tanım 2.2.6

İ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]
Tablo 2.2.12: REP MOVSB izlemesi. REP'in her turu ayrı bir satırdır.
AdımKomutSIDICXDF0300h–0302h
1CLD0000h0000h0000h0— — —
2MOV SI, 0200h0200h0000h0000h0— — —
3MOV DI, 0300h0200h0300h0000h0— — —
4MOV CX, 30200h0300h0003h0— — —
5REP MOVSB (1. tur)0201h0301h0002h0A — —
6REP MOVSB (2. tur)0202h0302h0001h0A B —
7REP MOVSB (3. tur)0203h0303h0000h0A 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).

0200h'A''B''C'—SIkaynak0300h'A''B''C'—DIhedef
Şekil 2.2.8: Üçüncü turdan sonra kaynak ve hedef alanları. 'A', 'B', 'C' baytları 0300h'den başlayarak yazılmıştır; SI = 0203h, DI = 0303h, CX = 0.

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.

Tanım 2.2.7

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, 2000h yazılamaz; assembler bu satırı reddeder. Değer önce bir genel amaçlı yazmaca konur: MOV AX, 2000h ve MOV 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, AX satırı da reddedilir.
  • Kesimden kesime MOV yoktur. MOV ES, DS yazı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, 10h ya da INC ES yoktur.

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
Tablo 2.2.13: DS yazmacının kurulması ve kullanılması.
KomutAXDSBXFiziksel adres20500h–20501h
MOV AX, 2000h2000h0700h0000h—5A 00
MOV DS, AX2000h2000h0000h—5A 00
MOV BX, 0500h2000h2000h0500h—5A 00
MOV AL, [BX]205Ah2000h0500h20500h (okuma)5A 00
MOV [BX+1], AL205Ah2000h0500h20501h (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).

Tablo 2.2.14: Yazmaç çiftleri ve kullanıldıkları erişimler.
ÇiftKullanımÖrnek
CS:IPsıradaki komuther komut getirme
SS:SPyığının üstüPUSH, POP, CALL, RET, INT
SS:BPyığın çerçevesiMOV AX, [BP+4]
DS:BX, DS:SI, DS:DIveriMOV AL, [BX+SI]
DS:SI → ES:DIdizgi 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.

Örnek 2.2.4

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.

Sık yapılan hata

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

Tanım 2.2.8

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).

Tablo 2.2.15: IP'nin komut uzunluklarına göre ilerleyişi (.COM programı, ORG 100h).
Komutun adresiKomutMakine koduUzunlukSonraki IP
0100hMOV AX, 1234hB8 34 1230103h
0103hMOV AL, 0FFhB0 FF20105h
0105hADD AL, 104 0120107h
0107hMOV AX, 12FFhB8 FF 123010Ah
010AhADD AX, 105 01 003010Dh

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.

0CF1–2PF3–4AF5–6ZF7SF8TF9IF10DF11OF12–13–14–15–durum bayrağı (sonuçtan etkilenir)kontrol bayrağı (programcı ayarlar)
Şekil 2.2.9: 16 bitlik bayrak yazmacı. Bit 0 CF, 2 PF, 4 AF, 6 ZF, 7 SF, 8 TF, 9 IF, 10 DF, 11 OF; öbür bitler kullanılmaz.

Yazmacın 16 bitinden yalnız 9'u kullanılır (Şekil 2.2.9). Bunlar iki gruba ayrılır:

Tanım 2.2.9

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.

Tablo 2.2.16: Durum bayraklarının toplayıcı çıkışından üretilmesi (8 bitlik işlem).
BayrakMantık ifadesiDevre
CFc8 (çıkarmada ödünç)son tam toplayıcının elde çıkışı
AFc4bit 3'ten bit 4'e geçen elde
ZF¬(r7 + r6 + … + r0)8 girişli NOR kapısı
SFr7en yüksek bitin kopyası (kapı yok)
PF¬(r7 ⊕ r6 ⊕ … ⊕ r0)XOR ağacı ve çıkışta değil kapısı
OFc7 ⊕ c8en 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.

Tablo 2.2.17: 8 bitlik sonuçlarda SF.
Sonuç (8 bit)İkiliİşaretli değerMSBSF
35h0011 0101+5300
7Fh0111 1111+12700
80h1000 0000−12811
C2h1100 0010−6211
FFh1111 1111−111

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.

Sık yapılan hata

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.

03h000000112 tane 1 → çift → PF = 107h000001113 tane 1 → tek → PF = 0
Şekil 2.2.10: Eşlik bayrağı. 03h'nin düşük baytında iki tane 1 vardır, PF = 1; 07h'de üç tane 1 vardır, PF = 0.

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.

Örnek 2.2.5

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.

0000 100008h+ 0000 100109h0001 000111h↑ bit 3 → bit 4 eldesiAF = 1 → DAA ile 17h (BCD 17)
Şekil 2.2.11: 08h + 09h = 11h. Düşük nibble'lar toplanınca 8 + 9 = 17 olur ve bit 3'ten bit 4'e elde çıkar: AF = 1. DAA bu bilgiyle sonucu BCD 17'ye düzeltir.

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.

Örnek 2.2.6

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.

Tablo 2.2.18: 8 bitlik işlemlerde CF. İşaretsiz yorum sonucun neden sığdığını ya da sığmadığını açıklar.
İşlem (8 bit)İşaretsiz yorumSonuçCF
FFh + 01h255 + 1 = 256 > 25500h1
80h + 80h128 + 128 = 256 > 25500h1
70h + 10h112 + 16 = 128 ≤ 25580h0
05h − 07h5 − 7 < 0 (ödünç)FEh1
07h − 05h7 − 5 = 202h0

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. STC CF'yi 1, CLC 0 yapar, CMC tersine çevirir. Durum bayrakları içinde doğrudan kurulup silinebilen tek bayrak CF'dir.
  • INC ve DEC CF'ye dokunmaz. AL = FFh iken ADD AL, 1 CF = 1 yapar; INC AL ise 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 = 0002h

Alt 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.

Tablo 2.2.19: CF ve OF'nin dört birleşimi (8 bitlik toplama).
İşlemİşaretsiz yorumİşaretli yorumSonuçCFOF
05h + 03h5 + 3 = 85 + 3 = 808h00
7Fh + 01h127 + 1 = 128127 + 1 ≠ −12880h01
FFh + 01h255 + 1 ≠ 0−1 + 1 = 000h10
80h + 80h128 + 128 ≠ 0−128 + (−128) ≠ 000h11

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.

Örnek 2.2.7

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.

Sık yapılan hata

“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. CLD komutu DF'yi 0 yapar.
  • DF = 1: yüksek adresten düşüğe doğru; her adımda SI ve DI azalır. STD komutu DF'yi 1 yapar.
CLD (DF = 0)100101102103104SI, DI ++STD (DF = 1)100101102103104SI, DI −−
Şekil 2.2.12: DF = 0 iken dizgi komutları adresleri artırarak, DF = 1 iken azaltarak ilerler.

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', 0

Program 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.

dizi (010Eh–0112h)K010EhL010FhM0110hN0111h·0112hSIDIDF = 1 · CX = 4
Şekil 2.2.13: std sonrasında, kopyalamadan önce: SI = 0111h ('N'), DI = 0112h, CX = 4, DF = 1.
Tablo 2.2.20: Geriye doğru örtüşen kopyalamanın izlemesi. Bellek sütunu 010Eh–0112h baytlarıdır (· = 0).
SatırKomutSIDICXDF010Eh … 0112h
2mov si, offset dizi+30111h0000h00K L M N ·
3mov di, offset dizi+40111h0112h00K L M N ·
4mov cx, 40111h0112h40K L M N ·
5std0111h0112h41K L M N ·
6rep movsb (1. tur)0110h0111h31K L M N N
6rep movsb (2. tur)010Fh0110h21K L M M N
6rep movsb (3. tur)010Eh010Fh11K L L M N
6rep movsb (4. tur)010Dh010Eh01K K L M N
7cld010Dh010Eh00K 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.

dizi (010Eh–0112h)K010EhK010FhL0110hM0111hN0112hSIDIDF = 1 · CX = 0
Şekil 2.2.14: Dört turdan sonra bellek “KKLMN”dir. SI ve DI kopyalanan alanın bir bayt öncesini gösterir; CX = 0.

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.

Sık yapılan hata

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. STI IF'yi 1 yapar.
  • IF = 0: bu istekler göz ardı edilir, yani maskelenir. CLI IF'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).

komut yürütülürTF = 1 → INT 1FLAGS, CS, IP yığına; TF = 0tek adım yordamıIRET → sonraki komut
Şekil 2.2.15: TF = 1 iken her komuttan sonra INT 1 oluşur: FLAGS, CS ve IP yığına atılır, TF temizlenir, tek adım yordamı çalışır, IRET ile sonraki komuta dönülür.

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.

Tablo 2.2.21: Kontrol bayraklarının karşılaştırması.
BayrakBit1 iken0 ikenKuran / silen komut
DF10dizgi adresleri azalırdizgi adresleri artarSTD / CLD
IF9INTR kesmeleri kabul edilirINTR kesmeleri maskelenirSTI / CLI
TF8her komuttan sonra INT 1normal çalışmayok; 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.

Tablo 2.2.22: Bayrakları doğrudan değiştiren komutlar.
KomutEtkiKomutEtki
CLCCF = 0CLDDF = 0
STCCF = 1STDDF = 1
CMCCF = ¬CFCLI / STIIF = 0 / 1
LAHFAH ← SF ZF – AF – PF – CFSAHFAH → düşük bayrak baytı
PUSHFFLAGS'i yığına koyPOPFyığı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.

Tablo 2.2.23: Komut gruplarının durum bayraklarına etkisi.
Komut grubuBayraklara etkisi
ADD, ADC, SUB, SBB, CMP, NEGCF, PF, AF, ZF, SF, OF hepsi güncellenir
INC, DECCF hariç hepsi güncellenir
AND, OR, XOR, TESTCF = OF = 0; PF, ZF, SF güncellenir; AF tanımsız
MUL, IMULCF = OF = sonucun yüksek yarısı anlamlı mı; SF, ZF, AF, PF tanımsız
DIV, IDIValtı durum bayrağı da tanımsız
SHL, SHR, SARCF = dışarı atılan son bit; PF, ZF, SF güncellenir
ROL, ROR, RCL, RCRyalnız CF ve OF değişir
MOV, PUSH, POP, XCHG, LEA, LOOP, JMP, Jccbayrakları 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:

Tablo 2.2.24: Bayrak yazmacının düşük baytı (LAHF/SAHF düzeni).
Bit76543210
BayrakSFZF–AF–PF–CF
DeğerSFZF0AF0PF1CF

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
Tablo 2.2.25: LAHF, SAHF ve CMC izlemesi.
SatırKomutAHSFZFAFPFCF
2mov al, 0FFh00h00000
3add al, 100h01111
4lahf57h01111
5cmc57h01110
6clc57h01110
7stc57h01111
8mov ah, 0C1hC1h01111
9sahfC1h11001

Üçü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.

bayraklar (düşük bayt)SF0ZF1–0AF1–0PF1–1CF0AH01010111AH = 57h
Şekil 2.2.16: 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.

bayraklar (düşük bayt)SF1ZF1–0AF0–0PF0–1CF1AH11000001↓AH = C1h
Şekil 2.2.17: sahf sonrasında: AH = C1h bayrak baytına yazılmıştır; SF = ZF = CF = 1, AF = PF = 0.
Örnek 2.2.8

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
Tablo 2.2.26: PUSHF/POPF ile bir bayrağın kurulması (.COM başlangıcı: IF = 1, öbür bayraklar 0).
KomutAXBXSPDFYığının tepesi
PUSHF0000h0000hFFFCh0F202h
POP AXF202h0000hFFFEh00000h
OR AX, 0400hF602h0000hFFFEh00000h
PUSH AXF602h0000hFFFCh0F602h
POPFF602h0000hFFFEh10000h
PUSHFF602h0000hFFFCh1F602h
POP BXF602hF602hFFFEh10000h

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.

Sık yapılan hata

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.

Sık yapılan hata

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.

Sık yapılan hata

Ö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.

Sık yapılan hata

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.

Sık yapılan hata

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.

Sık yapılan hata

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.

Sık yapılan hata

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.

Sık yapılan hata

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.

2.2.13Kendini Dene

  1. 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.

  2. AX = 0000h iken MOV AL, 0F0h, ADD AL, 10h ve ADC 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.

  3. 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.

  4. XOR AX, AX ile MOV AX, 0 arası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, 0 3 bayttır.

  5. MOV AX, 0FFFFh, MOV BX, 0002h, MUL BX sonrası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.

  6. 8 bitlik 80h − 01h ve 7Fh − FFh iş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.

  7. 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.

  8. MOV AL, 7Fh ve ardından INC 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.

  9. 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

  1. Intel, The 8086 Family User's Manual, 1979.
  2. Barry B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009.
  3. Muhammad Ali Mazidi, Janice G. Mazidi, The x86 PC: Assembly Language, Design, and Interfacing, 5. baskı, 2010.
  4. emu8086 belgeleri: 8086 instructions ve Addressing modes bölümleri.
  5. 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.