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