IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
IS1500 Lösningsförslag till övning CE_O2 2014
CE_O2. Nios II. Subrutiner med mera.
2.1. Binära lagringsformat
R-type: (Register-format)
rA (5 bit)
rB (5 bit)
rC (5 bit)
operationskod (17 bit)
Detta format är lämpligt för de instruktioner som använder innehåll från tre register
vid sin exekvering. Typexempel är ADD och liknande.
Instruktionen add rC,rA,rB medför att innehållet i angivet register rB adderas till
innehållet i angivet register rA. Resultatet skrivs därefter till angivet register rC.
Motsvarande gäller för många instruktioner, bland annat sub, and, or, nor, xor, mul
och div. Jämförelseinstruktionerna i Nios II kodas med R-formatet, likaså instruktionerna för skift och rotation (se en senare uppgift).
I-type: (Immediate-format
rA (5 bit)
rB (5 bit)
IMM16 (16 bit)
op­kod (6 bit)
Detta format används för instruktioner som använder en 16 bitars konstant, IMM16,
samt två register. Typexempel är ADDI och liknande.
Instruktionen addi rB,rA,IMM16 medför att en 16 bitar stor konstant IMM16 först
utvidgas till 32 bitar med sign-extension;, och sedan adderas med innehållet i angivet
register rA. Resultatet av additionen skrivs därefter till angivet register rB.
Motsvarande gäller för ett flertal instruktioner, som subi, andi, andhi, ori, orhi, xori,
xorhi och muli. De flesta jämförelseinstruktioner i Nios II finns i en immediate-variant
som kodas med I-formatet.
Formatet används även av LOAD och STORE-instruktioner med offset lagrad i
IMM16-fältet. Även för Load och Store görs sign-extension innan IMM16-värdet
används; det medför att offset kan vara både positiva och negativa.
J-type: (Jump-format)
IMMED26 (26 bit)
op­kod (6 bit)
Detta format används endast av instruktionen CALL. Hoppadressen tas fram som de
4 mest signifikanta bitarna från aktuellt värde i PC, följt av 26 bitar från IMMED26
och slutligen 2 nollor i de minst signifikanta bitarna. Effektiva adressen skrivs till PC
och medför subrutinanrop varvid returadressen sparas som vanligt i register r31 = ra.
Lösningsförslag till övning CE_O2, sida 1 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.2. Subrutinanrop med CALL eller CALLR
Instruktionen callr medför att hela innehållet i angivet register kopieras till
programräknaren (Program Counter, PC).
Instruktionen call (som är en av de få instruktioner som använder J-formatet) har
plats för en 26-bitars konstant.
Effektiva adressen, det vill säga det värde som skrivs till programräknaren, bildas på
följande sätt. Ur aktuellt värde i PC tar hårdvaran de 4 mest signifikanta bitarna,
sedan används de 26 bitarna från instruktionen. Till sist kompletterar hårdvaran med 2
nollor i de 2 minst signifikanta positionerna.
Konsekvensen blir att man kan anropa godtycklig adress på den "sida" om 256 Mbyte
som anges av de 4 mest signifikanta bitarna i PC. Det värde som finns i
CALL-instruktionens IMM26-fält multipliceras med 4 (skiftas 2 steg vänster) innan
det används. Det värde som står i CALL-instruktionen anger alltså word-offset inom
aktuell "sida".
Här nedanför visas ett exempel med instruktionen
call 0x471100
0x11c440 (0x471100 / 4, 26-bit absolute address)
PC
0010
0 (opcode)
0000 0001 0001 0001 0100 0111 0000 (= 0x0111470)
call instr
00 0001 0001 1100 0100 0100 0000 (= 0x11c440)
zeroes...
...since instruction addresses must end with binary 00
00
0010 0000 0100 0111 0001 0001 0000 0000 (= 0x20471100)
För både call och callr gäller att returadressen, det vill säga adressen till instruktionen
närmast efter call/callr, sparas i register r31. Register r31 kallas även ra
(return address register).
Lösningsförslag till övning CE_O2, sida 2 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.3. Retur från subrutin
Man ska alltid använda instruktionen ret som helt enkelt kopierar innehållet i
register r31 till PC. Man kan tro att det skulle gå lika bra med instruktionen JMP r31,
men det är inte tillåtet.
Instruktionen ret har binärkoden
11111 00000 00000 000101 00000 111010
Instruktionen jmp r31 är EJ tillåten, men skulle ha binärkod
11111 00000 00000 001101 00000 111010
Påminnelse: ra är bara är ett annat namn för register r31.
Instruktionerna ret och jmp r31 har alltså olika maskinkod. I Nios II Processor
Reference Handbook står dessutom följande:
It is illegal to jump to the address contained in register r31. To return from
subroutines called by call or callr, use ret instead of jmp.
2.4. Multiplikation med upprepad addition
a) Flödesschema för subrutinen muladd:
start
total = 0
i=1
i ≤ tal1
NO
YES
total =
total + tal2
i=i+1
return value =
total
return
Lösningsförslag till övning CE_O2, sida 3 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
b) Funktionen muladd kan skrivas så här i C:
int muladd( int a, int b )
{
int i;
int total = 0;
for( i = 1; i <= a; i = i + 1 )
total = total + b;
return( total );
}
/* b is added to total, a times */
Översättning från for-slinga till while-slinga ger följande:
int muladd( int a, int b )
{
int total = 0;
int i = 1;
while( i <= a)
{
total = total + b;
i = i + 1;
}
return( total );
}
Översättning från while-slinga till if-goto ger nästa version av funktionen:
int muladd( int
{
total = 0;
i = 1;
L1: if( i > a )
total = total
i = i + 1;
goto L1;
L2:
return( total
}
a, int b )
goto L2;
+ b;
);
Nu är det dags att översätta från vår förenklade C-kod till assemblerkod. Vi placerar
alla variablerna i register, och väljer dessutom att ha variabeln total i returvärdesregistret r2. För i väljer vi r8.
Före anrop till en funktion ska funktionens första parameter placeras i r4, andra
parametern i r5 och så vidare. De två tal som ska multipliceras finns alltså i r4 och r5
när funktionen muladd börjar exekvera.
muladd: movi
movi
L1:
bgt
add
addi
br
L2:
ret
r2,0
r8,1
r8,r4,L2
r2,r2,r5
r8,r8,1
L1
#
#
#
#
#
#
#
total = 0
i=1
if( i > a ) goto L2 /* a finns i r4 */
total = total + b /* b finns i r5 */
i=i+1
goto L1
total finns i r2
Lösningsförslag till övning CE_O2, sida 4 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
c) Körtiden för muladd beror på vilka värden som multipliceras, närmare bestämt på
det ena värdet som finns i register r4. Detta värde avgör hur många varv som
slingan mellan L1 och L2 körs. I värsta fall är värdet i storleksordningen
2 miljarder, och då körs slingan 2 miljarder varv. På den Nios II/s som används på
laborationerna tar detta omkring 8 miljarder klockcykler, eller närmare 3 minuter.
För att komma fram till detta värde gjordes följande beräkningar. Instruktionerna i
slingan är bgt – add – addi – br. Instruktionen bgt hoppar framåt, och hoppet tas
normalt inte; detta fall ger en körtid på 1 klockcykel. Instruktionerna add och addi
tar 1 klockcykel vardera. Instruktionen br hoppar bakåt; detta fall ger en körtid på
2 klockcykler. Summan är alltså 5 klockcykler per varv.
Programkoden före och efter själva slingan bidrar bara i försumbar utsträckning till
körtiden, när r4 innehåller stora värden.
Låt oss också beräkna körtiden för multiplikation med 10. Vi antar att talet 10 finns i
register r4. Slingan körs då 10 varv vilket tar 50 klockcykler. Det tillkommer 3 extra
cykler för bgt i sista varvet, då hoppet tas. Vidare tar de båda movi-instruktionerna
1 klockcykel vardera, och ret tar 4 klockcykler. Totalt tar funktionen muladd alltså
50 + 3 + 1 + 1 + 4 klockcykler = 59 klockcykler för multiplikation med 10.
2.5. Skifta och rotera bitarna i ett ord
Man skiljer mellan operationer/instruktioner för SHIFT och ROTATE.
SHIFT skiftar innehållet i utpekad operand varvid oftast nollor fylls på och utskiftade
bitar slängs bort.
Exempel: innehåll i R9 = 0x85011147
0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1
# skifta innehåll i r9 vänster 2 steg
bitar slli r9, r9, 2
0:or in till höger
bort
0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1 0 0
Innehåll i R9 efteråt = 0x1404451c = 0x05011147 x 4
ROTATE ser till att varje utskiftad bit skiftas in i den utpekade operanden.
Exempel: innehåll i R9 = 0x85011147
0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1
samma bitar
bitar
roli r9, r9, 2
# rotera innehåll i r9 vänster 2 steg
in till höger
ut
0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 1 1 1 0 1
Innehåll i R9 efteråt = 0x1404451d
Lösningsförslag till övning CE_O2, sida 5 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
SHIFT och ROTATE kan göras åt höger eller vänster.
SHIFT och ROTATE kan göras med olika antal steg.
Skift finns ofta i två olika versioner, logiskt och aritmetiskt.
ARITMETISKT SKIFT HÖGER motsvarar division med 2. För att det ska fungera
ska teckenbiten repeteras så att negativa tal fortsätter att vara negativa, och positiva
fortsätta att vara positiva.
Här är ett exempel där vi dividerar ett negativt tal med 4.
Logiskt högerskift ger fel resultat:
Exempel: innehåll i R9 = 0xfffffff4 = –12
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0
srli r9, r9, 2
0:or in
till vänster
# skifta innehåll i r9 höger 2 steg
bitar
bort
0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1
Innehåll i R9 efteråt = 0x3ffffffd = +1073741821 (fel!)
Aritmetiskt högerskift ger rätt resultat:
Exempel: innehåll i R9 = 0xfffffff4 = –12
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0
srai r9, r9, 2
# aritmetiskt skift av innehåll i r9 höger 2 steg
kopior av teckenbiten in till vänster
bitar
bort
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1
Innehåll i R9 efteråt = 0xfffffffd = –3 (rätt!)
SKIFT VÄNSTER motsvarar multiplikation med 2. Det ger risk för att talområdet
överskrids; för tal med teckenbit sker det när teckenbiten växlar värde. Nios II har
ingen hårdvara som upptäcker detta, utan programmeraren får skriva programkod
som undersöker värdena före och efter operationen.
Nios-II har följande instruktioner:
Rotation: rol, ror och roli.
Instruktionerna rol och ror är ROtate Left repektive ROtate Right.
Instruktionerna använder R-format med tre register. Exempel: rol rC,rA,rB
medför att innehållet i rA roteras det antal steg som anges av innehåll i rB (endast
5 bitar används, högst 31 stegs rotation) och resultatet placeras i register rC.
Lösningsförslag till övning CE_O2, sida 6 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
Instruktionen roli är en "immediate-variant" av rol där man anger antal steg som
en konstant med maxvärde 31. Det finns ingen immediate-variant av ror, men
"rotate right IMM5" kan utföras med instruktionen roli (32 minus IMM5).
Instruktionerna sll och srl samt slli och srli är logiska skiftinstruktioner.
Instruktionerna sra och srai är aritmetiska skiftinstruktioner
Intruktionerna utan "i" använder R-format enligt ovan och instruktionerna med "i"
utnyttjar IMM5-fält precis som rotate-instruktionerna ovan.
De aritmetiska skift-instruktionerna sra och srai duplicerar teckenbiten precis som
det önskades enligt ovan.
Man kan fråga sig vad man ska ha dessa instruktioner till. Ett svar är att in- och
utmatning på låg nivå kan kräva detta slags operationer på bitnivå. Exempelvis kan ett
program behöva undersöka varje bit i ett ord, och räkna hur många av bitarna som är
ettställda.
2.6. Multiplikation med konstant, med hjälp av skift och addition
Idén är att bilda produkten 10A genom att addera 2A + 8A, där 2A och 8A beräknas
med skift-instruktioner.
a) Vi får följande program om värdet A finns i r4. Koden fungerar för negativa tal i r4
om de lagras med 2-komplementsrepresentation.
mul10:
slli
slli
add
ret
r4,r4,1
r8,r4,2
r2,r4,r8
#
#
#
#
r4 får värdet 2A
r8 får värdet 8A
r4 får värdet 2A+8A dvs 10A
och så återhopp från subrutinen
b) Denna subrutin ska ta 11 klockcykler på den Nios II/s som används på
laborationerna. Instruktionen slli tar 3 klockcykler med den hårdvara som finns
på laborationerna, add tar 1 cykel och ret tar 4 cykler.
c) Talområdet för heltal utan tecken ( unsigned int) i Nios II är 0–4294967295. Om
produkten blir större än så så överskrids talområdet och resultatet blir fel.
d) Subrutinen mul10 fungerar bra för negativa tal, så länge talområdet inte överskrids.
Talområdet för heltal med tecken (signed int) i Nios II är (-2147483648)–
(+2147483647). Är produkten större än 2147483647, eller mer negativt än
(-2147483648), så överskrids talområdet och resultatet blir fel.
Lösningsförslag till övning CE_O2, sida 7 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.7. STACKOPERATIONER
a) Visa hur en stack fungerar.
adress 0
En stack är en speciell datastruktur. Det
går att reservera plats och lägga in nya
data, och det går att ta bort gamla data.
Stackstrukturen har en begränsning:
data måste tas bort i omvänd ordning
mot hur de lades in. Det betyder att det
senast inlagda värdet måste tas bort
först, sedan kan det näst senast inlagda
tas bort, och så vidare.
adress 0x3fff0
stack pointer
0x3fff0
nyast
äldst
adress 0xffffffff
I Nios II växer stacken alltid i riktning
mot adress 0.
adress 0
Ett av processorns register används som
stackpekare. Stackpekaren innehåller
alltid adressen till det senast inlagda
värdet. Man brukar säga att stackpekaren
pekar på det senast inlagda värdet.
När ett 4 byte stort värde ska läggas in på
stacken så minskas stackpekaren först
med 4, så att stackpekaren pekar på ett
lagom stort ledigt utrymme. Sedan skrivs
värdet till det lediga utrymmet. Denna
operation kallas push, och man talar
ibland om att pusha ett värde på stacken.
ännu nyare
adress 0x3fff0
2
inte längre nyast
X1
0x3ffec
0x3fff0
äldst
adress 0xffffffff
(fortsättning följer)
Lösningsförslag till övning CE_O2, sida 8 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
Operationen att ta bort ett tidigare pushat
värde brukar kallas pop. Man talar om att
poppa ett värde från stacken.
adress 0
Ska ett värde tas bort från stacken så görs
de omvända operationerna, i omvänd
ordning.
Först läses det senast inlagda värdet. Det
värdet finns ju på den minnesadress som
stackpekaren anger (pekar på). Vi
förutsätter att värdet är 4 byte stort.
1
adress 0x3ffec
2
0x3fff0
0x3ffec
X
just nu nyast
blir snart nyast
äldst
adress 0xffffffff
Sedan ökas stackpekarens värde med 4.
I och med att stackpekaren ökats med 4
så ligger det poppade värdet utanför stacken, och kommer att skrivas över vid nästa
push-operation.
b) Operationen PUSH rx (byt rx mot något av registren r2–r23)
subi sp,sp,4
stw rx,0(sp)
Operationen POP rx
ldw rx,0(sp)
addi sp,sp,4
c) Makron: register sp är samma som register r27
(se sid 3-2 i manualen kapitel 3: Programming Model)
.macro PUSH reg
subi sp,sp,4
stw \reg,0(sp)
.endm
.macro POP reg
ldw \reg,0(sp)
addi sp,sp,4
.endm
Lösningsförslag till övning CE_O2, sida 9 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.8. Subrutinanrop i flera nivåer (nästlade anrop)
Vilka speciella åtgärder måste vidtagas om man gör ett subrutinanrop i en subrutin?
Kallas även inkapslad subrutin eller nested procedure.
SVAR: Man måste se till att returadressen alltid förblir oförstörd. Det innebär att det
innehåll som finns i r31 vid anrop av en subrutin måste finnas kvar i r31 eller kunna
placeras i r31 innan man gör returhopp med instruktionen RET.
Man kan skydda innehållet i r31 på olika sätt. Det enklaste torde vara att se till att
man aldrig ändrar innehållet i r31. Men om man ska anropa en subrutin från en
subrutin måste man innan man gör subrutinanrop omplacera innehållet i r31. Det kan
man göra på olika sätt och det vanligaste är att kopiera r31 till en stackarea med en
PUSH-operation i början av subrutinen och att sedan återställa innehållet i r31 med en
POP-operation i slutet av subrutinen innan retur med RET.
Assemblerprogrammeraren måste också ha fullständig kontroll över vilka register som
används i olika programdelar av anroparen/caller respektive den anropade/callee.
Varje subrutin har rätt att förutsätta att register r8-r15 får användas helt fritt dvs att
den som anropar en subrutin måste uppträda som om innehåll i register r8-r15 kan
förstöras av den anropade subrutinen. Den som anropar en subrutin måste alltså vid
behov skydda innehåll i r8-r15 (t.ex. på stacken) under den tid subrutinen exekveras.
En subrutin som vill använda register r16-r23 måste skydda och återställa dessa
register innan returhopp eftersom det ska förutsättas att en callee (anropare) kan ha
använt r16-r23. Vilka krav som gäller för parametervärden i r2-r7 bör/måste framgå
av specifikationer för anroparen och den anropade.
2.9. Subrutin med parameteröverföring av värden i register
Skriv välkommenterad Nios II assemblerkod för en subrutin, sumv, som adderar två
32-bitars heltal vars värden lagrats i register r4 och r5 innan anropet av sumv.
Resultatet av beräkningen ska finnas i register r2 efter returhopp från sumv.
Nedan visas hur motsvarande c-liknande programkod ser ut
Handkompilera denna kod till assemblerkod för Nios-II
int sumv(int x, int y)
{
return (x+y);
}
SVAR:
SUMV:
ADD
RET
r2, r4, r5
# addera r2 <-- r4 + r5
# PC <-- r31 (JMP r31 är förbjuden)
Lösningsförslag till övning CE_O2, sida 10 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.10. Huvudprogram med parameteröverföring av värden i register
Skriv välkommenterad Nios II assemblerkod för ett huvudprogram som använder
subrutinen sumv, se föregående uppgift, för att addera två heltal.
Nedan visas ett exempel på hur ett sådant c-program kan se ut
Handkompilera denna kod till assemblerkod för Nios-II
int a, b, res;
extern int sumv(int par1, int par2)
int main()
{
a = 3;
b = 4;
...
/* annat programarbete
res = sumv (a, b);
...
return (0);
}
*/
SVAR:
.data
.align
2
A:
.word
0
B:
.word
0
RES: .word
0
.text
.align
2
.global main
main:
MOVIA
MOVIA
STW
MOVIA
MOVIA
STW
...
MOVIA
LDW
MOVIA
LDW
CALL
MOVIA
STW
#
#
#
#
#
#
r16,
r17,
r17,
r18,
r19,
r19,
A
#
3
#
0(r16)#
B
#
4
#
0(r18)#
r20, A
r4, 0(r20)
r21, B
r5, 0(r21)
SUMV
#
#
#
#
#
placera i
på adress
reservera
reservera
reservera
placera i
data-area
delbar med 4
plats för A
plats för B
plats för RES
program-area
adressen till A finns nu i r16
MOVI r17, 3 fungerar också
nu finns värde 3 i A
r16 är ledig och skulle fungera
r17 är ledig och skulle fungera
nu finns värde 4 i B
adressen till A finns nu i r20
nu finns parameter 1 i r4, värdet från A
adressen till B finns nu i r21
nu finns parameter 2 i r5, värdet från B
returvärde levereras i r2
r22, RES
# adressen til RES finns nu i r22
r2, 0(r22) # skriv resultatet till minnets plats RES
Lösningsförslag till övning CE_O2, sida 11 (av 12)
2015-01-13
IS1500 Datorteknik – Exempelsamlingens lösningar till övning CE_O2, 2014
2.11. Subrutin med parameteröverföring av adresser i register
int suma(int * x, int * y)
{
return (*x + *y);
}
Handkompilering till assemblerkod för Nios-II:
SUMA:
LDW r2, 0(r4)
LDW r8, 0(r5)
ADD r2, r2, r8
RET
# hämta värde till r2
# hämta värde 2 till r8
# addera
# retur med summan i r2
2.12. Huvudprogram med parameteröverföring av värden i register
int a, b, res;
extern int sumv(int* par1, int * par2)
int main()
{
a = 3;
b = 4;
...
/* annat programarbete
res = sumv (&a, &b);
...
return (0);
}
*/
Handkompilering till assemblerkod för Nios-II:
.data
.align
2
A:
.word
0
B:
.word
0
RES: .word
0
.text
.align
2
.global main
main:
MOVIA
MOVIA
STW
MOVIA
MOVIA
STW
...
MOVIA
MOVIA
MOVIA
CALLR
MOVIA
STW
#
#
#
#
#
#
r16,
r17,
r17,
r18,
r19,
r19,
A
#
3
#
0(r16)#
B
#
4
#
0(r18)#
r4, A
r5, B
r16, SUMA
r16
#
#
#
#
placera i
på adress
reservera
reservera
reservera
placera i
data-area
delbar med 4
plats för A
plats för B
plats för RES
program-area
adressen till A finns nu i r16
MOVI r17, 3 fungerar också
nu finns värde 3 i A
r16 är ledig och skulle fungera
r17 är ledig och skulle fungera
nu finns värde 4 i B
nu finns parameter 1 i r4, adressen till A
nu finns parameter 2 i r5, adressen till B
nu finns adressen till SMA i r16
returvärde kommer i r2
r16, RES
# återanvändning av r16
r2, 0(r16) # skriv resultatet till RES i minnet
Lösningsförslag till övning CE_O2, sida 12 (av 12)
2015-01-13