====== 3. Instrukční sada procesoru a přepis algoritmu ====== * pro vyučující: [[..:..:internal:tutorials:03:start|cvičení 3]] ===== Osnova cvičení ===== - Základní instrukce procesoru a jejich popis - Seznámení se simulátorem [[..:..:documentation:qtmips:start|QtMips]] - Otestování a implementace jednoduchého sčítání - Přepis programu z jazyka C do assembleru (instrukční sada MIPS) - Práce s periferií ===== Náplň cvičení ===== Zjistěte, jak jsou strojové instrukce prováděny/zpracovávány jednoduchým procesorem. Jednoduchost, přímočarost a ortogonalita kódování instrukcí je základním důvodem, proč si většina učebnic vybírá model [[https://en.wikipedia.org/wiki/MIPS_architecture|MIPS]] jako model procesorové architektury. Procesor čte instrukce zakódované v binární podobě. Osobní počítače třídy PC nám neumožňují provádět strojový kód založený na MIPS přímo na jejich procesoru (architektura [[https://en.wikipedia.org/wiki/X86|X86]]), ale existuje mnoho simulátorů této architektury. Simulátor [[..:..:documentation:qtmips:start | QtMips]] byl vyvinut jako didaktická pomůcka přímo pro tento kurz. ==== Základní instrukce - popis a použití ==== Bližší popis instrukcí: ^ Instrukce ^ Syntax ^ Operace ^ Význam ^ | add | add \$d, \$s, \$t | \$d = \$s + \$t; | Add: Sečte dva registry \$s + \$t a výsledek uloží do registru \$d | | addi | addi \$t, \$s, C | \$t = \$s + C; | Add immediate: Sečte hodnotu v \$s a znaménkově rozšířenou přímou hodnotu, a výsledek uloží do \$t | | sub | sub \$d,\$s,\$t | \$d = \$s - \$t | Subtract: Odečte znaménkově obsah registru \$t od \$s a výsledek uloží do \$d | | bne | bne \$s, \$t, offset | if \$s != \$t go to PC+4+4*offset; else go to PC+4 | Branch on not equal: Skáče pokud si registry \$s a \$t nejsou rovny | | beq | beq \$s, \$t, offset | if \$s == \$t go to PC+4+4*offset; else go to PC+4 | Branch on equal: Skáče pokud si registry \$s a \$t jsou rovny | | slt | slt \$d,\$s,\$t | \$d = (\$s < \$t) | Set on less than: Nastavi registr \$d, pokud plati podminka \$s < \$t | | sll | sll \$d,\$s,C | \$d = \$s << C | Shift Left Logical: Posune hodnotu v registru o C bitu doleva (ekvivalentni k operaci nasobeni konstantou 2C ) | | jump | j C | PC = (PC ∧ 0xf0000000) ∨ 4*C | Jump: Skáče bezpodmíněčně na návěstí C | | lw | lw \$t,C(\$s) | \$t = Memory[\$s + C] | Load word: Načte slovo z paměti a uloží jej do registru \$t | | sw | sw \$t,C(\$s) | Memory[\$s + C] = \$t | Store word: Uloží obsah registru \$t do paměti | | lui | lui \$t,C | \$t = C << 16 | Load upper immediate: Uloží předanou přímou hodnotu C do horní části registru. Registr je 32-bitový, C je 16-bitová. | | la | la \$t, LabelAddr | lui \$t, LabelAddr[31:16]; \\ ori \$t,\$t, LabelAddr[15:0] | Load Address: 32-bitové návěstí uloží do registru \$t. Jedná se o pseudoinstrukci - tzn. při překladu se rozloží na dílčí instrukce podle potřeby. | | li | li \$t, Immediate | lui \$t, Immediate[31:16]; \\ ori \$t,\$t, Immediate[15:0] | Load Immediate: 32-bitovou konstantu uloží do registru \$t. Jedná se o pseudoinstrukci shodnou s la - tzn. při překladu se rozloží na dílčí instrukce podle potřeby. | O něco podrobnější popis instrukcí na Wikipedii [[https://en.wikipedia.org/wiki/MIPS_architecture]]. Autoritativní popis architektury [[https://www.mips.com/]]: [[https://www.mips.com/?do-download=mips32-instruction-set-quick-reference-v1-01|MIPS32 Instruction Set Quick Reference v1.01]] [[https://www.mips.com/?do-download=the-mips32-instruction-set-v6-06|The MIPS32 Instruction Set v6.06]] ==== Překlad vlastního zdrojového souboru v assembleru ==== Pro psaní vlastního zdojového programu v assembleru lze použít libovolný textový textový editor (nainstalované jsou například [[https://www.vim.org/|vim]], [[https://www.gnu.org/software/emacs/|Emacs]], atd. Pro ty, co nemají vlastní preferenci, doporučujeme na začátek editor [[http://www.geany.org/|Geany]]. Připravíme jednoduchý zdrojový soubor v assembleru s názvem ''simple-lw-sw.S''. Podstatná je přípona velké "''.S''", ta je v standardních vývojových nástrojích vyhrazena pro zdrojový (Source) kód v assembleru a překladač se podle ní rozhoduje, jak bude se souborem nakládat. Další přípony jsou ''.c'' pro zdrojový kód v jazyce C, ''.cc'' nebo ''.cpp'' pro %%C++%%, ''.o'' (object file) pro objektové soubory (soubory kde je již zdrojový kód přeložen do nativních instrukcí procesoru ale ještě bez finálního umístění na adresy), Knihovní soubory ''.a'' (archive) slouží jako knihovny funkcí, které jsou do cílového programu vloženy podle potřeby. Výsledný spustitelný program pak bývá na systémech Unixového typu uložen bez přípony. .globl _start .set noat .set noreorder .text _start: // load the word from absolute address lw $2, 0x2000($0) // store the word to absolute address sw $2, 0x2004($0) loop: // stop execution wait for debugger/user break // ensure that continuation does not // interpret random data beq $0, $0, loop nop .data src_val: .word 0x12345678 dst_val: Zdrojový kód v assembleru se sestává ze * zápisu instrukcí, kdy se používají zkrácená jména operací (''lw'' - load word, ''sw'' - save word, ''add'', ''sub'' - subtract ), za identifikátorem operace mohou následovat operandy, to jsou většinou čísla nebo jiné označení registrů a dále přímé číselné hodnoty * návěští (zakončená dvojtečkou), na která se lze při zápisu instrukcí a přímých dat vkládaných do paměti odkazovat. Hodnota vkládaná na místo užití je adresa instrukce nebo datové položky, která za návěštím následuje * řídicí konstrukce (pseudo-instrukce) pro kompilátor assembleru, většinou začínají tečkou ''.'' * komentáře, v zdrojových souborech označených velkým ''.S'' probíhá před vlastním překladem fáze předzpracování se shodnými pravidly jako pro programy v jazyce C V kódu jsou použity následující řídicí příkazy * .globl - následující uvedené symboly jsou viditelné i vně kompilační jednotky. Symbol ''_start'' nebo ''_ _start'' je pak konvencí definovaný jako vstupní bod programu. * .set noat - zákaz používání pomocného registru pro realizaci některých instrukcí sestavovaných navíc assemblerem. Například ''la'' (load address), kdy se jedná o makro, které je assemblerem převedeno na sekvenci instrukcí ''lui'' a ''ori''. * .set noreorder - zákaz úpravy pořadí. Assembler je schopen optimalizovat pořadí některých instrukcí a vyplňovat delay-sloty (pro začátek pro jednoduchost zakázané), pro uvedený kód to není žádoucí * .ent jméno - označení počátku funkce * .end jméno - označení konce funkce * .text - přepnutí na plnění sekce ''.text'', do které jsou ukládané vlastní instrukce programu * .data - přepnutí na plnění sekce ''.data'', do které jsou ukládaná inicializovaná data * .word - požadavek na vložení přímo zapsané hodnoty do obsahu aktuálně plněné sekce Kompletní popis lze nalézt v manuálu [[https://sourceware.org/binutils/docs/as/index.html|GNU assembleru]]. Překlad provedeme křížovým překladačem [[..:..:documentation:mips-elf-gnu:start|mips-elf-gcc]] (křížový proto, že běží na architektuře PC, ale překládá pro architekturu MIPS). mips-elf-gcc -Wl,-Ttext,0x1000 -Wl,-Tdata,0x2000 -nostdlib -nodefaultlibs -nostartfiles -o simple-lw-sw simple-lw-sw.S Volání obsahuje množství parametrů, protože běžné programy v jazyce C jsou doplněné o inicializační sekvence a knihovní funkce. Účelem našeho výkladu je nejdříve ukázat, jak pracuje úplný základ a poté pochopit, jak může být obalen a rozšířen automaticky pracující inicializace a konstrukce, které umožňují pohodlné psaní programů na vyšší úrovni i ve vyšších jazycích. Volby ''-Wl,'' jsou přidávané sestavovacímu programu (linkeru) a určují, na jaké adresy bude umístěna sekce s instrukcemi ''.text'' a datová sekce ''.data''. Další volby postupně zakazují vložení standardní startovací sekvence (''-nostartfiles'') a automatické použití knihoven. Volba ''-o'' a následující argument určují jméno výstupního souboru (output). Dále následuje jeden nebo více zdrojových souborů. Zdrojový soubor k projektu **simple-lw-sw** a příslušné instrukce pro kompilaci (Makefile) jsou dostupné v adresáři ''/opt/apo/qtmips-semstart''. ==== Soubor buble_sort.S ==== Pro kompilaci interním překladačem QtMIPS využijte tuto šablonu buble_sort.S: // Directives to make interresting windows visible #pragma qtmips show registers #pragma qtmips show memory .globl _start .set noat .set noreorder .text _start: lui $8,0x2 .data .org 0x20200 src_val: .word 100,20,200,50,45,30,2,58,70,35 // Specify location to show in memory window #pragma qtmips focus memory src_val Pro překlad z C do assembleru zkuste soubor init_array.c: int pole[10]; int _start() { int N = 10,i; for(i=0; i Překlad do assembleru pro MIPS a zobrazení přeložených instrukcí provedeme následujícími příkazy. mips-elf-gcc -nostdlib -g init_array.c -o init_array mips-elf-objdump --source init_array Na Windows je možné nainstalovat buď kompletní [[http://mingw.org/|MSys]] s make nebo používat jen holý kompilátor [[..:..:documentation:mips-elf-gnu:start|mips-elf-gcc-i686-mingw32]] a dávkový soubor. PATH=%PATH%;c:\path\to\mips-elf-gcc-i686-mingw32\bin mips-elf-gcc -Wl,-Ttext,0x1000 -Wl,-Tdata,0x2000 -nostdlib -nodefaultlibs -nostartfiles -o simple-lw-sw test.S Pro provedení programu použijeme simulátor [[..:..:documentation:qtmips:start|QtMips]]. Volíme nejednodušší variantu procesoru "No pipeline no cache". Do pole "Elf executable" vložíme po vyhledání přes tlačítko "Browse" zkompilovaný soubor ''simple-lw-sw'' (bez přípony). {{..:qtmips-newdialog-basic.png|}} Dále v záložce dialogového okna ''Core'' vypneme položku ''Delay slot''. Tím se sice simulátor odkloní od reálného chování architektury MIPS, ale návrh programů v režimu ''set .noreorder'' bude přímočaře odpovídat předpokládanému chování skokových instrukcí. Později se k problematice vrátíme. {{..:qtmips-newdialog-core-nods.png}} V zobrazeném diagramu dvojklikem na čítači instrukcí (PC) otevřeme okno s programem. Dvojklikem na bloku zápisníkové paměti (Registers) je otevřený náhled na soubor registrů a nakonec dvojklikem na datech dojde k zobrazení paměti. Doporučené rozložení oken může být následující {{..:qtmips-basic-layout.png}} V okně "Program" navolíme "Follow fetch", kdy je vždy v okně vybraná právě procesorem načtená instrukce/řádka. V okně "Memory" nastavíme adresu 0x2000 na kterou byla umístěna data 0x12345678. Program krokujeme příkazem v menu "Machine" -> "Step" nebo z nástrojové lišty příslušným tlačítkem. Hodnota je nejdříve načtena do registru a poté zapsaná do paměti o jednu buňku dále. Nyní upravte program tak, aby prováděl kopírování periodicky. Při úpravách bude potřeba vypustit instrukci ''break'', která slouží k zastavení/přechodu do ladícího stavu. Vyzkoušejte, že modifikovaná hodnota na adrese 0x2000 je vždy překopírovaná na adresu 0x2004. Modifikujete program tak, aby sčítal dvě vstupní hodnoty na adresách 0x2000 a 0x2004 a výsledek ukládal na adresu 0x2008. Dále program upravte tak, aby sčítal dva vektory o délce čtyř slov. Využijte k nastavení registru na počáteční adresu vektoru makroinstrukce assembleru ''la vect_a'' (load address). ... .data vect_a: .word 0x12345678 .word 0x12345678 .word 0x12345678 .word 0x12345678 vect_b: .word 0x12345678 ... Dále navrhněte program pro počítání průměru z osmi čísel. ==== Použití Makefile pro řízení překladu ==== Volání kompilátoru je vhodné minimálně dokumentovat a lépe zautomatizovat. Jednou z možností je vytvořit skript pro [[https://en.wikipedia.org/wiki/Shell_(computing)|shell]] (na GNU/Linuxu obvykle [[https://www.gnu.org/software/bash/|BASH]] nebo [[https://en.wikipedia.org/wiki/Almquist_shell|DASH]]). Při překladu větších projektů je ale nepraktické překládat všechny kompilační jednotky opakovaně, když dojde k malé změně jen v některém ze zdrojových souborů. proto bylo vytvořeno množství programů speciálně navržených pro účel překladu a sestavení programu ([[https://en.wikipedia.org/wiki/Make_%28software%29|Make]], [[https://ant.apache.org/|Ant]], [[https://en.wikipedia.org/wiki/Qmake|qmake]], [[https://cmake.org/|Cmake]], [[http://mesonbuild.com/|meson]], atd.). ===Makefile=== [[https://en.wikipedia.org/wiki/Make_%28software%29|Make]] je nástroj pro automatickou kompilaci zdrojových kódů, potřebná konfigurace je uložena v souboru //Makefile//. //Makefile// šablona pro překlad zdrojového kódu v assembleru: ARCH=mips-elf CC=$(ARCH)-gcc AS=$(ARCH)-as LD=$(ARCH)-ld OBJCOPY=$(ARCH)-objcopy CFLAGS += -ggdb -O1 AFLAGS += -ggdb LDFLAGS += -ggdb LDFLAGS += -nostdlib -nodefaultlibs -nostartfiles LDFLAGS += -Wl,-Ttext,0x1000 -Wl,-Tdata,0x2000 all:default .PHONY:clean %.srec:% $(OBJCOPY) -O srec $< $@ %.o:%.S $(CC) -D__ASSEMBLY__ $(AFLAGS) -c $< -o $@ %.s:%.c $(CC) $(CFLAGS) $(CPPFLAGS) -S $< -o $@ %.o:%.c $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@ # default output default:change_me.srec # executable file:object file.o change_me:change_me.o $(CC) $(LDFLAGS) $^ -o $@ # all generated that would be cleaned clean: rm -f change_me change_me.o change_me.out change_me.srec Odsazení řádku v Makefile musí být pomocí **tabulátoru**. Makefile nerozezná odsazení pomocí mezer. Sestavovací soubor //Makefile// se sestává z definic (přiřazení hodnot proměnným) a pravidel. Pravidla začínají řádkou, která určuje závislost cílů na jejich závislostech uvedených za dvojtečkou. Pravidla mohou přímo uvádět jména souborů nebo mohou být obecná, kdy zástupným znakem pro doplňovanou část jména je znak procento "%". Kompletnější šablonu pro překlad na cílovou architekturu MIPS i s nalezením závislostí na hlavičkových souborech naleznete na počítačích v laboratoři v adresáři ''/opt/apo/qtmips_template''. ===Kompilace=== Kompilace probíhá příkazem //make// (příkaz //make// je nutné zadávat v adresáři, ve kterém je upravený Makefile a zdrojový kód programu). //Make// vygeneruje několik souborů, z nichž pro import do simulačního prostředí QtMips použijeme soubor bez koncovky. Překlad nejdříve převádí kompilační jednotky (jednotka je zjednodušeně jeden zdrojový soubor včetně vložených hlavičkových souborů) do objektového tvaru (''.o'') v relokovatelné podobě. Objektové soubory jsou linkerem složeny, vzájemné reference/odkazy vyřešeny a provedeno umístění na finální adresy. Pro uložení vlastních instrukcí strojového kódu je potřeba přidat obálku s informacemi, kde je potřeba kód upravit při konečném umístění ''.o'' souborů. Data a instrukce v konečném spustitelném tvaru je také potřeba doplnit informací na které adresy má být daná část/sekce umístěna. K uložení těchto informací je v našem případě a obecně na většině standardům odpovídajících systémů použitý formát [[https://en.wikipedia.org/wiki/Executable_and_Linkable_Format|ELF]] (Executable and Linkable Format). ===Zjištění adres proměnných a korespondence zdrojového kódu a výsledného ELF souboru=== Zjištění adres, na které byly jednotlivé funkce a datové položky linkerem umístěny lze zjistit mips-elf-nm program Rozmístění sekcí mips-elf-objdump --headers program Porovnání vstupu a výsledku překladu mips-elf-objdump --source program ==== Přepis programu z jazyka C do asembleru ==== V praktických aplikacích se častokrát setkáváme s použitím mediánového filtru. Ten nám pomáhá odstranit ze signálu (nebo obrazu) zcela zjevné výkmity (nebo poškozené pixely). Narozdíl od průměrovacího filtru, který spočítá aritmetický průměr nejakého okolí a stávající hodnotu signálu nahradí vypočteným průměrem, mediánový filtr ji nahradí prostřední hodnotou (mediánem) tohoto okolí. Pro realizaci mediánového filtru je potřebné nejdříve seřadit všechny hodnoty a pak z nich vybrat onu prostřední. Klíčovou roli zde sehrává řazení. Mějme následující problém. V datové paměti nech je uloženo N celých čísel (1 < N < 21) počínaje od nějaké adresy (například 0x00), přičemž jedno číslo v paměti zabírá velikost jednoho slova. Našim úkolem je uvedená čísla vzestupně seřadit. Nejsnažší způsob jak tento problém řešit je použít bublinkové řazení. Princip tohoto algoritmu spočívá v tom, že se postupně a opakovaně prochází seřazované pole, přičemž se porovnávají každé dva sousedící prvky, a pokud nejsou ve správném pořadí, prohodí se. Hodnotu N si zvolte sami. int pole[5]={5,3,4,1,2}; int main() { int N = 5,i,j,tmp; for(i=0; i \\ Princip algoritmu: Řazení prvků se uskutečňuje ve dvou vnořených cyklech - vnějším a vnitřním. Vnější cyklus zabezpečuje opakované spouštění vnitřního cyklu. Vnitřní cyklus postupně procházení pole a vzájemně vyměňuje dvě sousedící položky pokud nejsou v správném pořadí (má být menší vlevo, větší napravo). Protože vnitřní cyklus prochází polem zleva doprava (od menších indexů k větším) důsledkem je, že na konci pole se objeví vždy největší prvek z celého pole - největší prvek "probublal" na konec pole. Proto při dalším spuštění vnitřního cyklu nemusíme již procházet celým polem, ale jenom doposud neseřazenou částí tohoto pole. Postačuje N spuštění vnitřního cyklu aby se pole seřadilo.\\ \\ 5, 3, 4, 1, 2 --> počáteční stav \\ 3, 4, 1, 2, **5** --> po prvním průchodu vnějším cyklem \\ 3, 1, 2, **4**, **5** --> po druhém průchodu vnějším cyklem \\ 1, 2, **3**, **4**, **5** ... \\ 1, **2**, **3**, **4**, **5** \\ **1**, **2**, **3**, **4**, **5** --> po pátém průchodu cyklem - seřazeno \\ \\ Přepište výše uvedený program z jazyka C do asembleru. Vykonávání programu oveřte v simulátoru Mips. Tento program budete potřebovat na příštím cvičení - to co nestihnete na cvičeních bude nutno dodělat doma.. \\ Můžete využít předpřipravenou šablonu k tomuto úkolu: .globl pole .globl start .set noat .set noreorder .text .ent start start: // Zde muzete psat Vas program nop .end start .data .align 2 pole: .word 5,3,4,1,2 \\ ==== Příklady přepisu krátkých fragmentů kódu z jazyka C do asembleru ==== ^ Příkaz **//if//** ^^ | if (i ==j) f = g + h; f = f – i; | // s0=f, s1=g, s2=h, s3=i, s4=j bne $s3, $s4, L1 // Pokud i!=j, skoč na L1 add $s0, $s1, $s2 // if blok: f=g+h L1: sub $s0, $s0, $s3 // f = f-i | ^ Příkaz **//if-else//** ^^ | if (i ==j) f = g + h; else f = f – i; | // s0=f, s1=g, s2=h, s3=i, s4=j bne $s3, $s4, else // Když i!=j, skoč na else add $s0, $s1, $s2 // if blok: f=g+h j L2 // přeskoč blok else else: sub $s0, $s0, $s3 // blok else: f = f-i L2: | ^ Cyklus **//while//** ^^ | int pow = 1; int x = 0; while(pow != 128) { pow = pow*2; x = x + 1; } | // s0=pow, s1=x addi $s0, $0, 1 // pow = 1 addi $s1, $0, 0 // x = 0 addi $t0, $0, 128 // t0 = 128 pro porovnávání while: beq $s0, $t0, done // Když pow==128, ukončení cyklu while sll $s0, $s0, 1 // pow = pow*2 addi $s1, $s1, 1 // x = x+1 j while done: | ^ Cyklus **//for//** ^^ | int sum = 0; int i; for(i=0; i!=10; i++) { sum = sum + i; } | //Lze nahradit while: int sum = 0; int i = 0; while(i!=10) { sum = sum + i; i++; } | //ale zde i rychlejším do-while, //z konst.mezí víme,že se vždy vykoná: int sum = 0; int i = 0; do { sum = sum + i; i++; } while(i!=10) | ^ Načtení slova z datové paměti ^^ | // Jenom pro účely ukázky... int a, *pa=0x80020040; int b, *pb=0x80020044; int c, *pc=0x00001234; a = *pa; b = *pb; c = *pc; | // s0=pa (bazova adresa), s1=a, s2=b, s3=c lui $s0, 0x8002 // pa = 0x80020000; lw $s1, 0x40($s0) // a = *pa; lw $s2, 0x44($s0) // b = *pb; addi $s0, $0, 0x1234 // pc = 0x00001234; lw $s3, 0x0($s0) // c = *pc; | ^ Inkrementování prvků pole ^^ | int pole[4] = { 7, 2, 3, 5 }; int main() { int i,tmp; for(i=0; i<4; i++) { tmp = pole[i]; tmp += 1; pole[i] = tmp; } return 0; } | Kompletní program v prostředí QtMips: .globl pole // nazev "pole" bude viditelny pro linker .data // direktiva oznacujici zacatek datove casti .align 2 // zarovnani dat po slovech (4 Bytech) pole: // pojmenovani mista v pameti .word 7, 2, 3, 5 // inicializace pole... .text // zacatek textove casti / programu .globl _start // opet symbol viditelný pro linker .ent _start // vstupní bod _start: // la je pseudoinstrukce la $s0, pole // ulozi do registru s0 adresu pocatku pole addi $s1, $0, 0 // inicializacni prikaz cyklu for: i=0, kde i=s1 addi $s2, $0, 4 // nastaveni horni meze cyklu for: // mame konst. meze, lze prelozit jako do-while lw $s3, 0x0($s0) // nacteni polozky pole do registru s3 addi $s3, $s3, 0x1 // inkrementace registru s3 sw $s3, 0x0($s0) // prepsani (ulozeni) hodnoty registru s3 do pole addi $s0, $s0, 0x4 // posun na dasli polozku pole addi $s1, $s1, 0x1 // +1 k pocitadlu poctu pruchodu cyklem (i++) bne $s1, $s2, for // kdyz s1!=s2 opakuj cyklus skokem na navesti for konec: nop .end _start | \\ ==== Periferie mapované do paměťového adresního prostoru ==== Simulátor [[https://github.com/cvut/QtMips/|QtMips]] nabízí i několik jednoduchých periferií, které jsou mapované do paměťového adresního prostoru. První periferií je jednoduchý sériový port ([[https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter|UART]]) napojený na okénko terminálu. Rozložení a funkce registrů odpovídá emulátorům [[http://spimsimulator.sourceforge.net/|SPIM]] a [[http://courses.missouristate.edu/KenVollmar/MARS/|MARS]]. Ty mapují sériový port od adresy 0xffff0000. V simulátoru QtMips je mapovaný jak na tuto adresu, tak na adresu 0xffffc000, která je dosažitelná absolutním adresováním v instrukcích LW a SW proti registru zero. ^ Adresa ^ Symbolické označení ^ Bity ^ Popis ^ | 0xffffc000 | SERP_RX_ST_REG | | Stavový registr přijímače znaků z terminálu | | | | 0 | Pokud je 1 tak je v SERP_RX_DATA_REG nový přijatý znak | | | | 1 | Natavením na 1 povoluje přerušení od příjmu [[https://github.com/cvut/QtMips/blob/master/README.md#interrupts-and-coprocessor-0-support|podrobněji]] | | 0xffffc004 | SERP_RX_DATA_REG | 7 .. 0 | ASCII kód přijatého znaku | | 0xffffc008 | SERP_TX_ST_REG | | Stavový registr vysílače znaků na terminál | | | | 0 | Pokud je 1 tak je vysílač připravený na zápis znaku | | | | 1 | Natavením na 1 povoluje přerušení od vysílání [[https://github.com/cvut/QtMips/blob/master/README.md#interrupts-and-coprocessor-0-support|podrobněji]] | | 0xffffc00c | SERP_TX_DATA_REG | 7 .. 0 | ASCII kód vysílaného znaku | Další periferie emuluje interakci s reálnými prvky zařízení. Tato periferie přesně odpovídá rozložením registrů a bitů zjednodušené periferii otočných voličů a LED indikátorů, která je použitá pro vstup a výstup na vývojových kitech [[..:..:documentation:mz_apo:start|MicroZed APO]], na které budou použité v semestrálních úlohách. ^ Adresa ^ Symbolické označení ^ Bity ^ Popis ^ | 0xffffc104 | SPILED_REG_LED_LINE | 31 .. 0 | Slovo zobrazené binárně, dekadicky a hexadecimálně | | 0xffffc110 | SPILED_REG_LED_RGB1 | 23 .. 0 | Zápis RGB hodnot do PWM registrů pro RGB LED 1 | | | | 23 .. 16 | Červená složka R | | | | 15 .. 8 | Zelená složka G | | | | 7 .. 0 | Modrá složka B | | 0xffffc114 | SPILED_REG_LED_RGB2 | 23 .. 0 | Zápis RGB hodnot do PWM registrů pro RGB LED 2 | | | | 23 .. 16 | Červená složka R | | | | 15 .. 8 | Zelená složka G | | | | 7 .. 0 | Modrá složka B | | 0xffffc124 | SPILED_REG_KNOBS_8BIT | 31 .. 0 | Filtrované hodnoty voličů jako 8 bit čísla | | | | 7 .. 0 | Nastavení modrého voliče B | | | | 15 .. 8 | Nastavení zeleného voliče G | | | | 23 .. 16 | Nastavení natočení červeného voliče R | ==== Příklad na práci s perifériemi ==== .globl _start .ent _start .set noat .set noreorder .text _start: li $8, 0xffffc100 // base address into memory mapped I/O area loop: lw $9, 0x24($8) // load packed knob value into $9 (from addr. 0xffffc124) // depack $9 and store individual knob values into registers andi $12, $9, 0xFF // $12 <-blue knob is in bits 7..0 srl $1, $9, 8 // $11 <-green knob is in bits 15..8 andi $11, $1, 0xFF srl $1, $9, 16 // $10 <-red knob is in bits 23..16 andi $10, $1, 0xFF sw $9, 0x10($8) //write packed knobs to RGB led_1,(to addr. 0xffffc110) sw $9, 4($8) // and also to LED-line word-box of QtMips,(to addr. 0xffffc104) // bit negation of the packed knob value addi $1, $0, -1 // $1 = 0xffffffff xor $1,$1,$9 // xor with all 1 performs the bit negation of $9 sw $1, 0x14($8) // write negated value of the packed knob to RGB led_2 // (to addr. 0xffffc114) beq $0, $0, loop // repeat rd/wr in endless loop nop .end _start ==== Analýza výsledku kompilace ==== Jednoduchý program pro čtení polohy otočných voličů a převodu hodnoty na barvu a textový výstup naleznete na laboratorních počítačích v adresáři ''/opt/apo/binrep/qtmips_binrep''. Archiv ke stažení je k dispozici {{..:qtmips_binrep.tar.gz|zde}}. Můžete také použít šablony z repozitáře https://gitlab.fel.cvut.cz/b35apo/stud-support v adresari ''seminaries/binrep'' Zdojový kód je zkompilovaný sekvencí příkazů mips-elf-gcc -D__ASSEMBLY__ -ggdb -fno-lto -c crt0local.S -o crt0local.o mips-elf-gcc -ggdb -Os -Wall -fno-lto -c qtmips_binrep.c -o qtmips_binrep.o mips-elf-gcc -ggdb -nostartfiles -static -fno-lto crt0local.o qtmips_binrep.o -o qtmips_binrep Následně je obsah binárního výstup ve formátu ELF převedený do textové podoby příkazem mips-elf-objdump --source -M no-aliases,reg-names=numeric qtmips_binrep A doplněný o komentáře. qtmips_binrep: file format elf32-bigmips Disassembly of section .text: 00400018
: /* * The main entry into example program */ int main(int argc, char *argv[]) { 400018: 27bdffe8 addiu $29,$29,-24 allocate space on the stack for main() function stack frame 40001c: afbf0014 sw $31,20($29) save previous value of the return address register to the stack. while (1) { uint32_t rgb_knobs_value; unsigned int uint_val; rgb_knobs_value = *(volatile uint32_t*)(mem_base + SPILED_REG_KNOBS_8BIT_o); 400020: 8c04c124 lw $4,-16092($0) Read value from the address corresponding to the sum of "SPILED_REG_BASE" and "SPILED_REG_KNOBS_8BIT_o" peripheral register offset LW is instruction to load the word. Address is formed from the sum of register $0 (fixed zero) and -16092, which is represented in hexadecimal as 0xffffc124 i.e., sum of 0xffffc100 and 0x24. The read value is stored in register $4. 400024: 00000000 sll $0,$0,0x0 one NOP instruction to ensure that load finishes before the further value use. 400028: 00041027 nor $2,$0,$4 Compute bit complement "~" of the value in the register $4 and store it into register $2 *(volatile uint32_t*)(mem_base + SPILED_REG_LED_LINE_o) = rgb_knobs_value; 40002c: ac04c104 sw $4,-16124($0) Store RGB knobs values from register $4to the "LED" line register which is shown in binary decimal and hexadecimal on the QtMips target. Address 0xffffc104 *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB1_o) = rgb_knobs_value; 400030: ac04c110 sw $4,-16112($0) Store RGB knobs values to the corresponding components controlling a color/brightness of the RGB LED 1 Address 0xffffc110 *(volatile uint32_t*)(mem_base + SPILED_REG_LED_RGB2_o) = ~rgb_knobs_value; 400034: ac02c114 sw $2,-16108($0) Store complement of RGB knobs values to the corresponding components controlling a color/brightness of the RGB LED 2 Address 0xffffc114 /* Assign value read from knobs to the basic signed and unsigned types */ uint_val = rgb_knobs_value; the read value resides in the register 4, which correspond to the first argument register a0 /* Print values */ serp_send_hex(uint_val); 400038: 0c100028 jal 4000a0 40003c: 00000000 sll $0,$0,0x0 call the function to send hexadecimal value to the serial port, one instruction after JAL is executed in its delay-slot, PC pointing after this instruction (0x400040) is stored to the register 31, return address register serp_tx_byte('\n'); 400040: 0c100020 jal 400080 400044: 2404000a addiu $4,$0,10 call routine to send new line character to the serial port. The ASCII value corresponding to '\n' is set to argument a0 register in delay slot of JAL. JAL is decoded and in parallel instruction addiu $4,$0,10 is executed then PC pointing to the address 0x400048 after delay slot is stored to return address register and next instruction is fetch from the JAL instruction target address, start of the function serp_tx_byte 400048: 1000fff5 beqz $0,400020 40004c: 00000000 sll $0,$0,0x0 branch back to the start of the loop reading value from the knobs 00400050 <_start>: la $gp, _gp 400050: 3c1c0041 lui $28,0x41 400054: 279c90e0 addiu $28,$28,-28448 Load global data base pointer to the global data base register 28 - gp. Symbol _gp is provided by linker. addi $a0, $zero, 0 400058: 20040000 addi $4,$0,0 Set regist a0 (the first main function argument) to zero, argc is equal to zero. addi $a1, $zero, 0 40005c: 20050000 addi $5,$0,0 Set regist a1 (the second main function argument) to zero, argv is equal to NULL. jal main 400060: 0c100006 jal 400018
nop 400064: 00000000 sll $0,$0,0x0 Call the main function. Return address is stored in the ra ($31) register. 00400068 : quit: addi $a0, $zero, 0 400068: 20040000 addi $4,$0,0 If the main functio returns, set exit value to 0 addi $v0, $zero, 4001 /* SYS_exit */ 40006c: 20020fa1 addi $2,$0,4001 Set system call number to code representing exit() syscall 400070: 0000000c syscall Call the system. 00400074 : loop: break 400074: 0000000d break If there is not a system try to stop the execution by invoking debugging exception beq $zero, $zero, loop 400078: 1000fffe beqz $0,400074 nop 40007c: 00000000 sll $0,$0,0x0 If even this does not stop execution, command CPU to spin in busy loop. void serp_tx_byte(int data) { 00400080 : while (!(serp_read_reg(SERIAL_PORT_BASE, SERP_TX_ST_REG_o) & SERP_TX_ST_REG_READY_m)); 400080: 8c02c008 lw $2,-16376($0) 400084: 00000000 sll $0,$0,0x0 Read serial port transmit status register, address 0xffffc008 while (!(serp_read_reg(SERIAL_PORT_BASE, SERP_TX_ST_REG_o) & 400088: 30420001 andi $2,$2,0x1 40008c: 1040fffc beqz $2,400080 400090: 00000000 sll $0,$0,0x0 Wait again till UART is ready to accept character - bit 0 is not zero. NOP in the delayslot. *(volatile uint32_t *)(base + reg) = val; 400094: ac04c00c sw $4,-16372($0) write value from register 4 (the first argument a0) to the address 0xffffc00c (SERP_TX_DATA_REG_o) serial port tx data register. } 400098: 03e00008 jr $31 40009c: 00000000 sll $0,$0,0x0 jump/return back to continue in callee program address of the next fetch instruction is read from the return address register 32 ra void serp_send_hex(unsigned int val) { 004000a0 : 4000a0: 27bdffe8 addiu $29,$29,-24 allocate space on the stack for the routine stack frame 4000a4: 00802825 or $5,$4,$0 copy value of the fisrt argument regsiter 4 (a0) to the register 5 for (i = 8; i > 0; i--) { 4000a8: 24030008 addiu $3,$0,8 set the value of the register 3 to the 8 4000ac: afbf0014 sw $31,20($29) save previous value of the return address register to the stack. char c = (val >> 28) & 0xf; 4000b0: 00051702 srl $2,$5,0x1c shift value in register 5 right by 28 bits and store result in the register 2 4000b4: 304600ff andi $6,$2,0xff abundant operation to limit value range to the character type variable and store result in the register 6 if (c < 10 ) 4000b8: 2c42000a sltiu $2,$2,10 set register 2 to one if the value is smaller than 10 c += 'A' - 10; 4000bc: 10400002 beqz $2,4000c8 4000c0: 24c40037 addiu $4,$6,55 if value is larger or equal (register 2 is 0/false) then add value 55 ('A' - 10)..(0x41 - 0xa) = 0x37 = 55 to the register 6 and store result in the register 4. This operation is executed even when the branch arm before else is executed, but result is immediately overwritten by next instruction c += '0'; 4000c4: 24c40030 addiu $4,$6,48 add value 0x30 = 48 = '0' to the value in the register 6 and store result in the register 4 - the fisrt argument a0 serp_tx_byte(c); 4000c8: 0c100020 jal 400080 4000cc: 2463ffff addiu $3,$3,-1 call subroutine to send byte to the serial port decrement loop control variable (i) in delay-slot for (i = 8; i > 0; i--) { 4000d0: 1460fff7 bnez $3,4000b0 4000d4: 00052900 sll $5,$5,0x4 the final condition of for loop converted to do {} while() loop. If not all 8 character send loop again. Shift left value in the register 5 by 4 bit positions. The compiler does not store values of local variables to the stack even does not store values in caller save registers (which requires to save previous values to the function stack frame). Compiler can use this optimization because it knows registers usage of called function serp_tx_byte(). } 4000d8: 8fbf0014 lw $31,20($29) 4000dc: 00000000 sll $0,$0,0x0 restore return address register value to that found at function start 4000e0: 03e00008 jr $31 4000e4: 27bd0018 addiu $29,$29,24 return to the caller function. Instruction in jump register delay-slot is used to restore stack pointer/free function frame. ===== Úkoly ===== - Seznámení se simulátorem a vývojovým řetězcem - Rozšíření jednoduchého programu na sčítání čísel - Práce s vektorem - Implementace výpočtu n-tého členu Fibonacciho řady - Začátek práce na programu bubble-sort - Výpis řetězce na terminál - Doplnění programu pro výpočet Fibonacciho posloupnosti o výstup na terminál ===== Odkazy ===== * [[..:..:documentation:mips-elf-gnu:start|Křížový překladač GNU pro MIPS-ELF]] - překladač kompatibilní s GNU překladačem použitým v MipsIT, balíčky pro Debian GNU/Linux x86_64 a i586, konfigurace a kompilace ze zdrojových kódů * {{..:gcc-binutils-newlib-mips-elf_4.4.4-1_mingw32.zip}} - MIPS-ELF GCC 4.4.4 zkompilované pro MinGW32 (Windows) - pro složitější programy vyžaduje přidat přepínače -lm -lgcc -lc , pro pokusy v MipsIT pak nejčastěji i další -nostdlib -nodefaultlibs -nostartfiles -Wl,-Ttext,0x80020000 * [[http://en.wikipedia.org/wiki/MIPS_architecture|Popis procesoru MIPS]] - včetně kompletní instrukční sady. * [[http://courses.missouristate.edu/KenVollmar/MARS/|MARS (MIPS Assembler and Runtime Simulator)]] * Missouri State University - Velmi povedený simulátor v jazyce Java * Vyžaduje assembler bez makro definic (předzpracovaný). Pro předzpracování lze použít např. GCC ''gcc -E assembler.S -o predzpracovany-pro-mips.s'' * [[http://www.linux-mips.org/wiki/Emulators|Emulátory MIPS na Linux-MIPS.org]]