====== 2. Instrukční sada procesoru ====== * pro vyučující: [[..:..:internal:tutorials:02:start|cvičení 2]] ===== Osnova cvičení ===== - seznámení se simulátorem [[..:..:documentation:qtmips:start|QtMips]] - otestování a implementace jednoduchého sčítání - další navazující úlohy - zadání 1. domácího úkolu (na stránce [[https://dcenet.felk.cvut.cz/apo/]] ===== Co bych si měl na cvičení zopakovat/připravit ===== - Význam a použití základních instrukcí procesoru - Seznámení se simulátorem architektury MIPS [[..:..:documentation:qtmips:start|QtMips]] a simulace vykonávání jednoduchého programu - Upozornění na 1. domácí úkol ([[https://dcenet.felk.cvut.cz/apo/]]) ===== Náplň cvičení ===== Pochopit jak jsou vykonávané instrukce na jednoduchém procesoru. Právě jednoduchost a přímočarost kódování instrukcí je podstatným důvodem proč většina učebnic volí jako modelovou architekturu [[https://en.wikipedia.org/wiki/MIPS_architecture|MIPS]]. Vlastní procesor načítá instrukce kódované v binární podobě. Vlastní osobní počítače třídy PC nám nedovolují spouštět strojový kód určený pro architekturu MIPS přímo na jejich procesoru (architektura [[https://en.wikipedia.org/wiki/X86|X86]]), ale existuje množství simulátorů této architektury. Pro účely výuky základů byl vytvořený přímo na míru předmětu simulátor [[..:..:documentation:qtmips:start|QtMips]]. ==== První část - Význam a použití základních instrukcí ==== 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 Logical Left: 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 \$at, LabelAddr | lui \$at, LabelAddr[31:16]; \\ ori \$at,\$at, LabelAddr[15:0] | Load Address: 32-bitové návěstí uloží do registru \$at. Jedná se o pseudoinstrukci - tzn. při překladu se rozloží na dílčí instrukce. | 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 [[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''. 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 ===== Ú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 ===== Odkazy ===== * [[..:..:documentation:mips-elf-gnu:start|Křížový překladač GNU pro MIPS-ELF]] - překladač jazyka C a assembleru, balíčky pro Debian GNU/Linux x86_64, i586 a Windows, konfigurace a kompilace ze zdrojových kódů * {{courses:B35APO:tutorials:03: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 jednoduché programy v assembleru bez knihoven je potřeba přidat -nostdlib -nodefaultlibs -nostartfiles * [[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]] /* * [[courses:b35apo:solutions:02:start]]*/