====== 2. Dokončení reprezentace čísel, instrukční sada, QtRvSim ======
* pro vyučující: [[..:..:internal:tutorials:02:start|cvičení 2]]
===== Osnova cvičení =====
- dokončení reprezentace čísel z cvičení 1
- upozornění a příprava na 1. domácí úkol ([[https://dcenet.fel.cvut.cz/apo/]])
- křížový překlad pro různé procesory
* instrukce Risc V procesoru
* simulátor QtRvSim
**Co si zopakovat na druhé cvičení**
- operace sčítání, odčítání, násobení a dělení
- doplňkový kód
- bitové operace s čísly (and, or, posuny, ...)
- přečíst/zopakovat znalosti z {{ ..:..:lectures:apolos_v11.pdf|APOLOS}}
- reprezentace reálných čísel ([[https://en.wikipedia.org/wiki/IEEE_754|IEEE 754]])
===== Náplň cvičení =====
Zopakovat si reálná čísla.
Seznámit se s návazností programovacích jazyků různé úrovně abstrakce až po strojový kód. Počítejte s tím, že se jedná
pouze o přehled a získání představy čím se v našem předmětu budete zabývat.
==== Od programování na vyšší úrovni k strojovému kódu ====
=== Programovací jazyk C ===
Jedná se o jazyk se striktně programátorem definovanými datovými typy. Vlastní vazba identifikátorů
typů na způsob reprezentace se ovšem může lišit mezi architekturami, protože například typ pro celá čísla
se znaménkem (''int'') reprezentuje takové kódování celých čísel alespoň v rozsahu −32,767 až +32,767,
které nejlépe na zpracování vyhovuje danému cílovému procesoru. Na většině v dnešní době využívaných
architektur je pak rozsah datového typu ''int'' −2,147,483,648 až +2,147,483,647 ($-2^{31}$ až $2^{31}-1$)
a hodnota je ukládaná ve 32-bitech.
Program je obvykle nutné před spuštěním zkompilovat (existují i jiné alternativy - [[https://root.cern.ch/cling|CERN ROOT Cling]])
do binární podoby, ve které může být načtený operačním systémem do paměti. Vlastní výpočet pak provádí přímo procesor
podle přeložených/binárních strojových instrukcí.
== hello-apo.c ==
Na příkladu C programu Hello world je možné předvést jak vypadá jeho kompilace pro různé architektury procesorů. Program uložíme pod jménem ''hello-apo.c''.
#include
int main(int argc, char *argv[])
{
printf("Hello APO\n");
return 0;
}
Kompilace a spuštění pak vypadá následovně
gcc -Wall -o hello-apo hello-apo.c
./hello-apo
V adresáři ''/opt/apo/hello-apo'' na pracovních počítačích v laboratoři se nachází příklad včetně sestavovacího souboru ''Makefile'' pro různé architektury procesorů. Mimo počítače v laboratoři je možné k příkladům přistupovat přes webové rozhraní FEL instalace verzovacího systému GitLab nebo přímo přes jeho příklady (adresář [[https://gitlab.fel.cvut.cz/b35apo/stud-support/-/tree/master/seminaries/hello-apo|seminaries/hello-apo]] v repozitáři [[https://gitlab.fel.cvut.cz/b35apo/stud-support|https://gitlab.fel.cvut.cz/b35apo/stud-support/]]).
Příklad stažení kompilace a spuštění na počítačích v laboratoři
mkdir -p ~/apo
cd ~/apo
cp -r -L /opt/apo/hello-apo .
cd hello-apo
make ARCH=native
make ARCH=native run
Příklad je připravený tak, že na počítačích v laboratoři je možné za ARCH volit ''native'', ''x86'', ''riscv'', ''riscv64'', ''mips'', ''arm'' nebo ''aarch64''.
== sum2vars.c ==
#include
int var_a = 0x1234;
int var_b = 0x2222;
int var_c = 0x3333;
int main()
{
var_c = var_a + var_b;
printf("sum %d + %d -> %d\n", var_a, var_b, var_c);
printf("sum 0x%x + 0x%x -> 0x%x\n", var_a, var_b, var_c);
return 0;
}
Program zkompilujeme do binární podoby [[https://www.gnu.org/|GNU]] kompilátorem jazyka C ([[https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/|manuál]]).
gcc -Wall sum2vars.c
Standardní jméno výstupního spustitelného souboru je ''a.out''. Program spustíme udáním názvu s cestou ''./a.out''.
Požadované jméno binárního souboru můžeme specifikovat na příkazové řádce přepínačem ''-o'' stejně jako specifikovat
vložení ladících informací ''-ggdb''. Kompilátor je dále možné požádat o optimalizaci programu na délku přepínačem ''-Os''.
gcc -Wall -Os -ggdb -o sum2vars sum2vars.c
Obsah binárního souboru ve formátu [[https://en.wikipedia.org/wiki/Executable_and_Linkable_Format|ELF]] je možné zkoumat
například nástrojem [[https://sourceware.org/binutils/docs/binutils/objdump.html#objdump|objdump]].
objdump -S sum2vars
Překlad na jednotlivé strojové instrukce je možné prozkoumat i online
[[https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(fontScale:14,j:1,lang:c%2B%2B,selection:(endColumn:1,endLineNumber:18,positionColumn:1,positionLineNumber:18,selectionStartColumn:1,selectionStartLineNumber:18,startColumn:1,startLineNumber:18),source:'%23include+%3Cstdio.h%3E%0A%0Aint+var_a+%3D+0x1234%3B%0Aint+var_b+%3D+0x2222%3B%0A%0Aint+var_c+%3D+0x3333%3B%0A%0Aint+main()%0A%7B%0A++var_c+%3D+var_a+%2B+var_b%3B%0A%0A++printf(%22sum+%25d+%2B+%25d+-%3E+%25d%5Cn%22,+var_a,+var_b,+var_c)%3B%0A%0A++printf(%22sum+0x%25x+%2B+0x%25x+-%3E+0x%25x%5Cn%22,+var_a,+var_b,+var_c)%3B%0A%0A++return+0%3B%0A%7D%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:g92,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'1',trim:'1'),fontScale:14,j:1,lang:c%2B%2B,libs:!(),options:'-Os',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'x86-64+gcc+9.2+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4|Godbolt Compiler Explorer]]
=== Assembler - jazyk symbolických adres ===
Zápis v jazyce C je přeložený na takovou posloupnost strojových instrukcí, která vykoná nad přesně specifikovanými datovými typy
takové operace, že výsledek celého běhu programu, nebo běhu do sekvenčního bodu, je ekvivalentní zapsanému algoritmu.
Výstup kompilátoru ve formátu již přesně daných instrukcí, ale bez rozhodnutí o jejich finálním umístění v paměťovém
prostoru získáme pro nativní překlad (překlad pro systém, na kterém aktuálně kompilátor běží) příkazem
gcc -Wall -Os -S -o sum2vars.s sum2vars.c
Plné pochopení jak psát programy v assembleru pro architekturu x86_64 a pro běh pod operačním systémem GNU/Linux
je náročné a proto jsme pro výuku procesorových architektur dříve volili architekturu MIPS ([[..:..:documentation:qtmips:start#volba_architektury_mips|důvody]]) a nyní přecházíme na otevřenější a modernější architekturu [[https://riscv.org/technical/specifications/|RISC-V]]. Na začátek budeme používat variantu v maximálně omezené a zjednodušené podobě. Aby bylo možné sledovat vnitřní stavy a princip činnosti procesoru,
nebudeme používat přímo procesorové desky s [[..:..:documentation:embedded_systems/start#pic32mxpic32mx_levne_vykonnejsi_mikrokontrolery_na_bazi_architektury_mips|čipy]] implementujícími architekturu,
ale grafický simulátor vytvořený přímo pro účely našeho kurzu - [[..:..:documentation:qtmips:start|QtMips]] ([[https://pretalx.linuxdays.cz/2019/talk/EAYAGG/|materiály]] a [[https://youtu.be/fhcdYtpFsyw|video]] z jeho představení na akci [[https://www.linuxdays.cz/2019/|LinuxDays 2019]]) nebo nyní novější [[https://github.com/cvut/qtrvsim|QtRvSim]]. Současný stav je výsledkem práce vašich předchůdců ([[https://dspace.cvut.cz/bitstream/handle/10467/94446/F3-BP-2021-Dupak-Jakub-thesis.pdf|bakalářská práce pana Dupáka]] a [[https://dspace.cvut.cz/bitstream/handle/10467/96707/F3-BP-2021-Hollmann-Max-thesis.pdf|bakalářská práce pana Hollmanna]]).
Přitom následující ukázky jsou uvedené především pro získání přehledu, čím se budete během semestru zabývat.
K zápisu algoritmu v instrukcích procesoru se vrátíme ve třetí přednášce a [[..:03:start|třetím cvičení]].
Pro první přiblížení zkompilujeme kód křížovým překladačem pro architekturu RISC-V
riscv64-linux-gnu-gcc -ggdb -Os -Wall -static -fno-lto -o sum2vars-riscv sum2vars.c
Program je možné v naší laboratoři pustit i na GNU/Linux systému s architekturou x86_64, protože binární soubory pro architektury MIPS, RISC-V, ARM a další jsou automaticky interpretované emulátorem [[https://www.qemu.org/|QEMU]] v režimu user-space emulace.
Program také můžeme přeložit bez operačního systému, pokud z původního programu v C zakomentujeme výstupy:
//#include
int var_a = 0x1234;
int var_b = 0x2222;
int var_c = 0x3333;
int main()
{
var_c = var_a + var_b;
//printf("sum %d + %d -> %d\n", var_a, var_b, var_c);
//printf("sum 0x%x + 0x%x -> 0x%x\n", var_a, var_b, var_c);
return 0;
}
Překlad pak provedeme příkazem:
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -c sum2vars.c -o sum2vars.o
Aby se program mohl spustit, je nutné ho inicializovat a spustit funkci main. K tomu můžeme využít soubor start.S:
.globl _start
.text
.option norelax
_start:
la x2, _end+0x4000
la x3, __global_pointer$
jal main
ebreak
I modul start.S zkompilujeme a vytvoříme start.o
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -c start.S -o start.o
Nyní z obou modulů sum2vars.o a start.o vytvoříme program:
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib sum2vars.o start.o -o sum2vars-riscv -lgcc
a můžeme ho nahrát do simulátoru
{{ ..:qtmips-load-sum2vars.png?direct&400 |}}
a odkrokovat.
Manuální přepis do assembleru RISC-V (sobor ''sum2vars-riscv.S'') může vypadat následovně (pro zjednodušení bez volání funkce tisku)
.globl _start
.text
_start:
lw x4, var_a(x0)
lw x5, var_b(x0)
add x6, x4, x5
sw x6, var_c(x0)
addi x2, x0, 0
jr ra
.data
.org 0x400
var_a: .word 0x1234
var_b: .word 0x2222
var_c: .word 0x3333
#pragma qtrvsim show registers
#pragma qtrvsim show memory
#pragma qtrvsim focus memory var_a
#pragma qtrvsim tab core
Tento program lze zkompilovat i přímo v simulátoru. Aby simulátor nenahrával externí
program, spustíme ho v režimu bez nahrání ELF souboru.
{{ ..:qtmips-start-empty.png?direct&400 |}}
Zvolíme File -> New source
Nyní se využijí i direktivy pro rychlé nastavení pozice ve výpisu paměti
na proměnnou ''var_a'' a další.
Zkompilujeme volbou Machine -> Compile source
a můžeme ho odkrokovat.
{{ ..:qtmips-with-legend.png?direct&400 |}}
==== Sestavovací program make ====
V situaci, kdy máme jeden zdrojový soubor není problém spouštět překladač pokaždé ručně. U větších projektů ale máme souborů víc a často různých typů – máme třeba zdrojové kódy v C a v assembleru, dokumentaci v XML a třeba i něco dalšího. Překládat ručně každý soubor zvlášť a ještě k tomu jiným překladačem a spojovat výsledek dohromady je zbytečně velká práce. Proto byl vymyšlen program [[https://www.gnu.org/software/make/manual/html_node/index.html|make]]. Jeho základní myšlenka je ta, že se popíše jak se překládá který typ souboru a stanoví se které soubory jsou potřeba pro "vyrobení" jiného souboru.
Program make funguje tak, že po spuštění si přečte soubor Makefile, kde jsou zapsána pravidla pro kompilaci a vyvolává postupně překladače tak, jak je potřeba.
V souboru ''Makefile'' existují čtyři typy řádek:
* popis závislostí,
* příkazy pro kompilaci,
* nastavování proměnných,
* komentáře.
Nejjednodušší Makefile pro kompilaci programu hello vypadá následovně.
hello: hello.c
Obsahuje pouze jednu řádku s popisem závislostí. Takto zapsaná řádka říká, že pro vytvoření souboru hello je potřeba soubor ''hello.c''. Tomu, co je vlevo od dvojtečky se říká target (cíl) a vše co je napravo jsou takzvané prerekvizity.
Program make má v sobě vestavěna implicitní pravidla, díky nimž ví, jak má provést překlad. Kdyby tato pravidla neexistovala, musel by náš ''Makefile'' vypadat takto:
hello: hello.c
gcc -Wall -o hello hello.c
Na druhé řádce, která začíná tabulátorem, je příkaz, který bude vykonán pokud make usoudí, že je potřeba překompilovat soubor ''hello''.
Implicitní pravidla říkají, jak z jednoho typu souboru vyrobit jiný typ souboru. Tato pravidla jsou většinou definována pomocí proměnných. Chceme-li implicitní pravidlo jen trochu pozměnit, nemusíme ho definovat znova, ale stačí změnit jen hodnoty proměnných, které se v pravidlech používají.
CFLAGS = -g -Wall
CC = m68k-elf-gcc
hello: hello.c
Pomocí proměnné ''CFLAGS'' se například určuje, s jakými přepínači má být spouštěn překladač jazyka C. Jak se jmenuje překladač se zase specifikuje v proměnné ''CC''[3].
Pokud chceme překládat větší projekt, bývá zvykem rozšířit ''Makefile'' o další targety:
all: hello
clean:
rm -f hello
hello: hello.c
gcc -Wall -g -o hello hello.c
O programu make by toho šlo napsat mnohem víc. Jdou pomocí něj překládat i velmi komplikované projekty jako například samotné jádro Linuxu. Vetšinou není potřeba vytvářet složité soubory Makefile ručně. Nástroje jako Meson, CMake, autoconf či nějaké IDE Makefile generují automaticky.
==== Úkoly ====
- **S cvičícím si projděte výše uvedený program a zkuste si ho přeložit pro různé procesory**
* zdůvodněte, jak je možné, že na počítačích s procesorem x86 můžete spustit program pro ARM, RISC V, MISP a jiné procesory
* zkontrolujte si, že umíte na počítačích v laboratoři používat simulátor QtRvSim
* v případě, že vám není jasné, jak si simulátor nainstalovat doma, tak se zeptejte cvičících
- **Přeložte a spusťte uvedený program, interpretujte výstupy programu a postupně ho modifikujte tak**
* aby tiskl vnitřní reprezentaci i jiných datových typů (např. ''char, float, long, int*'')
* aby vytiskl tabulku celých čísel včetně jejich reprezentace v rozsahu ''-16'' až ''15''
* aby realizoval operace sčítání a odčítání dvou proměnných (celá čísla) a vytiskl na obrazovku vstupní operandy a výsledky těchto operací včetně jejich vnitřní reprezentace
* vyzkoušejte operace s kladnými i zápornými čísly, zaměřte se i na takové hodnoty, kdy po provedení operace dojde k přetečení
- **Reprezentace reálných čísel (IEEE 754)**
* vyzkoušejte si nástroj pro testování reálných čísel [[ https://www.h-schmidt.net/FloatConverter/IEEE754.html | IEEE-754 Floating Point Converter]]
* binární reprezentace reálných čísel (''float - 32bit, double - 64bit'')
* převeďte na binární reprezentaci číslo ''-0.75'', ověřte správnost s pomocí programu pro zobrazení reprezentace v paměti
* převeďte float z binární reprezentace ''0xC0A00000'' na reálné číslo v desitkové soustavě
* demonstrujte výpočet (v desítkové soustavě) ''9.999*10^1 + 1.1610*10^(-1)'', předpokládejte, že je možné uložit pouze 4 cifry čísla a 2 cifry exponentu.
* Návod:
- zarovnání čísel
- součet
- normalizace
- zaokrouhlení
* v binární reprezentaci sečtěte čísla ''0.5'' a ''-0.4375''
* demonstrujte výpočet (v desítkové soustavě) ''1.110*10^10 * 9.200*10^(-5)''
* v binární reprezentaci vynásobte čísla ''0.5'' a ''-0.4375''
===== Domácí úkoly =====
* domácí úkoly 1 až 4 budou zadané a odevzdávané elektronickou formou
* vstup k zadání a odevzdání úkolů je přes adresu [[https://dcenet.fel.cvut.cz/apo/|https://dcenet.fel.cvut.cz/apo/]]
* Na stránce "Assignments" naleznete seznam zadaných úkolů
* Pro nácvik práce s odevzdávacím systémem je k dispozici nehodnocená varianta prvního úkolu **1st training homework**, kde máte 999 pokusů. Nazapočítávají se z ní body, ale i tak ji musíte vyřešit aspoň na polovinu, tedy na 3 body, abyste směli odevzdat 1. hodnocený domácí úkol.
* Případnými problémy s odevzdávacím systémem se obracejte na svého cvičícího nebo přímo na autora/správce systému [[courses:b35apo:teacher:susta:start|Richarda Šustu]]
----
===== Užitečné odkazy =====
* [[http://support.dce.felk.cvut.cz/pos/cv-langc/|http://support.dce.felk.cvut.cz/pos/cv-langc/]] - Základy jazyka C
* [[http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html|http://www.gnu.org/software/libc/manual/html_node/Formatted-Output.html]] - Dokumentace k řízení formátovaného výstupu pro implementaci **printf** z knihovny funkcí pro jazyk C ([[http://www.gnu.org/software/libc/|GLIBC]]) z projektu [[http://www.gnu.org/|GNU]]
* [[https://bootlin.com/doc/legacy/command-line/command_memento.pdf|Základní příkazy pro práci v příkazové řádce]] na [[https://bootlin.com/|Bootlin]]
* Materiál k IEEE 754: {{..:apo2.pdf|}}, nová česká verze APO2020_cv2 {{ :courses:b35apo:tutorials:02:apo2020_cv2.pdf | PDF }} a {{ :courses:b35apo:tutorials:02:apo2020_cv2.pptx | PPTX}}
* Popis formátu IEEE 754 na [[https://en.wikipedia.org/wiki/IEEE_754|Wikipedii]]
* [[https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libgcc/fp-bit.c]] - implementace operací v plovoucí řádové čárce s využitím operací v pevné řádové čárce tak v knihovně kompilátoru [[http://gcc.gnu.org|GCC]] pro procesory, které hardwarovou implementací operací nedisponují.