Procesor

Počítač v našem pohledu sestává z paměti, procesoru a ostatních zařízení. Samotný procesor zahrnuje taky několik menších kousků paměti, se kterou umí přímo počítat a které se říká registry. Sice taková představa není vzhledem k současným počítačům moc přiléhavá a některá tvrzení v následujícím textu můžou být vyloženě nepravdivá, ale i tak je to užitečný náhled na věc, protože je přehledný. Popisovat dopodrobna skutečný hardware by nám přineslo spoustu komplikací, jak už to bývá.

Procesor pracuje s čísly zapsanými ve dvojkové soustavě. Teoreticky vzato je můžeme chápat jako jednotlivé bity (nuly a jedničky), ale mnohem praktičtější je seskupit bity do skupin po osmi, kterým budeme říkat byte a které tedy můžou nabývat 28 různých hodnot – obvykle čísla od 0 do 255. Když je to potřeba, můžeme podobným způsobem vyrobit skupiny po dvou bajtech, které nabývají 216 různých hodnot, anebo třeba po osmi bajtech, abychom vyjádřili čísla od 0 do 264 − 1.

Každý program sestává ze spousty drobných příkazů – instrukcí, z nichž každá je zapsaná jako několik bajtů. Instrukce může například v rámci paměti zkopírovat hodnotu z místa na místo, dvě hodnoty sečíst anebo různým způsobem ovlivnit, která instrukce se provede příště. Na běžných procesorech má instrukční sada stovky položek, a některé z těch výpočtů a příkazů jsou vyloženě obskurní. I proto jsme se v hodině omezili na mikrokontroler Atmega8, což je RISC – reduced instruction set computer.

Procesor si pamatuje instrukci, která se má provádět v příštím kroku. Prakticky vzato, každá instrukce je uložená v paměti na nějakém místě a v procesoru je speciální registr, instruction pointer nebo také program counter, který v sobě vždycky má číslo právě prováděné instrukce. Mezi dvěma kroky výpočtu se k němu vždycky přičte patřičná hodnota, aby ukazoval na následující instrukci.

Vlastně jediný nástroj, kterým může výpočet záviset na splnění nějaké podmínky anebo se opakovat dokola, je podmíněný skok. Vyskytuje se v mnoha obměnách, ale všechny principielně dělají totéž: za stanovených okolností se do instruction pointeru nastaví nějaká nová hodnota. Počítač si sám od sebe nepamatuje, co dělal před chvílí – když například chceme nějaký výpočet zopakovat právě stokrát, musíme si někde stranou napočítat do stovky a správným podmíněným skokem ten cyklus pak ukončit.

Assembler

Assembler je programovací jazyk jen kousek vzdálený od samotného strojového kódu. Namísto čísel z instrukční sady píšeme snáze zapamatovatelné zkratky příslušných instrukcí. Skoky na adresu v programu většinou řešíme záložkou (většina učebnic tomu říká návěští, což je skoro libovolné slovo, a za ním je dvojtečka. Instrukci skoku pak prostě napíšeme, na kterou záložku má skočit; kompilátor při překladu assembleru do strojového kódu vypočítá skutečnou adresu a do instrukce ji napíše za nás.

Vystačíme si s následujícími instrukcemi:

Mnohem bohatší popis téhle instrukční sady je na webu avr-asm-tutorial.net. Skutečnou instrukční sadu procesoru můžete ochutnat na Wikipedii, anebo si můžete na godbolt.org vložit nějaký nahodilý program v jazyce C++ a sledovat, na co se přeloží.

Příklad. Blikačka: nechte jeden výstup Atmegy střídavě zapnutý a vypnutý, obojí řádově sto tisíc taktů dlouho.

Řešení. .equ ddrb, 0x17 .equ portb, 0x18 start: sbi ddrb, 0 ; nastavíme, že pin 0 na portu B budeme používat jako výstup vyp: adiw x, 1 ; registr X je dvoubajtový, což je nám vhod: dovede počítat až do 65535 mov r16, xl cpi r16, 0 brne vyp mov r16, xh cpi r16, 0 brne vyp sbi portb, 0 ; rozsvítíme světlo, pokud je v registru X hodnota 0 zap: adiw x, 1 mov r16, xl cpi r16, 0 brne zap mov r16, xh cpi r16, 0 brne zap cbi portb, 0 ; zhasneme světlo, pokud je v registru X hodnota 0 rjmp vyp

Příklad. Přepínač: potom, co uživatel zmáčkne a pustí jedno tlačítko, nechte výstup zapnutý. Když zmáčkne a pustí druhé tlačítko, nechte výstup vypnutý.

Řešení. .equ portb, 0x18 .equ ddrb, 0x17 .equ pind, 0x10 start: sbi ddrb, 0 ; nastavíme, že pin 0 na portu B budeme používat jako výstup loop: sbic pind, 6 rjmp is_b sbis pind, 7 rjmp loop is_a: sbic pind, 7 rjmp is_a ; čekáme, dokud je první tlačítko pořád stisknuté cbi portb, 0 ; zhasneme světlo rjmp loop is_b: sbic pind, 6 rjmp is_b ; čekáme, dokud je druhé tlačítko pořád stisknuté sbi portb, 0 ; rozsvítíme světlo rjmp loop

Příklad. Kódový zámek: kdykoliv uživatel vymačká na tlačítkách A a B kód AABBAB, zapněte výstup, jinak ho nechte vypnutý.

Řešení. .equ buffer, 0x100 ; skoro libovolná adresa v paměti, kam budeme ukládat stisky kláves .equ portb, 0x18 .equ ddrd, 0x11 .equ pind, 0x10 mydata: .ascii "abaabb" start: .equ length, start - mydata ldi ddrd, 0x00 cbi portb, 0 ; na začátku je výstup vypnutý mainloop: sbic pind, 6 rjmp is_b sbis pind, 7 rjmp mainloop is_a: sbic pind, 7 rjmp is_a ldi r16, 'a' rjmp shift is_b: sbic pind, 6 rjmp is_b ldi r16, 'b' shift: ; následující úsek posune celý buffer o jedno místo doleva a na konec vloží obsah registru 16 sbi portb, 0 ldi xl, lo8(buffer + length) ldi xh, hi8(buffer + length) shift_loop: sbiw x, 1 ld r17, x st x, r16 mov r16, r17 mov r17, xl cpi r17, lo8(buffer) brne shift_loop mov r17, xh cpi r17, hi8(buffer) brne shift_loop ldi xl, lo8(buffer + length) ldi xh, hi8(buffer + length) ldi zl, lo8(start) ldi zh, hi8(start) check_loop: sbiw x, 1 sbiw z, 1 ld r17, x lpm cp r17, r0 brne mainloop cpi xl, lo8(buffer) brne check_loop sbi portb, 0 ; kód doběhne sem jedině, když se celý buffer rovná správnému kódu rjmp mainloop

Předcházející kód je v trochu zvláštní syntaxi pro Nasm na Linuxu, protože programování Atmegy z Windows na mém počítači nefungovalo. První dva příklady jde snad i bez úprav použít v Atmel Studiu, ten třetí ale vyžaduje poladit adresování do programové paměti (hi8, lo8 a násobení dvěma).