Hlavní navigace

Učíme trpaslíky počítat podruhé

10. 4. 2008
Doba čtení: 13 minut

Sdílet

V sedmé části seriálu o architekturách počítačů si řekneme, které logické instrukce je možné provádět v aritmeticko-logické jednotce. Především se ale budeme zabývat problematikou porovnávání dvou hodnot uložených v pracovních registrech a součinností porovnávacích instrukcí s podmíněnými skoky.

Obsah

1. Logické operace prováděné v ALU
2. Logická operace AND
3. Logická operace OR
4. Logická operace XOR
5. Logická operace COM
6. Nepodmíněný skok
7. Podmíněné skoky
8. Použití instrukce CMP v součinnosti s podmíněnými skoky
9. Obsah následující části seriálu

1. Logické operace prováděné v ALU

Většina aritmeticko-logických jednotek použitých v mikroprocesorech podporuje provádění nejméně čtyř základních logických operací, ke kterým se v některých případech mohou přidat i operace další. Jedná se o operaci logického bitového součinu (AND) nad všemi korespondujícími bity pracovních registrů či jednoho pracovního registru, operaci logického součtu (OR), nonekvivalenci (XOR či EOR) a konečně operaci bitové negace (COM) všech bitů jednoho z pracovních registrů A či B.

První tři operace, tj. logický součin, logický součet a nonekvivalence, jsou operacemi binárními, což znamená, že vyžadují dva operandy (většinou oba pracovní registry), poslední operace – negace –, je operací unární, tj. na vstupu vyžaduje pouze obsah pracovního registru A či B. Všechny čtyři podporované logické instrukce jsou vypsány v následující tabulce, pro jejich podrobnější popis jsou vyhrazeny samostatné kapitoly: AND, OR, XOR a COM.

Logické instrukce
Kód instrukce (hex) Mnemotechnická zkratka instrukce Význam
06 AND operace bitového součinu nad všemi korespondujícími bity pracovních registrů (A a B)
07 OR operace bitového součtu nad všemi korespondujícími bity pracovních registrů (A a B)
08 XOR operace bitové nonekvivalence nad všemi korespondujícími bity pracovních registrů (A a B)
09 COM negace všech bitů jednoho z registrů A či B

2. Logická operace AND

V programech psaných ve strojovém kódu nebo jazyku symbolických instrukcí se velmi často používá logická operace AND, která provádí logický součin obsahu pracovního registru A s obsahem pracovního registru B (je však možné provést i logický součin pouze s registrem A či registrem B). Součin je prováděn bit po bitu. Výsledek je uložen do jednoho z pracovních registrů v závislosti na adresní části instrukce.

pc0701

Průběh logické operace AND je shodný například se součtem

Podle výsledku operace je nastaven příznak Zero flag (výsledek je nulový či nenulový), naopak příznak Carry flag si zachová původní hodnotu. Tato operace slouží jak k implementaci logického součinu s pravdivostními hodnotami (pracovní registry v tomto případě typicky obsahují hodnoty 0×0000 nebo 0×FFFF), tak i k maskování vybraných bitů jednoho z pracovních registrů. Obsah druhého pracovního registru slouží jako takzvaná maska: pokud je nějaký bit masky roven nule, je vynulován i příslušný bit prvního pracovního registru. Následují příklady použití této instrukce:

hex binárně Zero flag
operand 1 0000 0000000000000000
operand 2 0000 0000000000000000
výsledek 0000 0000000000000000 1
operand 1 2A2A 0010101000101010
operand 2 00FF 0000000011111111
výsledek 002A 0000000000101010 0
operand 1 0FF0 0000111111110000
operand 2 FF00 1111111100000000
výsledek 0F00 0000111100000000 0
operand 1 00FF 0000000011111111
operand 2 FF00 1111111100000000
výsledek 0000 0000000000000000 1

Vzhledem k tomu, že má smysl použít instrukci AND na shodný pracovní registr, existují čtyři varianty této instrukce, které jsou navzájem odlišeny adresním bytem:

Strojový kód Zápis v assembleru Význam
06 00 AND A,A A←A & A
06 01 AND A,B A←A & B
06 10 AND B,A B←A & B
06 11 AND B,B B←B & B

3. Logická operace OR

Logická operace OR je určena k provedení logického součtu (opět bit po bitu) s obsahy pracovních registrů A a B. Výsledek je uložen do registru, který tvoří první parametr instrukce, tj. je uveden na prvním místě při zápisu instrukce v assembleru. Tato operace se používá například pro nastavení určených bitů pracovního registru na jedničku (jedná se opět o maskování, i když opačného významu, než u instrukce AND) nebo pro implementaci booleovských (pravdivostních) výrazů. Podle výsledku aplikace operace OR se nastaví příznak Zero flag, zatímco obsah příznaku Carry flag zůstane v nezměněné (původní) podobě. Opět následují příklady použití této operace:

hex binárně Zero flag
operand 1 0000 0000000000000000
operand 2 0000 0000000000000000
výsledek 0000 0000000000000000 1
operand 1 2A2A 0010101000101010
operand 2 00FF 0000000011111111
výsledek 2AFF 0010101011111111 0
operand 1 0FF0 0000111111110000
operand 2 FF00 1111111100000000
výsledek FFF0 1111111111110000 0
operand 1 00FF 0000000011111111
operand 2 FF00 1111111100000000
výsledek FFFF 1111111111111111 0

U této operace je použit naprosto stejný způsob kódování adresní části instrukce, jako u logického součinu, protože má smysl provádět logickou operaci OR nad tím samým pracovním registrem (zjištění nulovosti všech bitů, vynulování příznaku Zero flag atd.).

Strojový kód Zápis v assembleru Význam
07 00 OR A,A A←A | A
07 01 OR A,B A←A | B
07 10 OR B,A B←A | B
07 11 OR B,B B←B | B

4. Logická operace XOR

Pomocí instrukce XOR, která bývá v některých assemblerech označována mnemotechnickou zkratkou EOR, se provádí operace nonekvivalence, tj. exkluzivní (výlučný) logický součet obou zadaných operandů a to opět, stejně jako u předchozích dvou instrukcí, bit po bitu. Podle výsledku této instrukce je nastaven příznak Zero flag, podobně jako u výše uvedených operací AND a OR.

Zatímco logický součet i součin se mj. používá pro implementaci logických výrazů známých z vyšších programovacích jazyků, je operace nonekvivalence používána k selektivní negaci vybraných bitů, protože ve chvíli, kdy jsou oba korespondující bity nastaveny na logickou jedničku, je výsledkem logická nula. Tuto instrukci lze využít v počítačové grafice, šifrování, generátorech pseudonáhodných posloupností čísel (RND), konstrukci kontrolních součtů či cyklických kódů (CRC) atd. Je přitom využito faktu, že dvojí použití operace XOR se stejným druhým operandem vede k tomu, že výsledkem je původní hodnota, předaná operaci v prvním kroku. Následují příklady použití, ve kterých stojí za povšimnutí „negující“ vlastnost této instrukce (poslední příklad) i to, že aplikace nonekvivalence na tu samou hodnotu dává nulový výsledek (třetí příklad).

hex binárně Zero flag
operand 1 0000 0000000000000000
operand 2 0000 0000000000000000
výsledek 0000 0000000000000000 1
operand 1 2A2A 0010101000101010
operand 2 00FF 0000000011111111
výsledek 2AD5 0010101011010101 0
operand 1 2A2A 0010101000101010
operand 2 2A2A 0010101000101010
výsledek 0000 0000000000000000 1
operand 1 0FF0 0000111111110000
operand 2 FF00 1111111100000000
výsledek F0F0 1111000011110000 0
operand 1 00FF 0000000011111111
operand 2 FF00 1111111100000000
výsledek FFFF 1111111111111111 0
operand 1 002A 0000000000101010
operand 2 FFFF 1111111111111111
výsledek FFD5 1111111111010101 0

I logickou operaci nonekvivalence je možné aplikovat na ten samý pracovní registr (registr vystupuje v roli obou vstupních operandů). Z tohoto důvodu existují čtyři varianty instrukce odlišené její adresní částí, tj. bytem umístěným za vlastní operační kód instrukce:

Strojový kód Zápis v assembleru Význam
08 00 XOR A,A A←A ^ A
08 01 XOR A,B A←A ^ B
08 10 XOR B,A B←A ^ B
08 11 XOR B,B B←B ^ B

5. Logická operace COM

V předcházející části seriálu jsme si popsali mj. způsob odečítání dvou celočíselných hodnot. Z důvodu implementace sčítačky i odčítačky tím samým obvodem se na jednom ze vstupů sčítačky nachází blok šestnácti invertorů. Ten je (samozřejmě kromě zmíněného odčítání a dále vysvětlené instrukce CMP) využit i pro provedení operace negace všech bitů jednoho z pracovních registrů A či B.

pc0702

Průběh logické operace COM A se podobá například instrukcím inkrementace a dekrementace

Jedná se o instrukci nazvanou COM, což je zkratka anglického slova complement (doplněk). Ohledně mnemotechnické zkratky této instrukce panuje poměrně velký zmatek, protože někteří výrobci mikroprocesorů (kteří většinou současně zkratky instrukcí pro svoje výrobky definují) používají zkratku NEG či NOT, a jiní naopak zkratku NEG vyhradili pro instrukci určenou pro vytvoření dvojkového doplňku, tj. změny znaménka čísla. My se však budeme držet zkratky COM, která se používá například u mikroprocesorů firmy Intel či Motorola. Následuje příklad použití této instrukce:

dec unsigned dec signed hex binárně
původní hodnota 0 0 0000 0000000000000000
po provedení COM 65535  –1 FFFF 1111111111111111
původní hodnota 42 42 002A 0000000000101010
po provedení COM 65493  –43 FFD5 1111111111010101
původní hodnota 12345 12345 3039 0011000000111001
po provedení COM 53190 –12346 CFC6 1100111111000110

Jelikož se jedná o unární operaci, je zdrojový i cílový registr zakódován pouze jedním adresním bitem, tj. celá adresní část instrukce (jednobytová) nabývá buď hodnoty 00 nebo 01:

Strojový kód Zápis v assembleru Význam
09 00 COM A A←~A
09 01 COM B B←~B
pc0703

Průběh logické operace COM B

6. Nepodmíněný skok

Dalším typem instrukcí, které v různé podobě najdeme prakticky u všech mikroprocesorů, jsou instrukce provádějící skoky na nějakou adresu v operační paměti. Implementace skoku není v případě našeho jednoduchého mikroprocesoru nijak složitá, protože v něm není využíváno zřetězené zpracování instrukcí (pipelining), což je technologie, kterou si popíšeme později (právě kvůli pipeliningu jsou dnes skoky považovány za pomalé příkazy a v moderních mikroprocesorech se pro zmírnění následků skoku používají různé prediktory).

pc0704

Způsob načítání instrukčních kódů do řadiče

Skoky dělíme na podmíněné a nepodmíněné. Skoky nepodmíněné jsou jednodušší a svou podstatou odpovídají příkazu goto známého z některých programovacích jazyků a také mnoha článků, ve kterých autoři bez hlubšího zamyšlení se nad původní myšlenkou opakují, že by se goto nemělo při strukturovaném programování používat…). V assembleru se však skoky vesele používají, neboť právě pomocí nich se vytváří základní konstrukce strukturovaného programování – podmínky a programové smyčky.

pc0705

Po provedení každé instrukce (kromě skoku) je PC v ALU zvýšen o hodnotu odpovídající počtu bytů instrukce

Nepodmíněný skok je implementován velmi jednoduše a přímočaře – za operačním kódem instrukce se nachází šestnáctibitová adresa, která je načtena do registru PC. Vzhledem k tomu, že mikroprocesor čte instrukce v závislosti na obsahu registru PC, znamená změna tohoto registru skutečný přesun na zadanou adresu, protože další instrukce bude čtena z adresy, kterou PC obsahuje.

pc0706

Provedení nepodmíněného skoku: za opkódem instrukce se nachází adresa, která je načtena a následně uložena do PC

Náš mikroprocesor má implementovánu pouze jednu podobu nepodmíněného skoku: jedná se o absolutní skok, neboť adresa uložená za operačním kódem je chápána absolutně – přepíše PC bez ohledu na jeho předchozí hodnotu. Dále uvedené podmíněné skoky jsou skoky relativní. Operační kód instrukce pro provedení nepodmíněného absolutního skoku má hodnotu 0×16, jak ostatně ukazuje následující tabulka. Při psaní programu v jazyku symbolických instrukcí (assembleru) se většinou nezapisuje adresa skoku přímo (jako číslo), ale symbolicky, tj. pomocí identifikátoru adresy (takzvaný label).

Nepodmíněný skok
Kód instrukce (hex) Mnemotechnická zkratka instrukce Význam
16 JMP nepodmíněný skok na zadanou adresu

7. Podmíněné skoky

Mnohem zajímavější jsou podmíněné skoky, které se při programování v assembleru či strojovém kódu používají pro implementaci programových smyček a konstrukcí typu if-then-else atd. Podmíněný skok je proveden či neproveden na základě nějaké podmínky. Vzhledem k tomu, že pracujeme na té nejnižší programové úrovni, tj. na úrovni strojových instrukcí, není samozřejmě možné podmínku definovat nějakým složitým a sofistikovaným způsobem – musí se jednat o operaci, kterou mikroprocesor dokáže jednoduše a rychle zpracovat.

pc0707

Provedení podmíněného skoku: za opkódem instrukce se nachází relativní adresa, která je v případě splnění podmínky přičtena k obsahu PC

Z tohoto důvodu jsou podmínky založeny na testování jednoho z příznakových bitů či negací těchto bitů, což je v našem případě Carry flag a Zero flag, tj. příznak přenosu a příznak nulovosti. Existují tedy čtyři varianty podmíněného skoku, které jsou vypsány v následující tabulce. V případě, že podmínka není splněna (tj. testovaný příznakový bit má opačnou hodnotu než očekávanou), není skok proveden, tj. mikroprocesor instrukci skoku v podstatě ignoruje a pokračuje v načtení instrukce uvedené ihned za skokem.

Podmíněné skoky
Kód instrukce (hex) Mnemotechnická zkratka instrukce Význam
1a JC podmíněný skok za předpokladu, že je nastaven příznak přenosu (Carry flag)
1b JNC podmíněný skok za předpokladu, že je vynulován příznak přenosu (Carry flag)
1c JZ podmíněný skok za předpokladu, že je nastaven příznak nulovosti (Zero flag)
1d JNZ podmíněný skok za předpokladu, že je vynulován příznak nulovosti (Zero flag)

Za operačním kódem podmíněného skoku následuje adresní část, která má velikost pouze jednoho bytu. Vzhledem k tomu, že adresní prostor našeho mikroprocesoru je šestnáctibitový (obsahující všechny adresy 0×0000 až 0×FFFF), musí existovat způsob, jak tuto jednobytovou (tj. osmibitovou) adresu zpracovat. Řešení, které je z několika hledisek optimální, spočívá v tom, že onu osmibitovou adresní část považujeme za relativní adresu (reprezentovanou ve dvojkovém doplňku), která musí být přičtena k aktuální hodnotě registru PC.

Tento způsob adresování má několik důsledků: samotná adresa je krátká, což vede na úsporné strojové instrukce a především je program používající pouze relativní skoky přemístitelný na libovolné místo v paměti, neboli realokovatelný. Co to znamená? Vzhledem k tomu, že je adresa zapsána způsobem +xx resp.  -yy, je možné program posunout v operační paměti na prakticky jakékoli místo, protože se tato relativní adresa nemusí měnit, zatímco absolutní adresa by se měnit musela.

8. Použití instrukce CMP v součinnosti s podmíněnými skoky

V předchozí kapitole jsme si řekli, že podmíněné relativní skoky jsou použity pro implementaci programových smyček a podmínek. K tomuto účelu se podmíněné skoky kombinují s některou instrukcí, která modifikuje příznakové registry. Například jednoduchou počítanou smyčku by bylo možné implementovat kombinací instrukce DEC (ta nastavuje příznak nuly, tj. Zero flag) a podmíněného skoku JNZ, který je proveden tehdy, pokud příznak nuly není nastaven:

        LD A, počet_opakování  ; počet opakování smyčky
SMYCKA  příkaz 1               ; libovolné instrukce, jejichž celková
        příkaz 2               ; délka musí být menší než cca 120 bytů
        příkaz X               ; (kvůli omezení relativního skoku)
        DEC A                  ; snížení čítače smyčky o jedničku
        JNZ SMYCKA             ; přeloží se jako relativní skok -xx 

Spolu s podmíněnými skoky se velmi často používá ještě nepopsaná instrukce CMP. Ve své podstatě se jedná o běžné odečítání, tj. instrukci SUB, ovšem s tím rozdílem, že výsledek, tj. rozdíl není nikam zapsán, což znamená, že se obsah pracovních registrů provedením této instrukce nezmění. Na první pohled může vypadat tato instrukce nesmyslně – proč se mají odečítat dvě hodnoty, když se výsledek hned zapomene? Ovšem samotný výsledek není vše, protože při odečítání si mikroprocesor v příznakových registrech zapamatuje i to, zda byla odečítaná čísla shodná (Zero flag je roven jedné) či zda byla druhá hodnota větší než hodnota první (Carry flag je roven jedné). Instrukce CMP je vypsána v následující tabulce:

Testování a porovnání
Kód instrukce (hex) Mnemotechnická zkratka instrukce Význam
0f CMP aritmetické porovnání obsahu registrů a ovlivnění příznaků
pc0708

Průběh instrukce CMP A, B

Tato instrukce existuje ve čtyřech variantách lišících se porovnávanými registry (zde záleží na pořadí operandů, stejně jako u odčítání!):

Strojový kód Zápis v assembleru Význam
0f 00 CMP A,A A-A
0f 01 CMP A,B A-B
0f 10 CMP B,A B-A
0f 11 CMP B,B B-B

Uveďme si několik příkladů, jak se pomocí instrukce CMP nastavují příznakové bity:

menšenec menšitel Zero flag Carry flag
0000 0000 1 0
0010 0005 0 0
0010 0010 1 0
0005 0010 0 1

Z těchto příkladů vyplývá to, jak můžeme příznakové bity použít. Jestliže je zapotřebí testovat dvě hodnoty na rovnost, postačí zjistit, zda je Zero flag nastavený na jedničku. Pokud potřebujeme otestovat, jestli je první hodnota menší než druhá, lze zjistit hodnotu příznaku Carry flag atd. Test na nulovou hodnotu lze provést odečtením nuly – výsledek bude uložen v Zero flagu:

; test na rovnost dvou hodnot
LD  A, hodnota 1
LD  B, hodnota 2
CMP A,B
JZ  JE_ROVNO    ; skok na kód provedený v případě rovnosti
JNZ NENI_ROVNO  ; skok na kód provedený v případě nerovnosti


; zjištění relace dvou čísel
LD  A, hodnota 1
LD  B, hodnota 2
CMP A,B
JNC A_JE_VETSI_NEBO_ROVNO_B
JC  A_JE_MENSI_NEZ_B


; test na nulovost
LD  A, hodnota
SUB B,B          ; vynulování registru B (lze i XOR B,B)
CMP A,B
JZ  A_JE_NULOVE



; počítaná smyčka s krokem
        LD A, 0xFFFF-počáteční_hodnota*krok ; (vypočítá assembler v době překladu)
        LD B, krok
SMYCKA  příkaz 1               ; libovolné instrukce, jejichž celková
        příkaz 2               ; délka musí být menší než cca 120 bytů
        příkaz X               ; (kvůli omezení relativního skoku)
        ADD A,B                ; zvýšení čítače smyčky o krok
        JNC SMYCKA             ; příznak se nastaví při překročení 0xFFFF

; jak se bude měnit hodnota A při počátečním nastavení A=0xFFF0 a B=0x0002:
reg. A   Carry flag
0xFFF0   0
0xFFF2   0
0xFFF4   0
0xFFF6   0
0xFFF8   0
0xFFFA   0
0xFFFC   0
0xFFFE   0
0x0001   1 : konec smyčky (důvod, proč nepoužít JNZ v tomto typu smyčky) 

Tímto způsobem se dají vytvářet i další podmínky či smyčky. Další příklady si uvedeme příště, při vysvětlování instrukcí pro rotace a aritmetické posuvy.

pc0709

Průběh instrukce CMP A, A

skoleni

9. Obsah následující části seriálu

V další části seriálu o činnosti počítače budeme pokračovat v popisu instrukčního souboru našeho jednoduchého cvičného mikroprocesoru. Řekneme si, jakým způsobem je možné načítat konstanty do pracovních registrů, popíšeme si instrukce pro přesun obsahu pracovních registrů z a do operační paměti a také dokončíme část věnovanou skokům popisem instrukcí pro skok do podprogramu a návratu z podprogramu, včetně dvou instrukcí určených pro práci se zásobníkem. Budeme tedy znát všechny instrukce potřebné pro programování ve strojovém kódu nebo jazyku symbolických instrukcí (assembleru).

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.