Search
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.
simple-lw-sw.S
.S
.c
.cc
.cpp
.o
.a
.globl _start .option norelax .text _start: loop: // load the word from absolute address lw x2, 0x400(x0) // store the word to absolute address sw x2, 0x404(x0) // stop execution wait for debugger/user // ebreak // ensure that continuation does not // interpret random data beq x0, x0, loop nop nop ebreak .data .org 0x400 src_val: .word 0x12345678 dst_val: .word 0
Zdrojový kód v assembleru se sestává ze
lw
sw
add
sub
.
V kódu jsou použity následující řídicí příkazy
_start
_ _start
la
lui
ori
.text
.data
Kompletní popis lze nalézt v manuálu GNU assembleru.
Překlad provedeme křížovým překladačem
riscv64-unknown-elf-gcc -Wl,-Ttext,0x200 -Wl,-Tdata,0x400 -march=rv32i -mabi=ilp32 -nostdlib simple-lw-sw.S -o simple-lw-sw
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 případně i 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ů.
-Wl,
-nostartfiles
-o
/opt/apo/qtrvsim/qtrvsim_template
Pro provedení programu použijeme simulátor QtRVSim. 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).
simple-lw-sw
QT_SCALE_FACTOR=1.5 qtrvsim_gui
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í
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 0x400 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 ebreak, která slouží k zastavení/přechodu do ladícího stavu. Vyzkoušejte, že modifikovaná hodnota na adrese 0x400 je vždy překopírovaná na adresu 0x404. Modifikujete program tak, aby sčítal dvě vstupní hodnoty na adresách 0x400 a 0x404 a výsledek ukládal na adresu 0x408.
ebreak
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).
la vect_a
.data vect_a: .word 1 .word 2 .word 3 .word 4 vect_b: .word 5 .word 6 .word 7 .word 8
Dále navrhněte program pro počítání průměru z osmi čísel.
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
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:
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 slli s0, s0, 1 // pow = pow*2 addi s1, s1, 1 // x = x+1 j while done:
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)
// Jenom pro účely ukázky... int a, *pa=0x80020040; int b, *pb=0x80020044; int c, *pc=0x00000124; a = *pa; b = *pb; c = *pc;
// s0=pa (bazova adresa), s1=a, s2=b, s3=c lui s0, 0x80020 // pa = 0x80020000; lw s1, 0x40(s0) // a = *pa; lw s2, 0x44(s0) // b = *pb; addi s0, 0, 0x124 // pc = 0x00000124; lw s3, 0x0(s0) // c = *pc;
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; }
.globl pole // nazev "pole" bude viditelny pro linker .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, zero, 0 // inicializacni prikaz cyklu for: i=0, kde i=s1 addi s2, zero, 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: ebreak nop .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...
Ř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.
int pole[5]={5,3,4,1,2}; int main(){ int N = 5,i,j,tmp; for(i=0; i<N; i++) { for(j=0; j<N-1-i; j++) { if(pole[j+1]<pole[j]) { tmp = pole[j+1]; pole[j+1] = pole[j]; pole[j] = tmp; } } } return 0; }
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.
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 RISC-V. Tento program budete potřebovat na příštím cvičení - to co nestihnete na cvičeních bude nutno dodělat doma..
// Directives to make interresting windows visible #pragma qtrvsim show registers #pragma qtrvsim show memory .globl _start .globl array_size .globl array_start .option norelax .text _start: la a0, array_start la a1, array_size lw a1, 0(a1) // number of elements in the array //Insert your code there //Final infinite loop end_loop: ebreak // stop the simulator j end_loop nop .data // .align 2 // not supported by QtRVSim yet array_size: .word 15 array_start: .word 5, 3, 4, 1, 15, 8, 9, 2, 10, 6, 11, 1, 6, 9, 12 // Specify location to show in memory window #pragma qtrvsim focus memory array_start