8.5 KiB
RTS10 - Assembly Assignment
Finn van Reenen
opdrachten
Ik moest de volgende assignment maken:
- Assignment 1: Testing a simple LEGv7 Pinky program
- Assignment 2: Writeting a simple multiplication program
- Assignment 5: A smarter tail recursive multiply algorithm
- Assignment 7: A smarter power algorithm
Testing a simple LEGv7 Pinky program
Er is een project gegeven die toegevoerd en getest moet worden. De eerste keer dat ik door de stappen heen ging, kreeg een error dat de openOCD.exe fout ging. Dit is logisch voor mij, omdat ik Linux gebruikt als OS en .exe alleen voor Windows zijn.
Het opnieuw selecteren van de OpenOCD debug probe in het 'Debug Configurations' venster werd de Linux variant correct ingesteld en werkte het.
Voor de zekerheid heb ik het nog een keer geprobeerd terwel ik alle wijzigingen in het project bij hou ik git^[voor de geïnteresseerde heb ik de git repo op mijn server gezet waar de commit van de clock wijzigingen te vinden is met de volgende link (4dcb59bc05]]. Hier zag ik dat er wat clock instellingen werden gewijzigd. Dus ik heb het opnieuw geprobeerd, maar dan alleen het veld 'OpenOCD Command' aangepast in de debugconfiguratie. Met deze methode werden de clock instellingen niet aangepast, alleen een aantal dingen dat ik herken als de verschillen tussen Windows en Linux.
Hierna werken het nog steeds zoals verwacht.
Writeting a simple multiplication program
Om te beginnen heb ik het project van de vorige assignment. Hier heb ik aan main.c een paar simpele regels toegevoegd om de basis werking te testen.
/* main.c simple program to test assembler program */
#include <stdio.h>
extern int test(int a, int b);
extern unsigned int multiply(unsigned int a, unsigned int b);
int main(void)
{
extern void initialise_monitor_handles(void);
initialise_monitor_handles();
int a = test(3, 5);
printf("Result of test(3, 5) = %d\n", a);
unsigned int b = multiply(3, 5);
printf("Result of multipy(3, 5) = %d\n", b);
b = multiply(3, 0);
printf("Result of multipy(3, 0) = %d\n", b);
return 0;
}
Pas toen ik het werkend had, kwam ik achter dat ik a en b heb omgedraaid. Dit maakt gelukkig niet uit in een vermenigvuldiging. Daarnaast heb ik ook i weg gewerkt door i.p.v. i te verhogen en daarna te vergelijken met b heb ik b verlaagd tot het 0 is.
Dit is mijn implementatie:
.cpu cortex-m4
.thumb
.syntax unified
.globl multiply
.text
.thumb_func
multiply:
MOVS.N R2, #0 // set result to 0
CMP.N R1, #0
BEQ.N exit // goto exit if b == 0
loop:
ADDS.N R2, R0, R2 // add a to result
SUBS.N R1, #1 // substract 1 from b
BEQ.N exit // goto exit if b == 0
B.N loop // loop otherwise
exit:
MOVS.N R0, R2 // move result to R0
BX.N LR
En het testresultaat:
How many instructions does it take your procedure to run the code: multiply(65535, 65535)?
65535 (decimaal) = 0xFFFF (hex) ofwel 16 enen in binair.
Voor de loop zijn 3 instructies.
Een iteratie van de loop zijn 4 instructies, deze loop wordt net zo vaak als de waarde van b uitgevoerd dus 4 * 65535 - 1 = 262139 instructies (de - 1 is voor de laatste iteratie waar die uit de loop springt waardoor de laatste instructie in de loop wordt overgeslagen).
Na de loop zijn nog 2 instructie, dat een totaal maakt van 262144 instructies
A smarter tail recursive multiply algorithm
Voor deze opdracht heb ik niet exact de gegeven C code na gemaakt. Ik heb de twee functies als een geschreven. Hierbij is het argument m van function multiply2 verplaatst als laatste argument i.p.v. de eerste en de if (b == 0) heb ik verplaats naar multiply.
Het verplaatsen van argument m is alleen een probleem wanneer er andere code is die gebruik maakt dat deze functie behalve multiply.
Het verplassen van de if (b == 0)statement is een optimalisatie. Dit statement kan nooit waar zijn na de eerste iteratie. b wordt steeds 1 plek naar recht geschoven tot dat de laatste bit die 1 is op de 1 positie staat. In dat geval eindigt de functie direct.
De assembly code:
.cpu cortex-m4
.thumb
.syntax unified
.globl multiply
.text
.thumb_func
// R0 = a, R1 = b, R2 = result (m), R3 = odd/even mask/result
multiply:
MOVS.N R2, #0 // set result to 0
CMP.N R1, #0
BEQ.N exit // goto exit if b == 0
multiply2:
CMP.N R1, #1
BNE.N check_even // goto check_even if b == 1
ADDS.N R2, R0 // move a to result
B.N exit // goto exit
check_even:
MOVS.N R3, #1 // set R3 to 1 as bit mask
ANDS.N R3, R1 // and R3 (bit mask) with b
BNE.N odd // goto exit if b == 0
even:
LSLS.N R0, R0, #1 // a = a << 1
LSRS.N R1, R1, #1 // b = b >> 1
B.N multiply2 // recurse
odd:
ADDS.N R2, R0 // add a to result
LSLS.N R0, R0, #1 // a = a << 1
LSRS.N R1, R1, #1 // b = b >> 1
B.N multiply2 // recurse
exit:
MOVS.N R0, R2 // move result to R0
BX.N LR
Het testresultaat:
How many instructions does it take your procedure to run the code: multiply(65535, 65535)?
65535 (decimaal) = 0xFFFF (hex) ofwel 16 enen in binair.
Vanaf label multiply duurt het 3 instructie tot de eerste branch instructie. Deze wordt niet genomen omdat b niet 0 is.
Hierna komt multiply2 waar de 2de instructie een branche is die genomen wordt. b nog geen 0.
check_even heeft 3 instructies tot een brance. Deze brance wordt altijd genomen, omdat b in binair alleen maar uit enen bestaat, dus het schuiven naar rechts (de enige bewerking die gedaan wordt op b) verandert bit 1 niet naar 0 voor dat b 1 is geweest wanneer de loop eindigt.
odd heeft 4 instructies, waarna multiply2 weer wordt geroepen.
De loop van multiply2 -> check_even -> odd word 15 keer gedaan. Voor elk bitje dat 1 is in b tot dat er nog maar 1 over is. Deze loop bestaat uit 8 instructies, dus totaal 120 instructies.
Wanneer b == 1 is net nog 6 instructies tot de BX.N LR instructie.
Totaal is dat dus 129 instructies (3 van multiply + 120 van de loop + 6 van het einde). Dit is significant minder dat bij assignment 1, dit waren er 262144 dus deze implementatie is ongeveer 2032 keer minder instructies in dit specifieke geval.
A smarter power algorithm
Ik kwam wat kort in tijd en heb de machten tot 0 niet werkend kunnen maken. Hier wordt p * n terg gestuurd i.p.v. 1. Verder werkt de code.
De assembly voor de power functie:
.cpu cortex-m4
.thumb
.syntax unified
.globl power
.text
.thumb_func
// R5 = n, R6 = m, R4 = result (p)
power:
PUSH.N {R4, R5, R6}
SUB.N SP, SP, #4
STR LR, [SP, #0] // push LR to the stack
MOVS.N R4, #1 // set result to 1
CMP.N R1, #0
BEQ.N exit // goto exit if m == 0
MOVS.N R5, R0 // move n to R5
MOVS.N R6, R1 // move m to R6
loop:
CMP.N R6, #1
BEQ.N exit // goto exit if m == 1
MOVS.N R3, #1
ANDS.N R3, R6 // R3 = m & 0x1
BEQ.N even
MOVS.N R0, R4
MOVS.N R1, R5
LDR.N R3, =multiply
BLX.N R3 // p * n
MOVS.N R4, R0 // move multiply result to p
even:
MOVS.N R0, R5
MOVS.N R1, R5
LDR.N R3, =multiply
BLX.N R3 // p * n
MOVS.N R5, R0 // move multiply result to n
LSRS.N R6, R6, #1 // m = m >> 1
B.N loop
exit:
MOVS.N R0, R4
MOVS.N R1, R5
LDR.N R3, =multiply
BLX.N R3 // p * n
LDR LR, [SP, #0] // pop LR from the stack
ADD.N SP, SP, #4
POP.N {R4, R5, R6}
BX.N LR
de test code die ik heb gebruikt om de functie te testen:
#include <stdio.h>
unsigned int multiply(unsigned int a, unsigned int b);
unsigned int power(unsigned int n, unsigned int m);
int main()
{
extern void initialise_monitor_handles(void);
initialise_monitor_handles();
unsigned int n[] = {0, 1, 0, 1, 2000, 2 , 7, 4294967295u, 3 };
unsigned int m[] = {0, 0, 1, 1, 2, 31 , 11, 1 , 20 };
unsigned int r[] = {1, 1, 0, 1, 4000000, 0x80000000u, 1977326743, 4294967295u, 3486784401u};
for (size_t i = 0; i != sizeof(n)/sizeof(n[0]); i++)
{
printf("%u ^ %u: ", n[i], m[i]);
unsigned int result = power(n[i], m[i]);
unsigned int correct = r[i];
if (result != correct)
{
printf("Failed, function returned %u but the correct answer is %u\n", result, correct);
}
else
{
printf("Passed, %u\n", result);
}
}
return 0;
}
het testresultaat:




