Luku 2

Konekäskyt

Tässä osiossa tutustumme tarkemmin konekäskyihin. Selvitämme mm., mitä ominaisuuksia konekäskyissä on ja minkälaisia konekäskyjä suorittimissa voi olla. Esittelemme samalla myös (osan) esimerkkitietokoneen ttk-91 käskyistä ja annamme esimerkkejä (symbolisesta) konekielisestä koodista.

Konekäskykanta

Suorittimen kaikki konekäskyt yhdessä muodostavat sen konekäskykannan, joka on käyttöliittymä suorittimella tapahtuvaan laskentaan. Laskenta tapahtuu suorittimen rekistereiden välillä ja konekäskyissä yleensä nimetään kaikki sen käyttämät rekisterit.

Rekistereiden lukumäärä konekäskyssä

Yksi tapa luokitella konekäskykantoja perustuu siihen, kuinka monta operandia konekäskyjen aritmetiikkaoperaatioissa voi nimetä. Joissakin tapauksissa riittää yhden operandin nimeäminen. Tällöin toinen operandi ja tulos ovat aina yksi ja sama oletusarvoinen "akkurekisteri". Sen arvo tulee väkisten tuhottua operaation aikana, koska uusi tulos kirjoitetaan samalle paikalle akkurekisteriin.

Useissa konekielissä käskyssä voi aritmetiikkaoperaatioissa nimetä kaksi operandia. Toinen niistä on samalla tulosrekisteri. Tässäkin tapauksessa on se huono piirre, että toinen operandi tuhoutuu käskyn suorituksen aikana. Hyvänä ominaisuutena on kuitenkin se, että näissä prosessoreissa on useampi rekisteri, joten ongelma ei ole niin suuri.

Nykyisissä konekielissä on yleistä, että operandeja on aritmetiikkaoperaatioissa nimetty kolme kappaletta ja ne ovat kaikki rekistereitä. Näin kummankaan operandin arvoa ei tarvitse tuhota, ellei sitä erityisesti haluta nimeämällä tulosrekisteri samaksi kuin toinen operandirekistereistä. Nämä suoritinarkkitehtuurit ovat yleensä ns. load-store arkkitehtuureja, joissa muistiviittauksia tekevät käskyt eivät tee samalla aritmeettis-loogisia operaatioita.

On myös määritelty konekäskykantoja, joissa aritmetiikkaoperaatioissa ei ole nimetty lainkaan rekistereitä. Tuollaiset koneet ovat ns. pinokoneita, joissa laskenta tapahtuu pinoa käyttäen. Pinon voi mieltää pöydällä olevaksi korttipakaksi. Operandit löytyvät pinon pinnalta ja tulos talletetaan myös pinoon. Esimerkiksi yhteenlaskun yhteydessä pinosta poistetaan (päältä) kaksi arvoa operandeiksi, arvot lasketaan yhteen ja tulos talletetaan pinoon päällimmäiseksi. Pino on talletettu muistiin ja sen pinnalle osoittaa sisäinen pinorekisteri (SP, Stack Pointer). Korttipakkaesimerkkiä käyttäen yhteenlaskussa otamme pinon pinnalta kaksi korttia (ruutu 5 ja hertta 3) ja laitamme takaisin niiden arvojen summan (pata 8). Esimerkki ei toimi, jos pinon pinnalla on vain kuvakortteja, koska pelikorttien arvoalue on niin pieni.

Esimerkkikoneessa ttk-91 voi yhdessä konekäskyssä nimetä kaksi operandia, joista ensimmäinen on aina rekisteri. Toinen operandi voi olla joku rekisteri, se voi olla vakio, tai se voidaan määritellä näiden yhdistelmänä. Se voi olla myös muistissa, kuten seuraavassa on tehty.

Load-store arkkitehtuurin koodissa on paljon konekäskyjä, mutta sen suoritus voi olla hyvin nopeaa. Laskenta on erikseen muistioperaatioista ja suorittimella on riittävästi rekistereitä koodin suoritusnopeuden optimoimiseksi. Fiksu suoritin voisi osata noutaa A:n ja B:n arvot osittain samanaikaisesti muistista, koska käskyt ovat täysin riippumattomia toisistaan.

Yhteenlasku eri tyyppisillä suorittimilla

Laske C=A+B, kun A, B ja C ovat muuttujia muistissa eri
tyyppisillä suorittimilla. Operandien lukumäärä
ADD-käskyssä on 0, 1, 2 tai 3.

Pinokone   Akkurek.    Ttk-91        Load-store
(0 oper.)  (1 oper.)   (2 oper.)     (3 oper.)

push A     load  A     load  r4,A    load  r10,A
push B     add   B     add   r4,B    load  r11,B
add        store C     store r4,C    add   r12,r10,r11
pop C                                store r12,C

Olisi mukava, jos nopeita rekistereitä olisi paljon, koska tiedot löytyisivät tällöin usein mahdollisimman nopeasti. Suuri määrä rekistereitä kuitenkin tarkoittaa, että tarvitsemme enemmän bittejä niiden osoittamiseen konekäskyissä. Jos rekistereitä on 16, niin neljä bittiä riittää rekisterin osoitteeksi. Toisaalta, 128 rekisterille tarvitaan jo 7 bittiä niiden osoittamiseen. Tuolloin kolmen rekisterin nimeämiseen kuluu jo 21 bittiä, mikä tekee konekäskyistä ehkä turhan pitkiä. Usein rekistereitä on 16-32 kappaletta kutakin eri tyyppiä, jolloin yhden rekisterin nimeämiseen riittää 4-5 bittiä konekäskyissä.

Esimerkkikoneessa ttk-91 on 8 rekisteriä, joten niiden nimeämiseen konekäskyssä tarvitaan 3 bittiä. Konekäskyssä voi viitata kahteen rekisteriin. Jälkimmäistä rekisteriä sanotaan indeksirekisteriksi, koska sitä voidaan käyttää indeksinä taulukkoviitauksissa.

Eri tavat viitata muistiin

Konekäskyssä tarvitaan jonkinlaisia tapoja viitata muistiin. Korkean tason kielissä usein käytetyt tietotyypit voivat vaatia erilaisia viittaustapoja. Yleisiä tietotyyppejä korkean tason kielissä ovat muuttujat, vakiot ja 1-, 2- tai 3-ulotteiset taulukot. Sellaisia ovat myös tietueet tai oliot, joissa on erilaisia kenttiä. Usein tieto on myös esitetty epäsuorasti, jolloin tietorakenteessa ei olekaan itse tietoa, vaan ainoastaan osoite tietoon.

Olisi mukavaa, jos tietoon useimmiten pystyisi viittaamaan yksinkertaisesti yhden käskyn sisältä jotain suorittimella toteutettua muistinviittaustapaa käyttäen. Aina tämä ei ole mahdollista. Tällöin viitattu muistiosoite lasketaan ensin johonkin rekisteriin suorittamalla usea konekäsky ja sitten vihdoin itse muistiviite voidaan toteuttaa yhdellä konekäskyllä tuon rekisterin kautta. Tyypillisesti näin tehdään vaikkapa viitatessa 3-ulotteisen taulukon alkioihin, koska juuri missään suorittimessa ei ole valmista muistinviittaustapaa 3-ulotteisille taulukoille.

Viitattu tieto voi sijaita tietyssä muistiosoitteessa, joten konekäskyssä olisi mukava olla suoraan paikka tuolle osoitteelle. Joissakin korkean tason kielissä (esim. C) on pointtereita eli osoitinmuuttujia, jotka eivät sisällä itse tietoa vaan ainoastaan tiedon osoitteen muistissa. Tietoon viitatessa pitää ensin hakea muistista pointterin arvo ja vasta sitten sen avulla hakea muistista laskennassa tarvittava data. Kyseessä on tällöin epäsuora muistiviite. Useissa korkean tason ohjelmointikielissä (esim. Java) taas ei tietoisesti ole pointtereita, koska niiden käyttö on vaativaa ja johtaa helposti ohjelmointivirheisiin.

Usein osoite voi olla suhteellinen jonkin rekisterin suhteen. Viitatun tiedon osoite saadaan nyt laskemalla yhteen tuon rekisterin ja jonkin vakion arvot yhteen. Tällaista kutsutaan indeksoiduksi tiedonosoitukseksi. Esimerkiksi 1-ulotteisten taulukoiden tapauksessa tuo vakio voi olla taulukon alkuosoite muistissa ja rekisterin arvo kyseisen taulukon indeksi. Toisaalta taas tietueen tai olion tapauksessa rekisterissä on yleensä tietueen tai olion muistiosoite, ja vakiona on viitatun kentän suhteellinen osoite tietueessa tai oliossa. On hyvin käytännöllistä, että samalla tiedonosoitustavalla voidaan ratkaista kahden hyvin yleisen mutta silti erilaisen tietorakenteen käyttö. Lähes kaikissa suorittimissa on indeksoitu tiedonosoitus käytettävissä.

Muistiosoitteen laskennassa voidaan myös käyttää useampaa rekisteriä ja näin viitata esimerkiksi 2-ulotteisen taulukon alkioon hyvin helposti. Tällainen tiedonosoitustapa on kuitenkin nykyään harvinainen, koska se on niin monimutkainen muihin tapoihin verrattuna. Yksinkertaisien konekäskyjen suoritusnopeus on helpompi optimoida kuin monimutkaisten tiedonosoitustapojen.

Esimerkkikoneessa ttk-91 on kolme tiedonosoitustapaa ja ne perustuvat kaikki indeksoituun tiedonosoitukseen. Ensin lasketaan ohjelman käyttämä "muistiosoite" laskemalla yhteen käskyssä oleva vakio ja käskyssä olevan indeksirekisterin arvo. Jos "osoite" löytyy suoraan indeksirekisteristä, niin vakioksi laitetaan nolla. Jos "osoitteeksi" haluttiin pelkästään käskyssä oleva vakio, niin tämä on koodattu käskyyn laittamalla indeksirekisteriksi R0. Indeksirekisteriä R0 ei tämän vuoksi voi käyttää indeksointiin.

Ttk-91:n suorittimella on kolme vaihtoehtoista tapaa saada jälkimmäinen operandi edellä lasketun "muistiosoitteen" avulla ja ne valitaan 2-bittisen tiedonosoitusmoodin avulla. Moodin arvo 0 (välitön tiedonosoitus) tarkoittaa, että tuo äsken laskettu "muistiosoite" on sellaisenaan toinen operandi, eikä mitään muistiviitettä tarvita. Moodin arvo 1 (suora muistiviite) tarkoittaa, että muistiosoitetta käytetään yhden kerran operandin hakemiseksi muistista. Moodin arvo 2 (epäsuora muistiviite) tarkoittaa, että ensin haetaan muistista edellä laskettua muistiosoitetta käyttäen toisen operandin osoite ja vasta sitten haetaan muistista tuota osoitetta käyttämällä jälkimmäinen operandi.

Esimerkki: Ttk-91 käskyn toisen operandin arvon nouto TR:ään. Oletetaan, että käskyrekisterissä IR on jokin ttk-91 konekäsky. Suoritin toteuttaa seuraavat vaiheet kontrolliyksikön käskyttämänä:



Kopioi käskyn vakiokentän arvo tilapäisrekisteriin TR.

Jos käskyn oikeanpuoleisen rekisterin numero eri kuin 0:
    Laske operandin tosiasiallinen osoite (tai arvo) seuraavasti:
        Kopioi käskyn oikeanpuoleisen rekisterin arvo ALU:n operandiksi 1,
        Kopioi rekisterin TR arvo ALU:n operandiksi 2,
        Anna ALU:lle kontrollijohtimia pitkin komento "add",
        Odota vähän aikaa,
        Kopioi ALU:n ulostulo rekisteriin TR.
        (Nyt operandin tosiasiallinen osoite (tai arvo) on TR:ssä.)

Jos käskyn moodi-kentän arvo on vähintään 1,
    Kopioi TR:n arvo rekisteriin MAR,
    Anna väylän kontrollirekisterille (Bus Ctl) komento "Read",
    Odota vähän aikaa, että arvo saapuu muistista väylää pitkin MBR:ään.
    Kopio rekisterin MBR arvo rekisteriin TR.

Jos käskyn moodi-kentän arvo on vähintään 2,
    Kopioi TR:n arvo rekisteriin MAR,
    Anna väylän kontrollirekisterille (Bus Ctl) komento "Read",
    Odota vähän aikaa, että arvo saapuu muistista väylää pitkin MBR:ään.
    Kopio rekisterin MBR arvo rekisteriin TR.

Jos käskyn moodi-kentän arvo on vähintään 3,
    Aiheuta virhetilanne ("Bad Mode") ja keskeytä käskyn suoritus.

Ttk-91 koneen symbolisessa konekielessä välitön tiedonosoitus kuvataan ennen vakio-osaa olevalla '='-merkillä. Epäsuora muistiviite kuvataan vastaavasti ennen vakio-osaa olevalla '@'-merkillä, kun suorassa muistiviitteessä vakio-osa on sellaisenaan ilman mitään erikoismerkkejä.

Esimerkki: Ttk-91 symbolisen konekielen tiedonosoitusmoodi

Oletetaan, että kaikissa käskyissä alkuaan rekisterin r1 arvo
on 3, rekisterin r2 arvo on 10, muistipaikan mem(17) arvo on 45,
ja että muistipaikan mem(45) arvo on 88.

               op.koodi rek-1   moodi ind.rek. vakio  tulos
load r1, r2      -- 2      1        0     2        0    r1 <- 10
load r1, =7      -- 2      1        0     0        7    r1 <- 7
load r1, =7(r2)  -- 2      1        0     2        7    r1 <- 17
load r1, 7(r2)   -- 2      1        1     2        7    r1 <- 45
load r1, @7(r2)  -- 2      1        2     2        7    r1 <- 88
store r1, 7(r2)  -- 1      1        0     2        7  mem(17) <- 3
store r1, @7(r2) -- 1      1        1     2        7  mem(45) <- 3

Käskyn moodi-kentän arvon kertoo siis muistista lukujen lukumäärän käskyn suoritusaikana. Muistiin kirjoituskäskyn (STORE) yhteydessä moodikentän arvo on yhtä pienempi kuin vastaavassa muistin lukukäskyssä (LOAD) ja sillä tarkoitetaan aina suoraa tai epäsuoraa muistiviitettä. Käskyn suoritusaikana STORE-käskyssä tulee lopuksi aina yksi muistiin kirjoitus.

Konekäskyjen pituus ja muoto

Vaihtelevasta konekäskyjen pituudesta on se hyöty, että eri käskyillä voi olla erilaisia kenttiä. Esimerkiksi pelkästään rekistereiden välillä operoiva konekäsky ei tarvitse vakio-kenttää, mutta muistinviittauksen yhteydessä vakiokentästä taas olisi hyötyä. Joissakin tapauksissa vakiokenttä voisi olla kovinkin lyhyt (esim. 8 bittiä), kun taas muistisoitteiden tapauksissa se voisi olla jopa 32-bittinen tai pidempikin. Vaihtelevasta konekäskyjen pituudesta on kuitenkin myös haittaa. Käskyjen nouto muistista on vaikeata, kun ei heti tiedetä mitenkä monta tavua tarvitsee noutaa. Tavujen määrä selviää vasta kun operaatiokoodi on ensin haettu muistista. Tämän vuoksi nykyään yleensä käytetään usein vain vakiomittaisia konekäskyjä.

Konekäskyjä voi olla useata eri muotoa. Absoluuttinen hyppykäsky ei tarvitse operaatiokoodin lisäksi kuin hyppyosoitteen. Muistiviittauskäskyt voivat käyttää erilaisia muistinviittausmuotoja, joista jotkut käyttävät rekistereitä apunaan ja jotkut eivät. Aritmeettis-loogisissa operaatioissa voidaan tarvita yksi, kaksi tai kolme nimettyä rekisteriä. Yleensä konekäskyn muoto määräytyy suoraan sen operaatiokoodin perusteella, mutta joissakin konekielissä jokaisen operandin muoto voi määräytyä erikseen.

Esimerkkikoneen ttk-91 kaikki konekäskyt ovat 32-bittisiä ja niillä on kaikilla sama muoto: operaatiokoodi 8 bittiä, operandi/tulosrekisteri 3 bittiä, tiedonosoitusmoodi 2 bittiä, indeksirekisteri 3 bittiä ja vakiokenttä 16 bittiä. Tiedonosoitusmoodin käyttö voi tuntua aluksi sekavalta, mutta käytännön ohjelmoinnissa eri tiedonosoitusmoodeja tarvitaan koko ajan. Tällä kurssilla emme perehdy varsinaiseen konekieliseen ohjelmointiin muutamaa triviaalia esimerkkiä enempää.

Esimerkki: Ttk-91 symbolisen konekielen käskyjen talletusmuoto

     kentät:      op.koodi tul.rek. moodi ind.rek. vakiokenttä
     bittejä:        8        3       2      3        16

    -- Hae X:n arvo muistista rekisteriin r1.
    -- Muuttujan X osoite on sama kuin symbolin X arvo.
load r1, X         2 (load)     1      1     0     X:n osoite

    -- Lisää r2:n arvoon luku 6
add  r2, =6        17 (add)     2      0     0     6

    -- Kerro r4:n arvo muistissa olevan taulukon Tbl alkion i arvolla,
    -- kun i on r1:n arvo.
    -- Taulukon Tbl osoite on sama kuin symbolin Tbl arvo.
mul  r4, Tbl(r1)   19 (mul)     4      1     1     Tbl:n osoite

    -- Jaa r3:n arvo luvulla, jonka osoite on muistissa
                              osoitinmuuttujan ptrX arvona.
    -- Osoitinmuuttujan ptrX osoite on sama kuin symbolin ptrX arvo.
div  r3, @ptrX     20 (div)     3      2     0     ptrX:n osoite

    -- Hyppää osoitteeseen loop, jos r2:n arvo on <0.
    -- Silmukan loop osoite on sama kuin symbolin loop arvo
jpos  r2, loop     35 (jpos)    2      0     0     loop'in osoite

    -- Talleta r2:n arvo muistissa olevan muuttujan Y arvoksi.
    -- Rekisterin r2 arvo säilyy ennallaan.
    -- Muuttujan Y osoite on sama kuin symbolin Y arvo.
store r2, Y        1 (store)    2      0     0     Y:n osoite

Tiedon tyypit

Tietokone osaa (tietenkin) käsitellä kaiken tyyppistä tietoa. Suoritin ymmärtää kuitenkin vain muutamaa tietotyyppiä, joita varten on omat konekäskynsä. Kaikki muu tieto pitää kuvata näiden muutaman tietotyypin avulla. Sellaisen tiedon käsittely tapahtuu ohjelmallisesti, yleensä kutakin tietotyyppiä varten erikseen suunniteltujen aliohjelmien avulla.

Kokonaisluvut ovat yleensä kaikissa suorittimissa. Useissa on kokonaislukuja muutamaa eri pituutta, esimerkiksi 8-, 16-, 32 ja 64-bittisiä. On ehkä yllättävää, että kaikki tietokoneella ratkaistavissa olevat ongelmat voidaan ratkaista pelkästään kokonaislukujen avulla. Se ei ole kuitenkaan yksinkertaisin tai tehokkain tapa.

Missään suorittimessa ei ole reaalilukuja. Esimerkiksi, piin tarkka arvo vaatisi äärettömän suuren muistialueen. Sen sijaan suorittimissa käytetään liukulukuja, jotka ovat reaalilukujen kiinteän mittaisia likiarvoja. Liukulukuja on tyypillisesti kahta eri pituutta, 32- ja 64-bittisiä. Esimerkiksi, pii voitaisiin esittää tällöin likiarvona 3.1415927 tai 3.1415926535897931. Kaikissa suorittimissa ei ole edes liukulukuja, koska yksinkertaisissa laitteissa ei ole tarvetta sen tyyppiselle laskennalle.

Joissakin (vanhemmissa) suorittimissa on tietotyyppi totuusarvo (tosi ja epätosi). Nykyään totuusarvoja käsitellään bitteinä, jolloin tosi on koodattu lukuna 1 ja epätosi lukuna 0. Bittejä käsitellään raakadatan bittioperaatioilla (ks. alla).

Vanhemmissa suorittimissa saattoi olla myös tietotyypit merkeille ja merkkijonoille. Huonona puolena tässä oli, että koko järjestelmän piti rajoittua johonkin tiettyyn merkkien ja merkkijonojen esitystapaan. Nykyään käytössä on useita eri merkistöjä. Niiden merkit on koodattu kokonaislukujen avulla ja niitä käsitellään kokonaislukuina.

Kaikki tieto esitetään suorittimella loppujen lopuksi bitteinä, ja suorittimissa on yleensä myös tällainen raakadatan bittiesitysmuoto. Niillä käsitellään tietoa bitteinä riippumatta siitä, mitä nuo bitit tarkoittavat. Joissakin tapauksissa esimerkiksi kokonaislukuja voi olla järkevää käsitellä pelkästään niiden esitysmuodossa bitteinä kuin kokonaislukuina.

Käsittelemme eri tyyppisten tietojen esitystapoja tarkemmin seuraavassa luvussa.

Esimerkkitietokoneessa ttk-91 on vain 32-bittisiä kokonaislukuja ja bittiesitysmuodon 32-bittisiä sanoja.

Konekäskyt

Käskykannassa on kullekin suorittimen ymmärtämälle tietotyypille sen ominaiset perusoperaatiot. Jos samasta tietotyypistä (esim. kokonaisluvut) on olemassa eri pituisia muotoja (esim. 8-, 16-, 32- ja 64-bittiset esitysmuodot), niin tiedon pituus tulee koodata jollain tavoin. Pituus voi olla koodattu omalla operaatiokoodilla tai lisämääreellä. Lisäksi suorittimella on sekalainen joukko konekäskyjä suorittimen yleishallintoon ja käyttöjärjestelmän toimintojen tukemiseen.

Aritmetiikkakäskyt

Aritmetiikkakäskyissä on mukana aina yhteenlasku, vähennyslasku ja kertolasku. Usein siellä on myös jakolasku, mutta ei aina. Joskus jakolasku toteutetaan kertomalla jaettava jakajan käänteisluvulla, koska se voi olla nopeampaa. Kokonaislukujen jakolaskusta voi tulla talteen myös jakojäännös, mutta usein se pitää kaivaa esiin omalla modulo-konekäskyllä (esim., MOD-käsky).

Liukuluvuille on omat vastaavat konekäskynsä. Niiden toteutus on jonkin verran monimutkaisempaa kuin kokonaislukujen käsittely ja ne käyttävät yleensä niille varattuja omia liukulukurekistereitä.

Esimerkki: yhteenlasku eri tyyppisillä tiedoilla (ei ttk-91)

Laske C=A+B, kun A, B ja C ovat muuttujia muistissa
samalla suorittimella. Kustakin muuttujasta on kolme
versiota. Muuttuja iA on kokonaisluku, fA on
32-bittinen liukuluku, dA on 64-bittinen liukuluku, jne.

kokon.luvuilla  liukuluvuilla  64-bitt. liukuluvuilla

load  r1,iA      load f1,fA       dload f2,dA
load  r2,iB      load f2,fB       dload f4,dB
add   r3,r1,r2   fadd f3,f1,f2    dfadd f6,f2,f4
store r3,iC      store f3,fC      dstore f6,dC

64-bittiset rekisterit muodostetaan usein yhdistämällä kaksi peräkkäistä 32-bittistä rekisteriä. Esimerkin 64-bittiset liukuluvut on talletettu kahteen peräkkäiseen 32-bittiseen liukulukurekisteriin. Muuttujan dA 64-bittinen arvo ladataan rekisteriin f2-f3, jne.

Ttk-91:ssä on vain kokonaislukujen konekäskyt ADD, SUB, MUL, DIV ja MOD. Siinä ei ole käskyjä liukulukujen käsittelyyn ja sen käskyssä voi nimetä vain kaksi rekisteriä.

Bittioperaatiot

Bittien käsittelyä varten mukana on yleensä ainakin loogiset operaatiot AND, OR, XOR ja NOT. NOT-käskyllä on vain yksi operandi ja se komplementoi jokaisen bitin. Muilla käskyillä on kaksi operandia ja ne tekevät valitun loogisen-operaation pareittain jokaiselle operandien bitille. AND-operaation tulos on 1 (tosi), jos molemmat vastaavat bitit ovat 1, ja muutoin tulos on 0. OR-operaation tulos on 1, jos jompi kumpi tai molemmat operandibiteistä on 1. Muutoin OR-operaation tulos on 0. XOR-operaatio on mielenkiintoisempi. Lyhenne XOR tulee sanasta "exclusive or". XOR-operaation tulos on 1, jos jompi kumpi mutta ei molemmat operandibiteistä on 1. Muutoin XOR-tulos on 0. Toisin sanoen, XOR on 1, jos operandit ovat erilaisia.

Esimerkki: bittioperaatiot

operaatio: A and B   A or B   A xor B  not A
A:          1100      1100     1100    1100
B:          0101      0101     0101
tulos:      0100      1101     1001    0011

Bittikäskyt tekevät siis loogiset operaatiot kaikille operandien biteille pareittain. Ne sopivat kuitenkin myös käsittelemään loogisia muuttujia, joissa on vain yksi bitti käytössä. Tällöin esimerkiksi 32-bittisen muuttujan Flag arvo on talletettu vain yhteen bittiin ja loput bitit ovat aina nollia.

Bittejä käsitellään myös erilaisilla bittien siirtokäskyillä. Niissä yleensä siirretään rekisterissä olevia bittejä vasemmalle (SHL, shift left) tai oikealle (SHR, shift right) haluttu määrä. Siirron yhteydessä bittejä täytetään oikealta tai vasemmalta nollilla. Oikealle tapahtuvan normaalisiirron lisäksi usein on myös SHRA-käsky (shift right arithmetic), jossa nollan asemesta täytetäänkin vasemmalta alkuaan vasemmanpuolimmaista bittiä. Kokonaislukujen esitystavoissa etumerkki on tiedon vasemmanpuolimmainen bitti, joten SHRA-käsky säilyttää kokonaisluvun etumerkin. Tästäkin on hyötyä tietyissä ohjelmointiongelmissa!

Ttk-91:ssä on bittien siirtokäskyt SHL, SHR ja SHRA.

Kontrollin siirtokäskyt

Kontrollinsiirtokäskyillä voidaan (ehdollisesti) muuttaa oletusarvoista käskyjen virtaa, jossa seuraavaksi suoritettava käsky on aina edellisen perässä muistissa. Tyypillisesti tällaisia käskyjä ovat ehdottomat hyppykäskyt ja ehdolliset haarautumiskäskyt. Ehto voi määräytyä suoraan jonkun rekisterin perusteella vertaamalla sen arvoa nollaan. Esimerkiksi käsky voi olla jneg r1, negat. Se haarautuu osoitteeseen negat, jos rekisterin r1 arvo on negatiivinen. Toisaalta haarautuminen voi perustua aikaisemmin suoritettuun vertailukäskyyn (esim. comp r1, r2), jonka tulos on talletettu tilarekisteriin. Tällainen käsky voisi olla vaikkapa jnles loop. Se haarautuu, jos aikaisemman vertailun tulos oli "isompi tai yhtäsuuri" eli "ei pienempi".

Kaikki silmukat toteutetaan myös edellä mainituilla ehdottomilla hyppykäskyillä ja ehdollisilla haarautumiskäskyillä. Vaikka korkean tason kielissä on monenlaisia silmukoita (for, while, do-until), niin konekielessä niitä on vain kahta lajia. Silmukan loppumistestaus pitää tehdä joko ennen silmukan runkoa tai sen jälkeen. Silmukka toteutetaan korkean tason kielen semantiikan (merkityksen) mukaiseksi, joten esimerkiksi C-kielessä testi on ennen silmukan runkoa ja Fortranissa rungon jälkeen. Fortran-ohjelmissa silmukan runko suoritetaan aina vähintään yhden kerran.

Esimerkki: for-silmukka

For-loop C-kielen semantiikalla (testi silmukan alussa)

for (i=0; i<n; i=i+1) {         load   r1, =0     ; r1 on i
    tbl[i] = 0;            loop comp   r1, n      ; kaikki tehty?
    }                           jnles  done       ; poistu, jos valmista
                                load   r2, =0     ; alusta Tbl[i]
                                store  r2, tbl(r1)
                                add    r1, =1     ; seuraava i
                                jump   loop
                           done ...

Aliohjelmat, funktiot ja metodit ovat ohjelmoijan perustyökaluja ohjelmoinnissa. Niitä kutsutaan tässä kaikki yleisnimellä "aliohjelma". CALL-käskyllä kontrolli siirretään aliohjelmaan, eli se toimii ehdottoman hyppykäskyn tavoin ja aiheuttaa haarautumisen annettuun aliohjelmaan. Haarautumisen lisäksi se muuttaa laskentaympäristön aliohjelman omaan ympäristöön ja tallettaa paluuosoitteen johonkin. Esimerkiksi, aliohjelmassa voi olla omia muuttujia, jotka ovat käytettävissä vain aliohjelman suorituksen aikana. EXIT-käsky suorittaa paluun takaisin kutsun tehneeseen rutiiniin, kutsua seuraavaan konekäskyyn. Se myös palauttaa laskentaympäristön ennalleen.

Esimerkki: funktion kutsu

C-kieli               konekieli

x = sum(y, z);        push  sp, y    ; laita parametrin y arvo pinoon
                      push  sp, z    ; laita parametrin z arvo pinoon
                      call  sp, sum  ; kutsu fuktiota Sum
                      pop   sp, r1   ; ota funktion paluuarvo pinosta
                      store r1, x    ; talleta paluuarvo muuttujan x arvoksi

Käyttöjärjestelmän palvelupyynnöt (SVC, supervisor call) ovat hyvin samankaltaisia aliohjelmakutsujen kanssa, mutta kuitenkin vähän erilaisia. Suorittimen suoritustila muuttuu etuoikeutetuksi ja kutsun yhteydessä täytyy tarkistaa, onko ohjelmalla oikeus kutsua tätä palvelua vai ei. Palvelusta palataan lopulta omalla paluukäskyllä (esim. IRET, interrupt return).

Esimerkki: svc kutsu

C-kieli         konekieli

print(x);       load  r1, x      ; laita tulostettava arvo rekisteriin r1
                svc   sp, =print ; kutsu käyttöjärjestelmäpalvelua Print

Ttk-91:ssä on ehdoton hyppykäsky JUMP. Siellä on myös rekisterin nolla-arvoon vertailuun perustuvat haarautumiskäskyt JNEG, JZER, JPOS, JNNEG, JNZER ja JNPOS. Siellä on kahden operandin vertailukäsky COMP tilanteisiin, jossa vertailun kohde on nollasta poikkeava. Vertailun tulokseen perustuva haarautumiskäskyt ovat JLES, JEQU, JGRE, JNLES, JNEQU ja JNGRE. Nämä haarautumiset vaativat siis aina kahden konekäskyn suorittamisen.

Ttk-91:ssä on aliohjelmia ja käyttöjärjestelmän palvelupyyntöjä varten CALL, EXIT ja SVC-käskyt. Mitään IRET-käskyä ei ole, koska määrittely ei ole täydellinen. Aliohjelmia ei käsitellä tällä kurssilla tämän enempää.

I/O-käskyt

I/O-laitteiden käyttö on vaikeata, koska siinä pitää synkronoida toiminta suorittimen ulkopuolisen laitteen kanssa. Yleensä sen tekevät vain käyttöjärjestelmän laiteajurit etuoikeutetussa tilassa. Yksinkertaisille laitteille voi olla omat (etuoikeutetut) konekäskynsä I/O:n tekemiseen. Monimutkaisempien I/O-laitteiden kontrollointia I/O-laitteen oma muisti näkyy keskusmuistin tavoin ohjelman käyttämässä muistiavaruudessa. Laiteajuri voi sitten kirjoittaa sinne dataa ja komentoja sekä lukea laitteen tilatietoa tavallisilla load/store-käskyillä. Emme käsittele I/O:n toteutusta tämän tarkemmin tällä kurssilla.

Ttk-91:ssä on IN-käsky tiedon lukemiseen näppäimistöltä ja OUT-käsky tiedon kirjoittamiseen näytölle. Näitä voi käyttää tavallisessa suoritustilassa, koska ttk-91:ssä ei muita suoritustiloja ole edes määritelty.

Syötteen lukeminen käyttäjältä

Ttk-91:ssä käyttäjän syötteen tulee aina olla kokonaisluku. Konekäskyllä

IN R3, =KBD

saadaan luettua tulosrekisteriin rekisteriin R3 käyttäjän syötteenä antama kokonaisluku. Sallittuja rekistereitä IN-käskyn kanssa käytettäessä ovat R0 - R5. Rekisterit R6 (SP) ja R7 (FP) ovat varattu erikoistarkoituksia varten (pino-osoitin ja kehysosoitin).

Kokonaisluvun tulostaminen

Ttk-91:ssä tulostaminen näytölle rekisteristä R3 tehdään seuraavasti:

OUT R3, =CRT

Rekisteri voi olla R3:n sijasta myös R0 - R7 eli R0 - R5, SP tai FP.

Esimerkki: I/O-käskyt

C-kieli       konekieli

Print(x);     load  r1, x    ; laita tulostettava arvo rekisteriin r1
              out   r1, =crt ; tulosta r1 arvo näytölle konekäskyllä out

Erityiskäskyt

Suorittimella on lisäksi sekalainen joukko suorittimen ja järjestelmän hallintaan liittyviä konekäskyjä. Useissa suorittimissa on erikoinen käsky NOP (no operation), mikä ei nimensä mukaisesti tee mitään. Se kuitenkin haetaan käskyjen nouto- ja suoritussyklissä normaalisti, joten se kuluttaa aikaa. Jossain tapauksissa tämä on helpoin tapa rytmittää asioita oikein.

Suorittimissa voi olla rekisterissä olevien 1-bittien lukumäärän laskemiskäsky, jota tarvitaan joidenkin salakirjoitusjärjestelmien yhteydessä tai niiden murtamiseen. Suorittimissa voi olla (etuoikeutettuja) käskyjä eri välimuistien tyhjentämiseen. Etuoikeutettuja käskyjä on myös kanta- ja rajarekistereiden lukemiseen ja asettamiseen, samoin kuin muidenkin sisäisten muistinhallintarekistereiden käsittelyyn.

Ttk-91:ssä on NOP-käsky. Siinä ei ole muita erityiskäskyjä, koska määrittely ei ole täydellinen.

— erityiskäskyesimerkki

Esimerkki: NOP-käsky

C-kieli           konekieli

if (x<y)                 load r1, x    -- onko x<y?
  y = x;                 comp r1, y
                         jnles  jatka  -- ei ole, ohita store
                         store r1, y
                  jatka  nop           -- nop-käskyyn voi hypätä

Symbolisen konekielen kääntäjän ohjauskäskyt, valekäskyt

Ohjelmien symbolisen konekielisessä esitystavassa on suorittimen konekäskyjen lisäksi mukana myös kääntäjän ohjauskäskyjä. Niiden avulla ilmaistaan mm. tilanvarauksia muuttujille ja muille tietorakenteille sekä nimiä halutuille vakioarvoille. Näitä kutsutaan joskus myös valekäskyiksi, koska ne näyttävät tavallisilta käskyiltä, mutta niistä ei tule mitään suoritettavaa konekäskyä. Ne vaikuttavat vain ohjelman kääntämisen tai latauksen aikana.

Muuttujien, taulukoiden ja vakioiden määrittelyn ohjauskäskyt ttk-91 symbolisessa konekielessä

Muuttujan tilanvaraus

Muuttujan tai vakion tilanvarauskäsky on DC (data constant). Esimerkiksi muuttujan Anna tilanvaraus ja alkuarvon asettaminen tehdään kirjoittamalla ohjelmaan näin:

Anna DC 200

Käännösvaiheessa kääntäjä varaa muuttujalle Anna yhden muistipaikan ohjelman sisäisessä muistiavaruudeta ja asettaa sen muistipaikan arvoksi 200. Käännöksessä kääntäjä käyttää hyväksi symbolien arvoa. Kääntäjä asettaa symbolitauluun symbolin Anna arvoksi muuttujan Anna osoitteen.

Huomaa, että kun käännösvaiheessa puhutaan muistipaikoista, muistiosoitteista ja osoitteista, niillä tarkoitetaan aina ohjelman sisäisen muistiavaruuden osoitteita, jotka alkavat nollasta ja päättyvät ohjelmalle varattuun kokoon vähennettynä yhdellä. Vasta ohjelmaa suorittaessa muistinhallintayksikkö MMU muuntaa ohjelman sisäisen muistiavaruuden osoitteen fyysiseksi keskusmuistiosoitteeksi.

Oletetaan, että muuttujan Anna osoite on 1000 ja ohjelmakoodissa on seuraava konekäsky:

LOAD R1, Anna

Tällöin kääntäjä kääntää tämän käskyn ikään kuin se olisi:

LOAD R1, 1000

Taulukon tilanvaraus

Taulukko on tietorakenne, joka koostuu peräkkäisistä muistipaikoista. Taulukon tilanvaraus tehdään tilanvarauskäskyllä DS (data segment). Esimerkiksi tilanvaraus taulukolle Arvosanat saadaan tehtyä seuraavasti.

Arvosanat DS 30

Tällöin kääntäjä varaa taulukolle 30 peräkkäistä muistipaikkaa ja asettaa symbolin Arvosanat arvoksi taulukon ensimmäisen osoitteen.

Oletetaan, että tuo alkuosoite on 1001 ja että ohjelmakoodissa on seuraava käsky:

STORE R1, Arvosanat(R3)

Tällöin kääntäjä kääntää sen kuin ohjelmakoodissa olisi lukenut:

STORE R1, 1001(R3)

Koska ttk-91:ssä on käytössä indeksoitu muistiinosoitus, tuo konekäsky tallentaa rekisterin R1 arvon osoitteeseen, joka saadaan laskemalla 1001 + R3 eli 1001 + rekisterin R3 arvo. Jos rekisterin R3 arvo on ennen tuon käskyn suorittamista 20, niin tällöin tuo konekäsky tallentaa rekisterin R1 arvon osoitteeseen 1001+20\ = 1021.

Symbolin arvon määrittäminen

Joskus on kätevää määritellä ohjelmoidessa joitakin vakioita, jotka kuitenkin muunnetaan jo käännösvaiheessa kokonaisluvuiksi. Tätä varten ttk-91-kääntäjässä on käytössä ohjauskäsky EQU. Sitä käytetään seuraavasti ohjelmakoodissa:

VASTAUS EQU 42

Tällöin kääntäjä asettaa symbolitauluun symbolin VASTAUS arvoksi 42. Oletetaan, että ohjelmassa on lisäksi tällainen rivi:

COMP R2, =VASTAUS

Tällöin kääntäjä kääntää kyseisen konekäskyn ikään kuin siinä olisi lukenut seuraavasti:

COMP R2, =42

Ttk-91 ohjelmaesimerkki

Ttk-91 ohjelmaesimerkki

Laske taulukon tbl alkioiden arvojen summa muuttujan sum arvoksi. Tulosta
muuttujan sum arvo.

                   -- tilanvaraukset
sum    dc    0        -- määr. ja varaa tilaa muuttujalle sum, alkuarvo 0
                      -- symbolin sum arvo on muuttujan sum osoite
tbl    ds   20        -- määrittele ja varaa tilaa 20-alkioiselle
                      -- taulukolle tbl.
                      -- symbolin tbl arvo on taulukon ensimmäisen
                      -- alkion tbl[0] osoite
lkm    equ  20        -- määrittele symboli lkm, jolla arvo 20

start  ...            -- aloita ohjelman suoritus
       ...            -- alusta taulukko tbl jollain tavalla

                      -- alusta summan laskeminen
      load r3, =lkm      -- r3=raja-arvo, aseta arvo  (välitön tied.os.)
      load r2, =0        -- r2=indeksi i, alkuarvo 0  (välitön tied.os.)
      load r1, =0        -- r1=summa, alkuarvo 0      (välitön tied.os.)
                      -- vertaa ja laske summaa
loop  comp r2, r3        -- vertailun tulos tilarekisteriin (väl. tied.os.)
      jeq done           -- poistu silmukasta lopuksi       (väl. tied.os.)
      add r1, tbl(r2)    -- lisää tbl[i] summaan           (suora muistiv.)
                      -- seuraava alkio
      add r2, =1         -- lisää indeksiin r2 luku 1   (välitön tiedonos.)
      jump loop          -- palaa testaamaan            (välitön tiedonos.)
                      -- tallenna ja tulosta summa
done  store r1, sum      -- tallenna summa muuttujaan sum  (suora muistiv.)
      load  r4, sum      -- lue r4:ään muuttujan sum arvo  (suora muistiv.)
      out   r4, =crt     -- tulosta r4:n arvo näytölle   (välitön tied.os.)
                      -- lopeta ohjelman suoritus
      svc   sp, =halt    -- kutsu käyttöjärj.palv. 11    (välitön tied.os.)
Pääsit aliluvun loppuun! Jatka tästä seuraavaan osaan:

Muistathan tarkistaa pistetilanteesi materiaalin oikeassa alareunassa olevasta pallosta!