Seriál s: Malý úvod do assembleru II (5 b)

Zadáno v čísle 23.5.

Zadání

Nejprve se na chvíli vrátím k instrukční sadě, kterou jste navrhovali. Konkrétně k jednomu z možných řešení malé délky instrukcí, které se dnes používá. Jsou jím únikové sekvence. Myšlenka je prostá – procesor přečte instrukci a podívá se například na její první bit. Pokud je tento bit nulový, procesor instrukci okamžitě vykoná. Pokud ale nalezne hodnotu jedna, uloží instrukci do předem určeného registru a do jiného registru (například do registru F) si poznamená, že narazil na únikovou sekvenci a dále již nic nevykonává. V příštím kroku pak nebere v potaz jen přečtenou instrukci, ale i poznamenanou hodnotu. Konkrétní příklad pro ADD Rd, Rr z minula:

 
 ESC A, B  ; zahajuje únikovou sekvenci, do registru E
           ; jsou zapsány adresy A, B
           ; do registru F příznak únikové sekvence
 ADD       ; najednou máme k dispozici celých 8 bitů
           ; na zakódování instrukce -- a tedy
           ; pro nás najednou není problém zakódovat
           ; libovolných 256 instrukcí se
           ; dvěma operandy

Únikové sekvence se dají zobecnit a vedou k instrukcím o různých délkách – ty, které se používají často, budou krátké, zatímco obskurnosti budou delší řetězce. Tato vlastnost je jak jejich největší výhodou, tak jejich strašlivou slabinou. Výhodou je možnost mít mnoho rozličných instrukcí s délkou závislou na užitečnosti a specifičnosti instrukcí. Nevýhodou je značná nepřehlednost výsledného strojového kódu a menší kontrola nad jeho strukturou.

Za zmínku stojí rozštěpení architektur procesorů na dva druhy: RISC a CISC. První skupina, jejíž název je zkratkou z Reduced Instrucion Set Computer, se rozhodla, že do procesoru zabuduje instrukcí jen málo. Tento přístup má výhodu v přehlednosti a jednoduchosti. Na druhou stranu si toho hodně musí zařizovat programátor sám. Druhá skupina, Complex Instruction Set Computers, usoudila, že co zadrátovat půjde, to zadrátují. Takže jejich procesory podporují velmi široké spektrum instrukcí – třeba maticové násobení nebo modifikace většího rozsahu adres paměti naráz.

Ač jsou CISCové procesory nepochybně zajímavé, pro naše účely jsou zbytečně komplikované. Náš PAPIRAS je tedy assembler RISCový – instrukcí bude málo. Jak jsme si minule ukázali, 8 bitů nám nebude stačit. Od této chvíle přecházíme na 16 bitů (tedy registry, paměť i instrukce nyní budou mít 16 bitů). To pořád není nic extra, ale už je to na jedné straně dostatečné pro většinu praktických účelů a na straně druhé dostatečně omezující na to, abychom nezpohodlněli – jeden z důvodů naší snahy naučit se práci s assemblerem je práce s omezenými zdroji. Ještě krátce odbočím do historie, abych vás dostatečně motivoval ke krokování na papíře. Původní assembler totiž sloužil jen jako mnemotechnická pomůcka pro lepší zapamatování instrukcí – programátor si sepsal kód v assembleru, potom uchopil do jedné ruky instrukční tabulku k procesoru a do druhé psadlo a jal se přepisovat assembler na jedničky a nuly. A nesměl udělat chybu. Oproti tomu je naše krokování docela jednoduché, ne?

Instrukce

Popis

NOP

No operation – procesor nedělá nic

LDI R, k

R $\leftarrow $ k (zamyslete se, je konstanta k něčím omezena? Čím?)

INC R

Přičte k registru jedna. Operace přetéká.

DEC R

Odečte od registru jedna. Operace podtéká.

ADD Rd, Rr

Rd $\leftarrow $ Rd $+$ Rr operace přetéká

SUB Rd, Rr

Rd $\leftarrow $ Rd $-$ Rr operace podtéká

ORI Rd, k

Rd $\leftarrow $ Rd OR k, bitový or

ANDI Rd, k

Rd $\leftarrow $ Rd AND k, bitový and

NOT Rd

Rd $\leftarrow $ NOT Rd, bitová negace

OR Rd, Rr

Rd $\leftarrow $ Rd OR Rr, bitový or dvou registrů

AND Rd, Rr

Rd $\leftarrow $ Rd AND Rr, bitový and dvou registrů

MOV Rd, Rr

Rd $\leftarrow $ Rr

SHL Rd, k

Rd $\leftarrow $ Rd $\ll $ k, bitový posun doleva

SHR Rd, k

Rd $\leftarrow $ Rd $\gg $ k, bitový posun doprava

STOP

ukončí chod procesoru

Tabulka 1: Seznam instrukcí. Velkým písmenem značíme registr, malým konstantu.

Jdeme programovat. Podívejte se tedy prosím na Tabulku 1. Jedná se o trochu rozšířený seznam instrukcí z minula, pro připomenutí. Také se nám bude hodit připomenout si náš procesor, který ale oproti minulému dílu rozšíříme o jeden registr. Tento registr se bude nazývat Flags (Příznaky) a budeme jej označovat zkratkou Fl.

Můžeme se k němu chovat stejně jako k registrům AH, ale spíše to dělat nebudeme – procesor jej totiž používá k ukládání informací – tzv. flagů. Pár malých programů jste si měli zapsat minule – a tehdy jste nejspíše zjistili, že PAPIRAS toho zatím neumí ani tolik jako obyčejná kalkulačka. Zejména programátorům také chyběla možnost kontrolovat chod programu – tedy větvení a skoky. Je tedy na čase si rozšířit instrukční sadu.

Budiž skoky. Skoky jsou instrukce, které modifikují registr PC. Rozlišujeme dva druhy – skoky absolutní a relativní.

[labelwidth=1.8cm, leftmargin=2cm, align=right]
JMP A

skok na adresu obsaženou v registru A je prototyp absolutního skoku. Nevýhodou je nutnost předem do nějakého registru uložit adresu, ale díky tomu můžeme skočit kamkoliv v rozsahu dostupné paměti.

JMPA k

k současné adrese přičte konstantu k

JMPS k

od současné adresy odečte konstantu k

Poslední dva skoky patří mezi skoky relativní – skáčeme o nějaký kus dopředu nebo dozadu. Dokud neřeknu jinak, budeme první instrukci nahrávat na adresu 0 a další hned za ni. Instrukce relativních skoků nemohou skákat příliš daleko – jsme omezeni délkou instrukce, takže můžeme skákat třeba o 1024 adres1. Nebudu vás nutit to dodržovat, ale rád bych, abyste si to uvědomili. Skok nám umožní například generovat Fibbonacciho čísla v registrech A, B:

    LDI B, 1    ;adresa 0x0
    LDI C, 0x2 
    ADD B, A    ;adresa 0x2
    ADD A, B
    JMP C       ;nahraje do PC registru hodnotu 0x2 a 
                ;dále tedy pokračujeme zase
                ;od instrukce ADD B, A

Zkuste si to odkrokovat a potom vymyslete, jak celý kód o instrukci zkrátit.

Se skoky úzce souvisí pojem návěstí (label). Jedná se o zkratku pro překladač, který si s její pomocí dopočítá posun, o který má skočit. Abychom nemuseli myslet na to, jestli skákat s pomocí JMPA nebo JMPS podle umístění návěstí, zavedeme si zobecňující instrukci JMPR label (Jump Relative), která skočí na návěstí nezávisle na jeho umístění. (Ve skutečnosti by si překladač vybral, kterou z instrukcí JMPA, JMPS ji má nahradit pro dané návěstí). Používání návěstí doporučuji, je to pohodlnější. Návěstí také můžeme nahrát do registru pomocí instrukce LDI – v tom případě budeme mít v registru uloženu přímo jeho adresu, využitelnou následně pro absolutní skoky. Při použití návěstí by předchozí příklad vypadal takto:

    LDI B, 1
    LDI C, vypocet      ;alternativně by bylo možno 
                        ;tuto instrukci vynechat
    vypocet:
    ADD B, A            ;a použít místo absolutního 
                        ;skoku skok relativní
    ADD A, B
    JMP vypocet

Ale skoky, jakkoliv užitečné, nám samy o sobě stačit nebudou – místo rovné cesty získáme cestu, která se bude všelijak kroutit, ale ještě stále neumíme odbočit. Nemůžeme se rozhodovat. K programování ale nějaký způsob, jak řídit chod programu, potřebujeme. A k tomu nám bude sloužit následující instrukce: BREQ label – Branch If Equal. Skoč, pokud se něco rovnalo. Ale co se mělo rovnat? A jak to zjistíme? Vzpomeňte si na registr Flags (Fl). Ten se nám teď bude hodit – procesor si do něj totiž ukládá některé informace o předchozích operacích. Pro jeden z jeho bitů se používá zkratka Z – ten je nastaven na hodnotu 1 v případě, že výsledek předchozí aritmetické operace byl nula; pokud tento výsledek nebyl nula, je bit naopak vynulován. Tedy BREQ skočí, pokud je bit Z registru Fl nastaven na hodnotu 1. Druhá instrukce ze stejného soudku je BRNE label, Branch If Not Equal, tedy opak BREQ.

Teď už toho umíme docela dost – nějaké základní aritmetické operace, skoky v programu a řízení toho, kam skáčeme. S tím už by se možná dalo udělat něco zajímavějšího.


1) Záleží na instrukční sadě a procesoru. Pro většinu našich účelů to bude stačit a když ne, máme absolutní skok.

5.5 Programování v Assembleru (5b)

Podúloha 5.5.1 – Tisíckrát nic (2b)

Napište kus kódu, který poběží přesně tisíc instrukcí a bude co nejkratší co do spotřebované paměti. Nejhorší možné řešení je tedy napsat 999 krát instrukci NOP následovanou instrukcí STOP.
Bonus: Napište kus kódu s parametrem (uloženým v registru H), který určí, kolik instrukcí se má vykonat, než skončí.

Podúloha 5.5.2 – Ach ta paměť (1b)

Registry jsou sice jednoduše přístupné, ale přeci jen je jich poněkud málo. Paměť k ukládání programu je sice poměrně velká, ale zatím do ní neumíme zapisovat. Co když budeme chtít ukládat větší množství dat? (Například při periodickém čtení nějakého senzoru.) Paměť pro data, stejně jako paměť pro program, je páska s adresami, kde každé adrese odpovídá jedno 16bitové políčko. Zamyslete se nad čtením a zápisem dat do paměti. Tedy navrhněte instrukci pro čtení a instrukci pro zápis (nebo více různých instrukcí).

Když jsme si řekli, že datová paměť vypadá stejně jako paměť programová, je vůbec nějaký důvod je mít oddělené? A pokud oddělené nebudou, jaké to bude mít důsledky?

Podúloha 5.5.3 – Malý velký Indián (2b)

S pomocí instrukcí pro práci s pamětí zkuste do paměti uložit 32bitové číslo. (Pozor, jedno políčko paměti má 16 bitů, co s tím?) Máte? Uložte ještě jedno. A teď je zkuste sečíst. Možná narazíte na problém s přenosem bitu z jedné poloviny výsledku do druhé. Pomůže nám opět registr Flags – instrukce ADD nastaví jeho carry bit, pokud při sčítání bezznaménkových čísel dojde k přetečení. Přístup k němu je sekvence instrukcí (načtu carry bit do registru A)

MOV A, Fl
ANDI A, 0b1 ;(carry bit je nultý bit registru Flags)

Pokud vám ani to nepomůže, použijte instrukci ADC Rd, Rr – Add With Carry, která provede operaci Rd $\leftarrow $ Rd + Rr + Carry bit registru Flags (přesněji Rd + Rr + (Fl AND 0b1)).
Bonus: Použili jste při ukládání do paměti Little Endian nebo Big Endian? A co jsou to ti endiáni?

A to bude pro dnešek vše. Skončili jsme přesně na hranici, za kterou věci začínají být velmi zajímavé, ale za kterou už jsou končiny poměrně obtížně schůdné.

Tomáš Bartoněk