Logikai műveletek


Elég sokat beszéltünk már a bitekről de eddig sehol sem volt rájuk különösebben szükségünk. A következőkben ismertetésre kerülő utasítások működésének megértéséhez azonban elengedhetetlen a bitek fogalmának ismerete. Ugyanis a most következő logikai műveletek hatása legegyszerűbben az egyes bitek viselkedésének vizsgálatával érthető meg.

A legegyszerűbb ilyen művelet a bitek invertálása (ellenkezőjére való fordítása). Ezt a not utasítással végezhetjük el.


A NOT MŰVELET MŰKÖDÉSE:

Kiinduló érték: 01001010
A not művelet utáni érték: 10110101

A példán jól látható, hogy az egyes helyiértékek tartalma az ellenkezőjére váltott. Felhasználhatjuk ezt például 255-ből való kivonás helyett, mivel az eredmény ugyan az. Ezen kívül még sok más helyen alkalmazható.

Egy az előzőhöz nagyon hasonló a neg művelet. Az eltérés csupán annyi, hogy míg not un. egyes komplemenst számol addig ez a szám kettes komplemensét adja eredményül, ami az eredeti érték -1-szerese, ezt legegyszerűbben úgy számolhatjuk ki, ha invertáljuk a biteket és az eredményhez hozzáadunk egyet.


A NEG MŰVELET MŰKÖDÉSE:

Kiinduló érték: 01001010
A neg művelet utáni érték: 10110110

Ezt a tömörítő eljárásnál fogjuk használni, de erről majd ott. A műveletnek egyébként negatív számok kezelésénél lenne szerepe, de ezzel nem foglalkozunk.

A következő logikai műveletekhez már két adatra lesz szükség, mivel az egyes bitek találkozásától függ az eredmény értéke. Ezek az and, or illetve xor utasítások.

Az and (és) művelet során az eredményben csak ott lesz az eredmény adott bitje 1 értékű, ahol a kiinduló értékben mindkét adatban 1 az adott bit értéke.


AZ AND MŰVELET MŰKÖDÉSE:

1.adat: 1100
2.adat: 1010
eredmény: 1000

Az or (vagy) műveletnél minden bit 1 értékű az eredményben, ahol a forrás adatok közül bármelyikben 1 az adott bit értéke.


AZ OR MŰVELET MŰKÖDÉSE:

1.adat: 1100
2.adat: 1010
eredmény: 1110

A xor (kizáró vagy) utasítás hasonlít az or működéséhez, annyi különbséggel, hogy itt a két darab egyes találkozása is nullát ad eredményül.


AZ XOR MŰVELET MŰKÖDÉSE:

1.adat: 1100
2.adat: 1010
eredmény: 0110

Ezek kombinálásával bármilyen logikai művelet előállítható. Például két számot and kapcsolatba hozunk egymással és az eredményt invertáljuk stb. A xor műveletnek szokták kihasználni azt a tulajdonságát, hogy két azonos bitre mindig nullát ad. így ha egy számot saját magával xor-olok, annak eredménye biztos, hogy nulla lesz. Ezt a trükköt regiszterek nullázására szokták felhasználni, mivel a mov ax,0 utasítás sor a memóriában 3, a xor ax,ax csak 2 Byte-ot foglal el. Ez hosszabb programoknál jelentős lehet. Illetve az or, and utasításokat lehet például arra felhasználni, hogy egy regiszter stb. tartalmáról megtudjunk pár dolgot, mivel az or illetve and művelet végrehajtásakor a flag egyes bitjei az eredménynek megfelelően állnak be. Ha például ax regiszter értéke nulla akkor az or ax,ax utasítássor végrehajtása után a z flag értéke 1 lesz. Ez a sor szintén rövidebb a cmp ax,0 megoldásnál.

A következő művelet, ami ebbe a témakörbe tartozik, az a forgatás. Lehetőség van ugyanis a byte, word tartalmának forgatására többféle módon. Ami közös mindegyik megoldásnál, hogy a byte vagy word széléről kicsúszó bit mindig beíródik a carry flagbe.

A szó szerinti forgatást a ror (rotate right) illetve rol (rotate left) utasítások végzik. A forgatáskor az utasítás után kell írni, amit forgatni akarunk (regiszter, memóriatartalom) és egy vessző után, hogy mennyit. Ez a 8086 alapú gépeknél (XT) vagy 1 vagy cl. Utóbbi esetben cl regiszterben megadott értékkel forgat. 80286-tól fölfelé megadható nagyobb szám is, de ekkor a programszövegben jelölni kell a fordítónak, hogy a programot nem XT-re írtuk. Ez úgy történik, hogy az első sorba egy .286 sort helyezünk el. Innen a fordító tudni fogja, hogy a program 80286 utasítást is tartalmaz.


A ROR UTASÍTÁS MŰKÖDÉSE:

forgatás előtt: 01100101
forgatás után: 10110010

carry értéke: 1 mivel a byte jobb szélén kicsúszó bit értéke 1.


A ROL UTASÍTÁS MŰKÖDÉSE:

forgatás előtt: 01100101
forgatás után: 11001010

carry értéke: 0 mivel a byte bal szélén kicsúszó bit értéke 0.

Tehát a byte forgatása során a kiforgó bit beíródik a carry flagbe és a byte másik szélén befordul mind a rol mind a ror utasításnál.

Hasonló módon működik az rcr illetve rcl utasítások, de itt a forgatott adatot megtoldja egy bittel a carry flag. Tehát mint a byte kilencedik bitje működik ugyanis a kiforduló bit a carry flagbe kerül és a carry előző értéke fordul be a byte másik oldalán.


AZ RCR UTASÍTÁS MŰKÖDÉSE:

carry értéke forgatás előtt: 0

forgatás előtt: 01100101
forgatás után: 00110010

carry értéke: 1 mivel a byte jobb szélén kicsúszó bit értéke 1. carry értéke forgatás előtt: 1

forgatás előtt: 01100101
forgatás után: 10110010

carry értéke: 1 mivel a byte jobb szélén kicsúszó bit értéke 1.


AZ RCL UTASÍTÁS MŰKÖDÉSE:

carry értéke forgatás előtt: 0

forgatás előtt: 01100101
forgatás után: 11001010

carry értéke: 0 mivel a byte bal szélén kicsúszó bit értéke 0. carry értéke forgatás előtt: 1

forgatás előtt: 01100101
forgatás után: 11001011

carry értéke: 0 mivel a byte bal szélén kicsúszó bit értéke 0.

A harmadik forgatási lehetőség, amikor az adat kicsúszó bitje szintén a c flagbe íródik, de a másik oldalról becsúszó bit értéke minden esetben 0.


AZ SHR UTASÍTÁS MŰKÖDÉSE:

forgatás előtt: 01100101
forgatás után: 00110010

carry értéke: 1 mivel a byte jobb szélén kicsúszó bit értéke 1.


AZ SHL UTASÍTÁS MŰKÖDÉSE:

forgatás előtt: 01100101
forgatás után: 11001010

carry értéke: 0 mivel a byte bal szélén kicsúszó bit értéke 0.

Egy byte illetve word forgatására még egy lehetőség van, amikor a jobbra forgatásnál a signum (előjel) flag értéke íródik az adat bal szélére. A jobb szélső bit forgatáskor szintén c-be íródik. A művelet fordítottja azonos az shl működésével.


A SAR UTASÍTÁS MŰKÖDÉSE:

signum értéke forgatás előtt: 0

forgatás előtt: 01100101
forgatás után: 00110010

carry értéke forgatás után: 1 mivel a byte jobb szélén kicsúszó bit értéke 1. signum értéke forgatás előtt: 1

forgatás előtt: 01100101
forgatás után: 10110010

carry értéke forgatás után: 1 mivel a byte jobb szélén kicsúszó bit értéke 1.


AZ SAL UTASÍTÁS MŰKÖDÉSE:

forgatás előtt: 01100101
forgatás után: 11001010

carry értéke: 0 mivel a byte bal szélén kicsúszó bit értéke 0.


Mindezeket a műveleteket kipróbálhatjuk a következő két mintaprogram segítségével, ha a megfelelő helyre az általunk kipróbálni kívánt utasítást írjuk. Ennek helye a szövegben külön jelölve van.

Pelda05 Segment ;Szegmensdefinicio
assume cs:Pelda05,ds:Pelda05 ;Cs, ds beallitasa
Start: mov ax,Pelda05 ;Ds regiszter beallitasa
mov ds,ax ;a kod elejere
mov ax,0b800h ;A kepernyomemoria szegmens-
mov es,ax ;cimet es regiszterbe tolti.
mov ax,3 ;80*25 karakteres mod be-
int 10h ;allitasa, kepernyotorles.
xor di,di ;Di nullazasa.
mov si,offset SZOVEG1 ;Si mutatja a szoveg kezdo-
;cimet.
call Kiiro1 ;Meghivja a Kiiro1 eljarast.
mov bl,byte ptr [SZAM1] ;Bl-ben a kiirando szam van
call Kiiro2 ;es ezt a Kiiro2 rutin
;irja ki a kepernyore.
mov di,160 ;A kovetkezo szoveg
;kezdocime.
mov si,offset SZOVEG2 ;Ugyan az mint elobb.
call Kiiro1
mov bl,byte ptr [SZAM2]
call Kiiro2
mov di,320
mov si,offset SZOVEG3
call Kiiro1
mov bl,byte ptr [SZAM1] ;Az elso szamot bl
AND bl,byte ptr [SZAM2] ;regiszterbe teszi es
call Kiiro2 ;vegrehajtja a kijelolt
;muveletet a masodik szammal
;amit utanna kiir a
;kepernyore. Itt kell a
;kivant muveletet beallitani.
xor ax,ax ;Billentyuvaras.
int 16h
mov ax,4c00h ;Kilepes a DOS-ba.
int 21h
Kiiro1 Proc ;Kiiro1 rutin kezdete.
mov cx,16 ;A szoveg 16 karakterbol all.
mov ah,15 ;Fekete alapon feher szin.
.1_Kiiro1: mov al,[si] ;A kiiratando betut al
;regiszterbe tolti, majd
mov es:[di],ax ;kiirja es:[di] alltal
;mutatott cimre.
add di,2 ;A kovetkezo karakterpozicio.
inc si ;A kovetkezo karakter
loop .1_Kiiro1 ;Csokkenti cx erteket es ugrik
;a megadott helyre ha cx nem 0
ret ;Visszateres a hivo
;programreszhez.
Kiiro1 Endp ;A rutin vege.
Kiiro2 Proc ;Kiiro2 rutin kezdete.
add di,6 ;Harom karakterpozicioval
;arrebb lep.
mov cx,8 ;Az adat 8 bitbol all.
mov ah,15 ;Fekete alapon feher szin.
.1_Kiiro2: mov al,"0" ;Al regiszterbe a 0 ascii
;kodjat tolti.
shl bl,1 ;Az adatot egyel balra
;lepteti, igy a kicsordulo
;bit a carry flagbe kerul.
jnc .2_Kiiro2 ;Ha ez a bit 0, akkor ugras
;a .2_Kiiro cimkehez.
mov al,"1" ;Ha 1, akkor az al-be az
;1 ascii kodjat toltjuk.
.2_Kiiro2: mov es:[di],ax ;A szamjegyet a kepernyore
;irjuk.
add di,2 ;Egy hellyel arrebb.
loop .1_Kiiro2 ;Ismetles cx-nek megfeleloen.
ret ;Visszateres a rutinbol.
Kiiro2 Endp ;A rutin vege.
SZOVEG1: db "Az elso byte :"
SZOVEG2: db "A masodik byte :"
SZOVEG3: db "Az eredmeny :"
SZAM1: db 01011101b
SZAM2: db 10101011b
Pelda05 Ends ;A szegmens vege.
End Start ;A program vege.

Ez a program már sokkal összetettebb mint az előző négy. Ebben már megtalálhatók a ciklusok, eljárások, feltételek stb. Mint az látható is a regiszterek nullázására itt már a xor művelet lett használva.

Ha egy feladatra többször van szükségünk, akkor azt elég egyszer megírni, majd a programból egy call utasítással végrehajtatni. Ez hasonlít a jmp utasításra, de itt a gép megjegyzi a call utasítás címét a későbbi visszatéréshez. Erre mutat két példát is az 5. program. Az eljárás (procedure) kezdetét egy Proc szó jelzi. Természetesen ahogy a szegmenseknek, így az eljárásoknak is kell egy nevet adni, amivel később hivatkozhatunk rá. Ez a név a Proc előtti címke. A rutint a címkenév és az Endp zárja. Nagyon fontos sor a rutinunkban a ret. Ugyanis ez az utasítás jelenti a gépnek, hogy térjen vissza a call utáni sorra, ahonnan elindították a rutint. Nagyon fontos dolog, hogy ne próbáljunk meg eljárásból kilépni a DOS-ba, mert ez nagy valószínűséggel egy lefagyást fog eredményezni. Ennek oka, hogy a számítógép kezel egy úgynevezett stacket, ahová adatokat lehet elmenteni illetve onnan visszaolvasni. Ez a stack egy a memóriában visszafelé növekvő terület, ha nem adunk meg az ss regiszternek külön értéket, akkor a stack eleje a szegmensünk legvége lesz. Ha adatot mentünk ide, akkor azt beírja és csökkenti a stack mutató értékét, ami mindig az utoljára beírt adatra mutat. Ugyanígy kiolvasáskor is a legutoljára beirt számot kapjuk meg először és utána az előzőt stb. Nos visszatérve a lefagyás okára, a program indításakor a visszatérési cím beíródik a stackbe amit kilépéskor kiolvasva tudja, hova kell visszatérni. A call utasítás is a stacket használja a visszatérési cím tárolására amit a ret-hez érve olvas ki és ugrik a tárolt címre. Ha a rutinból próbálnánk meg kilépni, nem a DOS-hoz való visszatérés címét olvasná ki a gép, hanem a call címét. Természetesen van megoldás, de ez egy kicsit bonyolultabb, ugyanis megtehetjük, hogy kiolvassuk a stackből a call visszatérési címét és ekkor a legutolsó tárolt adat a DOS-hoz való visszatérési címet fogja mutatni.

A másik fontos dolog ami megtalálható a programban az a ciklus. A ciklusok működése azon az elven alapszik, hogy egy regiszterbe beírjuk a végrehajtások számát, és a programrészlet végén csökkeltjük a regiszter értékét és ha még nem nulla, akkor

megismételjük a programot mindaddig míg a regiszter értéke nulla nem lesz. A PC-n ezt a feladatot egyszerűen megoldhatjuk, mivel külön utasítás van erre a célra a loop. A ciklus lefutásának számát cx regiszterben kell megadni és amikor a program a loop utasítássorhoz ér, csökkenti cx értékét, és ha az még nem nulla, akkor ugrik a megadott címre, ami hasonlóképpen a jmp-hez lehet címke illetve regiszter.

A programban a kilépésen kívül két ROM BIOS funkció is használva lett. Az 10h megszakítás a képernyőt kezeli. Ha ah-ba 0 van, akkor a képernyő üzemmódját állítja be al értékének megfelelően. Jelen esetben a 80*25 karakteres módot. A 16h rutin a billentyűzet kezelést végzi. Ha ah-ban nulla van, akkor a gép vár egy billentyű lenyomására, és annak ascii kódját al illetve scan kódját ah regiszterben adja vissza. Itt a visszaérkező adatot nem használjuk fel, mivel a dolog szerepe csak egy billentyűvárás, hogy ne azonnal térjen vissza a DOS-hoz.

A program működését illetően a bináris számkiíratás ami új. Ezt úgy oldja meg, hogy az adatot tartalmazó byte-ot eggyel balra forgatja, így abból a bal szélső bit értéke a carry flagbe kerül. A művelet végrehajtása előtt al regiszterbe a nullás számjegy kódját töltöttük be. Ha a forgatás során a c értéke 1 lenne, akkor al tartalmát az egyes számjegy kódjára változtatjuk. Ha nulla, akkor átugorjuk a változtatást. Az így kialakult számjegyet a már megszokott módon a képernyőre írjuk. Mindezt megismételjük az összes bitre (azaz 8-szor).

Pelda06 Segment ;Szegmensdefinicio
assume cs:Pelda06,ds:Pelda06 ;Cs, ds beallitasa
Start: mov ax,Pelda06 ;Ds regiszter beallitasa
mov ds,ax ;a kod elejere
mov ax,0b800h ;A kepernyomemoria szegmens-
mov es,ax ;cimet es regiszterbe tolti.
mov ax,3 ;80*25 karakteres mod be-
int 10h ;allitasa, kepernyotorles.
xor di,di ;Di nullazasa.
mov si,offset SZOVEG1 ;Si mutatja a szoveg kezdo-
;cimet.
call Kiiro1 ;Meghivja a Kiiro1 eljarast.
mov bl,byte ptr [SZAM1] ;Bl-ben a kiirando szam van
call Kiiro2 ;es ezt a Kiiro2 rutin
;irja ki a kepernyore.
mov di,160 ;A kovetkezo szoveg
;kezdocime.
mov si,offset SZOVEG2 ;Ugyan az mint elobb.
call Kiiro1
mov cl,1 ;A forgatas erteket egyre
;allitja.
mov bl,byte ptr [SZAM1] ;A szamot bl regiszterbe tolti
ROR bl,cl ;es vegrehajtja a kijelolt
call Kiiro2 ;muveletet, amit utanna kiir a
;kepernyore. Itt kell a
;kivant muveletet beallitani.
xor ax,ax ;Billentyuvaras.
int 16h
mov ax,4c00h ;Kilepes a DOS-ba.
int 21h
Kiiro1 Proc ;Kiiro1 rutin kezdete.
mov cx,21 ;A szoveg 21 karakterbol all.
mov ah,15 ;Fekete alapon feher szin.
.1_Kiiro1: mov al,[si] ;A kiiratando betut al
;regiszterbe tolti, majd
mov es:[di],ax ;kiirja es:[di] alltal
;mutatott cimre.
add di,2 ;A kovetkezo karakterpozicio.
inc si ;A kovetkezo karakter
loop .1_Kiiro1 ;Csokkenti cx erteket es ugrik
;a megadott helyre ha cx nem 0
ret ;Visszateres a hivo
;programreszhez.
Kiiro1 Endp ;A rutin vege.
Kiiro2 Proc ;Kiiro2 rutin kezdete.
add di,6 ;Harom karakterpozicioval
;arrebb lep.
mov cx,8 ;Az adat 8 bitbol all.
mov ah,15 ;Fekete alapon feher szin.
.1_Kiiro2: mov al,"0" ;Al regiszterbe a 0 ascii
;kodjat tolti.
shl bl,1 ;Az adatot egyel balra
;lepteti, igy a kicsordulo
;bit a carry flagbe kerul.
jnc .2_Kiiro2 ;Ha ez a bit 0, akkor ugras
;a .2_Kiiro cimkehez.
mov al,"1" ;Ha 1, akkor az al-be az
;1 ascii kodjat toltjuk.
.2_Kiiro2: mov es:[di],ax ;A szamjegyet a kepernyore
;irjuk.
add di,2 ;Egy hellyel arrebb.
loop .1_Kiiro2 ;Ismetles cx-nek megfeleloen.
ret ;Visszateres a rutinbol.
Kiiro2 Endp ;A rutin vege.
SZOVEG1: db "Az eredeti szam :"
SZOVEG2: db "A művelet eredménye :"
SZAM1: db 01011101b
Pelda06 Ends ;A szegmens vege.
End Start ;A program vege.


Ez a program semmi újdonságot nem tartalmaz, mindössze nem két forrásadat lesz, csak egy mivel a forgatáshoz csak egy adat szükséges.

Egy szintet vissza, vagy vissza a főmenübe.