Övningsuppgifter i Datorteknik, EIT070 1
Övningar 2
Övning 1 1. Skriv en subrutin i assembler utan innehåll, som bara anropas och direkt hoppar tillbaks. Den ska använda rätt instruktioner och konventioner för anropet. Skriv också den instruktion i huvudprogrammet som anropar subrutinen. 2. Förklara hur en vektor av characters (char vec[10]), ligger lagrad i minnet. Skriv de deklarationer i assembler som behövs för att allokera vektorn. Vad blir det för skillnad om vektorn består av trettiotvåbitars heltal i stället (int vec[10])? 3. Skriv assemblerkod för en for-loop som gör följande: for (i=0; i<10; i++) { sum += vec[i]; } Antag att i, sum och vec är tretttiotvåbitars heltal med tecken (int). 4. I minnet på adress 0x77770120 börjar en vektor av 16 st. 32-bits heltal. Skriv en assemblerrutin som adderar talet +1 till varannan och -1 till varannan av talen i minnet och skriver resultatet till motsvarande plats i minnet. 5. Bakgrundsscenario På förlossningsavdelningarna i Sverige finns en datoriserad apparat vars uppgift är att övervaka modern och den bebis som ska födas. Så fort något värde är utöver det normala slår apparaten larm, och en sköterska eller läkare tillkallas. Värdena som mäts presenteras på en skärm för att en barnmorska eller läkare snabbt ska kunna avgöra hur mor och barn mår. Mätvärdena sparas dessutom på en diskett för att senare kunna analyseras. Vi väljer här att betrakta ett par specifika funktioner hos denna apparat: Apparaten ska mäta barnets EKG, dvs information om hjärtverksamheten, och beräkna barnets hjärtfrekvens. Detta EKG fås fram ur en spänning som mäts 20 ggr/sekund. Apparaten ska mäta hur mycket mammans livmoder dras samman (tokogram, TOKO). Detta mäts genom en speciell givare 2 ggr/sekund. Datorsystemet som styr denna apparat antar vi vara baserat kring en MIPS-processor. Apparatens basinställningar och program finns lagrat i EPROM och användaren kan spara inställningar i EEPROM. Datorn har ett arbetsminne om 4 Mbyte RAM. Det finns ett antal adresserbara I/O-portar som är anslutna till yttre enheter som mätgivare, display, larmsignal och diskettenhet. Apparaten initieras då strömmen slås till, men börjar inte mäta EKG och TOKO förrän en knapp tryckts in på apparatens frontpanel. Apparatens funktion är sedan att: läsa de uppmätta värdena från speciella minnesplatser. visa dem på en display. skriva dessa data till en diskett. Mätning av EKG och TOKO görs från därför avsedda I/O-portar, och mätvärdena skrivs till motsvarande minnesplatser. Motivering till uppgiften: Utifrån de uppgifter du fått i texten ovan får du här beskriva datorsystemets hårdvara och funktionalitet. 3
a) Rita en systemskiss över hårdvaran i apparatens datorsystem och över de vägar som data transporteras i systemet. (5p) b) Gör ett flödesschema över de funktioner datorsystemet ska utföra (enl ovan). (5p) 6. I en 8-bits 2-komplementrepresentation av heltal vill du utföra vissa aritmetiska operationer. Utför operationerna och kommentera resultatet om något oväntat händer! a) 42 + 82 b) 45-82 c) 45 + (-82) d) 51 + 82 e) -51-82 f) 89*(-67) (Antag 16-bitars resultat) 4
Övning 2 1. Översätt följande syntetiska instruktioner till instruktioner som finns i MIPS: la a0, 0x80020000 la a0, 0x80030024 add t0, t1, 50 move a1, t2 add t0, t1, 0x356000 Bakgrund för uppgift 1 och 2 Formatet som används är IEEE754. Ett tal skrivet i detta format ser ut på följande sätt: S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM Förklaring: S: teckenbit. EEEEEEEE: 8-bitars exponent. MMM..M: 23-bitars signifikand. Ett tal som representeras i detta format motsvaras decimalt på följande vis: s x expontent 1 127 1+ signifikand ( ) ( ) ( ) 2. a) Addera de tal som representeras av de båda bitfälten: y= 01000000110000000000000000000000 z= 00111110111110000000000000000000 b) Vilka decimala tal motsvarar talen y och z samt deras summa? c) Var kan det uppstå fel vid flyttalsberäkning? 3. a) Bestäm de decimala motsvarigheterna till talen: a= 11000000011000000000000000000000 b= 00111111010010000000000000000000 b) Utför flyttalsmultiplikationen mellan talen a och b. Vad blir svaret i IEEE-754-representation? 4. Från I/O-adressen 0x70502030 läses en byte med instruktionen lbu till register t0. Bitarna i denna byte är b7_b0. Skriv ner instruktioner som kollar om bitarna b0, b3 och b7 är satta i den lästa byten och om så är fallet hoppar man till subrutinen 'unset'. I annat fall läses I/O-porten igen. 5. Vad händer med register ra($31) när en subrutin anropar en ny subrutin. Rita och förklara! Hur förhindras den eventuellt oönskade effekten? 6. Skriv i MIPS-assembler en rekursiv subrutin för beräkning av summan: N i, den aritmetiska i= 1 summan av de första N naturliga talen. Antalet iterationer, N ligger sparat i register a0, svaret skall sparas i register v0. 5
Övning 3 1. Motivering till uppgiften: Den här uppgiften kräver att du förstår hur en subrutin i assembler måste vara konstruerad, att du kan läsa dig till funktionen hos den utifrån given kod och att du kan korrigera mindre fel i rutinen. Följande subrutin är tänkt att användas i ett programsystem där det ska kunna anropas av C och assemblerrutiner. Den ska alltså vara skriven enligt programmeringskonventionen, med inparameter i register a0 och resultat i register v0. my_func: xor s0, a0, zero beq s0, zero, END addi a0, a0, -1 jal my_func add v0, v0, s0 b OUT END: addi v0, zero, 0 OUT: jr ra Rutinen är tänkt att anropas enligt följande (parametern i a0, resultatet i v0): addi a0, zero, 3 jal myfunc # Här ska värdet 6 ligga i v0 addi a0, zero, 4 jal my_func # Här ska värdet 10 ligga i v0 addi a0, zero, 2 jal my_func # Här ska värdet 3 ligga i v0 a) Vad är subrutinen my_func tänkt att göra? (4p) b) Kommentera koden för my_func. (2p) c) Koden i uppgiften fungerar inte. Varför? Rätta koden så att my_func fungerar. (4p) 6
Bakgrund för uppgift 2 och 3 För att kunna skriva en avbrottsrutin måste man känna till följande två register, Status och Causeregistrer som ser ut enligt följande i en MIPS arkitektur: Cause-registret: IP[5..0]: Interrupt pending. Den bit som motsvarar numret på användaravbrottet sätts då detta avbrott signaleras. SW[1..0]: Mjukvaruinterrupt (2 st) signaleras i dessa två bitar. Status-registret: IntMask[7..0]: Bit 0 och 1 sätts om man vill tillåta de två mjukvaru avbrotten. Bit 2_7 sätts för att tillåta de sex olika användardefinierade avbrotten (bit 2 motsvarar avbrott 0 osv.). KU: Kernel/User mode (Privilegierat systemtillstånd om biten är 0 eller användartillstånd). Old/Previous/Current. IE: Interrupt enable. Tillåter användardefinierade avbrott om biten är 1. Old/Previous/Current. 2. Motivering: Ofta stöter man på problemet att datorn är upptagen med att exekvera en lång avbrottsrutin när ett sällsynt avbrott av annan typ uppstår. Ett exempel är komplex reglering av en process, t.ex. en pappersmaskin, där en nödsituation oväntat kan uppstå. I ett realtidssystem uppbyggt kring en MIPS-dator styr en yttre klocka med periodtid 22.4 s ett av de externa avbrotten (nr 3). Varje gång avbrottet genereras, påbörjas en omfattande beräkning av en väderprognos. Denna beräkning tar 20 s. Extern mätutrustning kan generera s.k. kalibreringsavbrott, ett externt avbrott (nr 5) som måste tas om hand då mätapparaturen genererar felaktiga mätvärden. Dessa avbrott inträffar relativt sällan, mindre än 1 gång per minut, och exekveras färdigt på 1.35 s. Kalibreringsavbrotten måste ovillkorligen tas om hand genast, och måste alltid slutföras så fort som möjligt. a) Beskriv steg för steg t.ex. med hjälp av en punktlista eller flödesschema hur en avbrottshanterare för detta system måste byggas upp. (4p) b) Skriv avbrottshanteraren i MIPS-assembler. Väderberäkningen görs i subrutinen weather och kalibreringen i subrutinen calibrate. (6p) 3. Motivering: Övervakningsapparaten mäter värden i realtid, med förutbestämda tidsintervall. Det bästa sättet är att implementera detta med timerstyrda avbrott. Här får du visa dina insikter om hur man kan implementera de önskade funktionerna. Mätprocesserna för mätning av EKG och TOKO ska ske 20 ggr/sekund resp. 2 ggr/sekund. För att implementera dessa väljer vi att implementera dessa som avbrottsrutiner, där ett timeravbrott (externt avbrott nummer 3) sker med 20 ggr/s och ett annat timeravbrott (externt avbrott nummer 5) sker med 2 ggr/s. a) Hur förbereds MIPS-systemet för att kunna hantera avbrotten? Hur används MIPS avbrottsregister här? (3p) b) Hur ska en avbrottshanterare se ut för detta system? Antag att subrutinerna ekg och toko finns implementerade. (4p) 7
c) Ge ett förslag till hur man kan implementera olika prioriteter mellan avbrotten i ett MIPS-system! (3p) 4. Funktionen int strcmp(char* s1, char *s2) finns i C-biblioteket. Den används för att jämföra teckensträngar och returnerar heltalet +1 om teckensträngen som finns på adress s1 är större än teckensträngen på adress s2, -1 om det är tvärtom och 0 om strängarna är lika. Ex: abcd på adress s1 och Bbcd på adress s2 resulterar i returvärdet +1, medan Abcd på adress s1 och bbcd på adress s2 resulterar i -1 (eftersom 'A' är mindre än 'a' som byte sett.) Alla strängar förutsätts avslutas med byten 0x00. Skriv en rekursiv MIPS-assemblerrutin som implementerar funktionen hos strcmp. Följ programmeringskonventionerna! 8
Övning 4 1. I din gamla Volvo har du installerat ett MIPS-system. Du vill att din MIPS-processor ska kunna utföra två uppgifter 'samtidigt' - dels att mäta temperaturen utanför bilen, dels att mäta momentana hastigheten samt beräkna medelhastigheten. Visa hur du kan implementera detta genom att använda dig av avbrottsmekanismen i MIPS! Du har tillgång till en timer med ställbar frekvens i bilen. a) Formulera rimliga krav på systemet (3p) b) Beskriv hur båda uppgifterna kan tas om hand genom avbrottsmekanismen (3p) c) Skriv i MIPS-assembler kod för en lämplig avbrottshanterare (4p) 2. Motivering: Detta skulle kunna vara en verklig situation för dig när du blir färdig civilingenjör. Dina kunskaper i ämnet gör att du kan strukturera problemet och beskriva hur samspelet mellan mjuk- och hårdvara ska se ut. Du har börjat arbeta på konsultfirman C-con. Efter någon vecka ringer en kund: - Hej. Jag har ett MIPS-system och behöver stöd för åtminstone tre olika processer - eller trådar, tror jag det heter. Vi vill kunna skriva till tre olika terminaler från samma processor och läsa från tre olika operatörsterminaler - samtidigt. Vi har programvara för en uppsättning, men nu behöver vi tre. En kille hos oss har börjat skriva någonting med 'polling', men det verkar inte funka. Vad ska vi göra? Kan du hjälpa oss? Du funderar kort och ber att få återkomma med ett förslag. Du inser att vad de behöver är ett realtidssystem av något slag. a) Vilket hårdvarustöd i MIPS måste du använda? (1p) b) Skissera grovt hur realtidssystemet ska se ut. Vilka moment och datastrukturer måste ingå? (6p) c) Vilka data måste finnas separat för varje tråd? (3p) 3. Motivering: I denna uppgift får du fundera lite över ekvivalensen mellan instruktioner och data. Följande subrutin har skrivits i MIPS-assembler:.globl strange.ent strange.set noreorder.text strange: lw t0, 20(a0) lw t1, 24(a0) addi t2, zero, 2 add t0, t0, t2 add t1, t1, t2 add v0, t0, t1 and v1, t0, t1 sw t0, 20(a0) sw t1, 24(a0) jr ra 9
.end strange I dataarean finns följande vektor definierad:.globl vector.data vector:.word 0.word 0.word 0.word 0.word 0.word 0xAA.word 0xFF.word 0xBB.word 0xCC Följande huvudprogram anropar subrutinen strange:.globl start.ent start.set reorder.text start: la a0, vector jal strange # Position 1 la a0, strange jal strange la a0, vector jal strange # Position 2.end start Antag att all maskinkod ligger i RAM-minne. a) Kommentera koden i strange och start, dvs tala om vad koden gör. (1p) b) Bestäm innehållet i registren v0 och v1 vid position 1 i koden enligt ovan. (2p) c) Bestäm innehållet i registren v0 och v1 vid position 2 i koden enligt ovan. Förklara tydligt vad som händer! (7p) 4. Beskriv med figur vilka tillstånd en tråd kan befinna sig i och vad som krävs för att tråden ska byta tillstånd. 5. Beskriv två sätt att hindra trådar att samtidigt få åtkomst till en och samma odelbara resurs. 10
Lösningsförslag 11
Övning 1 1. Subrutinen har fått namnet subr och kan se ut så här:.globl subr.ent subr subr: # Här görs inget! jr ra.end I huvudprogrammet anrops subrutinen så här: jal subr 2. En vektor lagras som konsekutiva minnesceller. Man måste själv hålla rätt på var varje cell ligger när man vill använda ett visst element. Deklarationen blir: vec:.byte 10 Om vektorn består av trettiotvåbitars heltal blir det i stället så här: vec:.word 10 # OBS! 4 * 10 bytes! 3. addi t4, zero, 0 # t4 innehåller sum, sätt sum till noll addi t0, zero, 0 # t0 är loopräknaren i, sätt den till noll addi t1, zero, 10 # t1 innehåller den övre gränsen 10 repeat: bge t0, t1, break # testa om index >= 10 sll t2, t0, 2 # multiplicera index med fyra (!) lw t3, vec(t2) # hämta värdet i vec[i] add t4, t4, t3 # lägg till värdet till sum b repeat # nästa varv i loopen break: Eftersom varje element i vec är ett heltal som tar fyra bytes i minnet, måste man multiplicera indexet med fyra. 12
4. #include <iregdef.h>.set noreorder.text.globl start.ent start start: lui s0,0x7777 ori s0,s0,0x0120 # Lägger adressen i s0 add s1,zero,zero # Skapar en räknare loop: sll s2,s1,3 # multiplicerar räknaren med 8 addi s1,s1,1 # räknar upp add s2,s0,s2 # beräknar adressen för nästa värde lw s3,0(s2) # hämtar värdet vid i lw s4,4(s2) # hämtar värdet vid i+4 addi s3,s3,1 # adderar med 1 addi s4,s4,-1 # adderar med -1 sw s3,0(s2) # skriver tillbaka sw s4,4(s2) # skriver tillbaka.end start bne s1,8,loop # Om ej klar tillbaka till loop 13
5. a) 14
b) 15
6. a) 0 0 1 0 1 0 1 0 (42) + 0 1 0 1 0 0 1 0 (82) 0 1 1 1 1 1 0 0 (124) b) 0 0 1 0 1 1 0 1 (45) - 0 1 0 1 0 0 1 0 (82) 1 1 0 1 1 0 1 1 (-37) c) 0 0 1 0 1 1 0 1 (45) + 1 0 1 0 1 1 1 0 (-82) 1 1 0 1 1 0 1 1 (-37) d) 0 0110 011(51) + 01010 010 (82) 10 0 0 0101(-123) e) 1 1 0 0 1 1 0 1 (-51) - 0 1 0 1 0 0 1 0 (82) 0 1 1 1 1 0 1 1 (123) f) 010110 01(89) *101 11101(-67) 0 0 0 0 0 0 0 0 010110 01 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 010110 010 0 + 0 0 0 0 0 010110 010 0 0 + 0 0 0 0 010110 010 0 0 0 + 0 0 0 010110 010 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0-0 010110 010 0 0 0 0 0 0 111010 0 0101 10 101(-5963) 16
Övning 2 1. a) lui $4,0x8002 d) addu $5,$10, $0 b) lui $4,0x8003 ori $4,$4,0x24 c) addi $8,$9,0x32 e) lui $1,0x35 ori $1,$1,0x6000 add $8,$9,$1 2. a) Vi börjar med att undersöka exponenterna. För y är exponenten 10000001 127 = 129 127 = 2. För z är exponenten 01111101 127 = 125 127 = 2. z har lägst exponent och därför ska signifikanden för z skiftas åt höger tills exponenterna är lika, dvs. 4 steg. Detta ger: 0.000111110000000000000000000. Därefter adderas talen: 1.10000000000000000000000 (y:s signifikand) + 0.000111110000000000000000000 (z:s signifikand högerskiftad 4 steg) 1.100111110000000000000000000(summans signifikand) Vi har bara 23 värdesiffror för signifikanden vilket ger: 1.10011111000000000000000 Observera att den mest signifikanta bitens etta är implicit! Båda talen är positiva och har den gemensamma exponenten 4 (efter att signifikanden justerats). Detta medför följande: S exp signifikand 0 10000001 10011111000000000000000 b) y = 0 10000001 10000000000000000000000 0 ( 129 127) 23 2 y = ( 1) 2 ( 1+ 1 1/ 2+ 0 1/ 4+ 0 1/ 8+... + 0 1/ 2 ) = 1 2 3/ 2 = 6 z = 0 01111101 11110000000000000000000 0 125 127 23 z = 1 2 1+ 1+ 1 1/ 2+ 1 1/ 4+ 1 1/8+ 1 1/16+ 0 1/ 32+... + 0 1/ 2 = c) Vi får begränsad noggrannhet i beräkningarna då vi endast har ett begränsat antal bitar för att representera signifikanden. ( ) ( ) ( ) 0,484375 17
3. a) a = 11000000011000000000000000000000 = 3,5 b = 00111111010010000000000000000000 = 0,78125 b) Signifikanderna multipliceras (bortser från avslutande nollor): 11100 *11001 11100 00000 00000 11100 +11100 1010111100 Signifikanden behöver göras om till normalform: K = 10.10111100 = 2 1 1.010111100 MMM..M = 010111100..0 Exponent: 1 1 1 1 2 2 2 = 2 (exponent från K * exponent från B en offset) Detta leder till att exponent = 128 = 10000000 Teckenbit: (- 1) 1 = 1 S = 1 a b = 1 10000000 01011110000000000000000 4. #include <iregdef.h>.set noreorder.text.globl start.ent start start: lui t1,0x7050 # adressen ori t1,t1,0x2030 addi t2,zero,0x89 # masken loop: lbu t0,0x0(t1) # hämtar värdet and t0,t0,t2 bne t0,t2,loop jal unset.end start 18
5. Kapitel 4.5 i Brorsson 6. #include <iregdef.h>.globl sum.ent sum.set noreorder sum: subu sp,sp,8 sw ra,4(sp) sw s0,0(sp) add s0,zero,a0 beq s0,zero,l1 addiu a0,a0,-1 jal sum add v0,s0,v0 L1: lw s0,0(sp) lw ra,4(sp) addiu sp,sp,8 jr ra.end sum 19
Övning 3 1. a) Subrutinen my_func adderar talen 0+1+2+... +N om den anropas med parametern N (som måste vara 0). b) my_func: xor s0, a0, zero # Kopiera argumentet till s0 beq s0, zero, END # Om s0==0, hoppa till END # addi a0, a0, -1 # Minska argumentet med 1 jal my_func # Rekursivt anrop add v0, v0, s0 # Lägg till argumentet till v0 # my_func(a0-1) b OUT # Hoppa ut END: addi v0, zero, 0 # Nollställ v0-registret OUT: jr ra # Återhopp c) Återhoppsadressen skrivs över, och enligt programmeringskonventionen måste vi spara s0 och a0. Register s0 måste sparas på stacken eftersom värdet används efter anropet av my_func. Korrigerad kod: my_func: subu sp,sp,16 # Plats för 4 ord på stacken sw ra,12(sp) # Återhoppsadress sw s0,8(sp) # Subrutinregister s0 sw a0,4(sp) # Argument xor s0, a0, zero # Kopiera argumentet till s0 beq s0, zero, END # Om s0==0, hoppa till END # addi a0, a0, -1 # Minska argumentet med 1 jal my_func # Rekursivt anrop add v0, v0, s0 # Öka v0 med argumentet b OUT # Hoppa ut END: addi v0, zero, 0 # Nollställ v0-registret OUT: lw a0, 4(sp) # lw s0, 8(sp) lw ra, 12(sp) # Återställ återhoppsadressen addiu sp,sp,16 # Återställ stacken jr ra # Återhopp 20
2. Vi måste göra det möjligt att avbryta ett pågående avbrott (nummer 3). Därför måste detta avbrott spara EPC samt de register det använder. a) De olika stegen för att bygga upp den specificerade avbrottshanteraren: i. Initiera huvudrutin som sparar register v0, v1, t0-t9, a0, a3 samt EPC. ii. Kontrollera vilket avbrott som skett iii. Hoppa till relevant subrutin som betjänar avbrottet iv. Återställ register + EPC v. Hoppa tillbaka till den adress som pekas ut av EPC följt av rfe-instruktionen. Den subrutin som betjänar det snabba avbrottet ser ut som en helt vanlig subrutin. Den andra, långsamma, rutinen som betjänar avbrott 3 måste dessutom läsa statusregistret, spara detta på stacken, ändra SR så att de snabba avbrotten (nr 5) tillåts och tillåta avbrott genom att sätta IECur=1. När rutinen för avbrott 3 är klar måste de register som denna rutin använder inklusive SR återställas. b).globl int_routine.ent int_routine int_routine: #Spara register på stacken #Spara ra, v0-v1, t0-t9, a0-a3, EPC mfc0 k0, C0_CAUSE # Hämta Cause-registret till k0 andi t0, k0, EXCMASK # bne t0, zero, internal_int # Internt avbrott har inträffat andi t0, k0, EXT_INT5 # Kolla om avbrott 5 beq t0, zero, test_int_3 # Om inte, kolla avbrott3 jal INT_5 # Betjäna avbrott 5 testa_int_3: andi t0, k0, EXT_INT3 # Kolla avbrott 3 beq t0, zero, restore # Om inte hoppa ur avbrottet jal INT_3 # Betjäna avbrott 3 restore: #Återställ register+epc från stacken mfc0 k0, C0_EPC.set noreorder jr k0 rfe.set reorder.end int_routine # Läs ut EPC:n.globl INT_5.ent INT_5 INT_5: subu sp, sp, 8 sw ra, 0(sp) jal calibrate lw ra, 0(sp) addu sp, sp, 8 # Kalibreringsrutin 21
jr ra.end INT_5.globl INT_3.ent INT_3 INT_3: subu sp, sp, 8 sw ra, 0(sp) mfc0 t0, C0_SR # Hämta Statusregister sw t0, 4(sp) # Spara SR på stacken. xori t0, t0, 0x2000 # Maska bort avbrott 3 mtc0 t0, C0_SR # Skriv modifierat statusregister rfe # Tillåter avbrott jal weather # Väderrutin lw ra, 0(sp) lw t0, 4(sp) # Hämta SR från stacken. mtc0 t0, C0_SR # Skriv tillbaka SR addu sp, sp, 8 jr ra.end INT_3 3. a).ent intstub.set noreorder intstub: j introutine.set reorder.end intstub i. Kopiera intstub till adress 0x80000080 ii. Sätt IEC i statusregistret till 1. (Tillåt användardefinierade avbrott). iii. Sätt Int och Int 5-bitarna i statusregistret till 1. (Bit 13 och bit 15). b).globl int_routine.ent int_routine int_routine: addiu sp, sp, -X*4 # Allokera plats på stacken för # X register sw ra, 0x0(sp) # Spara ra, v0-v1, t0-t9, a0-13 # samt alla som används utom # k0,k1 mfc0, k0, C0_cause andi t0, k0, EXT_INT3 beq t0, zero, testa_int_5 # Hämta causeregistret till k0 # Kolla om avbrott 3 genererats # Om ej, kolla nästa avbrott 22
jal ekg # Utför subrutinen ekg om # avbrott 3 testa_int_5: andi t0, k0, EXT_INT5 # Kolla avbrott 5 beq t0, zero, restore # Hoppa till slutet jal toko # Utför subrutinen toko om # avbrott 5 b restore restore: lw ra, 0x0(sp) addiu sp, sp, X*4 mfc0 k0, C0_EPC.set noreorder # Hopp till slutet jr k0 # Återhopp rfe.set reorder.end int_routine # Återställ alla registren # Återställ stacken # Hämta återhoppsadressen # Återställ efter avbrott i # hoppluckan c) Kolla av avbrotten i prioritetsordning Tillåt avbrott av högre prioritet då ett avbrott av lägre prioritet exekveras 4. #include <iregdef.h>.globl strcmp.ent strcmp strcmp: subu sp,sp,24 sw ra,20(sp) sw s0,16(sp) sw s1,12(sp) sw a0, 8(sp) sw a1, 4(sp) lbu s0,0(a0) lbu s1,0(a1) bgt s0,s1,bigger blt s0,s1,smaller beq s0,zero,equal # Läser in första tecknet i resten av # sträng A # Läser in första tecknet i resten av # sträng B # if A[i]>B[i] goto bigger # else if A[i]<B[i] goto smaller # else if A[i]==B[i]==0 goto equal 23
addi a0,a0,1 # else anropa rekursivt med A[i+1] och # B[i+1] addi a1,a1,1 jal strcmp b restore smaller: addi v0,zero,-1 b restore bigger: addi v0,zero,1 b restore equal: add v0,zero,zero restore: lw a1, 4(sp) lw a0, 8(sp) lw s1,12(sp) lw s0,16(sp) lw ra,20(sp) addiu sp,sp,24 jr ra.end strcmp 24
Övning 4 1. a) De olika uppgifterna har två helt skilda tidskonstanter. Rimligt är att kräva att hastigheten mäts och beräknas var 1/10: dels sekund, medan det för temperaturmätningen bör räcka med en gång varannan sekund. Vi kan förutsätta att själva temperaturmätningen sker betydligt snabbare än en tiondels sekund. Om detta inte är fallet kan problem uppstå. b) För att kunna hantera dessa två processer kan man antingen välja att designa ett realtidssystem med två trådar och en scheduler som fördelar processortiden ojämnt mellan dessa två trådar. Systemet är dock fortfarande så enkelt att en enkel avbrottshanterare kan lösa problemet: Vi låter externa avbrott genereras av timern var 1/10 sekund. I avbrottsrutinen kontrolleras avbrottet, hastighetsmätningsrutinen avropas. Dessutom räknas en variabel upp en gång per avbrott, tills den når ett värde som motsvarar ca 20 avbrott - då anropas temperaturmätningsrutinen. Huvudprogrammet behöver inte göra någonting, utan kan löpa i en oändlig slinga. c) Vi antar här att timerinterruptet är extern int 3.data counter:.word 0.text inthandler: subu sp,sp, 64 sw t0,60(sp) sw t1,56(sp) sw t2,52(sp)... # Register t3-t9 också sw v0,20(sp) sw v1,16(sp) sw a0,12(sp) sw a1,8(sp) sw a2,4(sp) sw a3,0(sp) mfc0 t0,c0_cause andi t0,ext_int3 beq t0,zero,out # Negligera andra interrupt jal velocity # Beräkna hastighet (på något sätt) la t1, counter lw t2,0(t1) addi t2,t2,1 # Stega upp räknaren addi t0,zero,20 bne t2,t0, INCRCNT jal temperature subu t2,t2,20 INCRCNT: la t1, counter # Kan ha ändrats av de anropade 25
sw t2, 0(t1) # rutinerna OUT: lw t0,60(sp) lw t1,56(sp) lw t2,52(sp)... # Register t3-t9 också lw v0,20(sp) lw v1,16(sp) lw a0,12(sp) lw a1,8(sp) lw a2,4(sp) lw a3,0(sp) addiu sp, sp, 64 # Återställ stackpekaren.set noreorder mfc0 k0,c0_epc jr k0 # Hoppa tillbaka rfe 2. a) Avbrottshantering b) Realtidssystemet måste innehålla stöd för trådhantering, dvs följande: En tråd som läser från varje operatörsterminal (totalt 3 st) En tråd som skriver till varje terminal (3 st) En avbrottshanterare som utför följande: o Vid avbrott stoppas den exekverande tråden. Denna tråds kontext i CPU:n (alla register + specialregister) sparas i minnet. o En scheduler körs igång och väljer nästa tråd som ska köras enligt någon algoritm. o Den utvalda trådens sparade kontext läses från minnet till registren i CPU:n. o Återhopp från avbrottet sker till den nya trådens EPC (det var ju där den avbröts senast). o Mekanismer för ömsesidig uteslutning, t.ex. för åtkomst till I/O-enheter. c) Varje tråd måste ha ett eget TCB, thread control block, där register $31 sparas (exkl. k0, k1) samt där specialregister som HI, LO, Status, Cause och EPC sparas. Dessutom måste varje tråd ha en separat stack. 3. a) Programmet tar en adress till en vektor som argument. Det läser orden med offset 20 och 24 från vektorns början, adderar 2 till dessa och skriver tillbaka dem på samma plats. Första gången anropas rutinen med en vektor i dataarean som argument. När den anropas med sig själv som argument, ändras binärkoden för rutinen själv. Utparametrarna v0 och v1 beräknas på olika sätt första gången och andra gången (då rutinen anropats med sig själv som argument) eftersom opkoden ändras från add till sub och från and till xor. Gammal instruktion Ny instruktion add v0, t0, t1 sub v0, t0, t1 and v1, t0, t1 xor v1, t0, t1 26
Detta är ett enkelt exempel på självmodiferande kod. I datorernas barndom när minne var mycket dyrt briljerade man i denna svåra konst. b) v0=0xaa + 0x02 + 0xFF + 0x02 = 0x1AD, v1=(0xaa + 0x02) AND (0xFF + 0x02)=0. c) v0=(0xaa + 0x02) - (0xFF + 0x02) = 0xFFFFFFAB, v1=(0xaa + 0x02) XOR (0xFF + 0x02)= 0x1AD. 4. Exekverande tråd kan bli avbruten av: Tidsavbrott dags att byta tråd Blockerad resurs antingen självmant genom yield(), eller tvingad. Exekverande Redo Blockerad Ny tråd 5. Antingen stänga av avbrotten (inte att föredra eftersom alla trådar blir hindrade, även de med högre prioritet) eller använda semaforer. 27
Lösningsförslag till övningar från Mats Brorssons bok, Datorsystem, Program- och maskinvara Lösningarna är gjorda av: Mats Brorsson Bengt Öhman 28
Kapitel 2 1. Vad är ett register? Ett register är en lagringsplats för data inne i processorn som i de flesta processorarkitekturer rymmer ett ord. För MIPS-arkitekturen är det fyra byte, 32 bitar. Den största skillnaden mellan register och det som kallas minnet i en processor är att minnet innehåller både instruktioner och data medan register enbart kan innehålla data. Processorn kan inte hämta instruktioner från registren. Det går också mycket fortare att hämta data från ett register än från minnet tack vare att det är mindre, är närmare kopplat till övriga delar i processorn samt att det bara krävs ett fåtal bitar för att benämna ett register eftersom det finns så få av dem. 2. Vad har programräknaren för funktion? Programräknaren är ett register i processorn som innehåller minnesadressen till nästa instruktion som ska exekveras. 3. Hur många bitar används för att koda en MIPS-instruktion? 32. 4. En given dator har 268435456 byte minne. Varför har konstruktören valt ett så udda antal minnesceller i stället för, till exempel 250 000 000? 268435456 är ett jämt tal, i alla fall i hexadecimal eller binär representation. Det är 1000000016 i hexadecimal representation. Det är också en jämn 28 potens av basen 2 : 2. Detta innebär att med 28 bitledningar kan man adressera precis 268435456 olika minnesceller och därför är det ett naturligt val för konstruktören. 5. Visa hur man kan skriva nedanstående C-sats med en, eller ett fåtal, MIPSinstruktioner: a = b + 100; Anta att a finns i register $8 och b i register $9. Assemblerkoden för detta är: addi $8,$9,100 29
6. Lägg till kommentarer till koden nedan och beskriv i en mening vad den gör. Register $4 används för att föra in en parameter och register $2 för att föra ut resultatet. Inparametern i $4 är ett heltal som du kan benämna n i din beskrivning. start: addi $8,$0,0 addi $9,$0,1 loop: slt $10,$4,$9 bne $10,$0,finish add $8,$8,$9 addi $9,$9,2 j loop finish: add $2,$8,$0 Programmet räknar ut summan av alla udda tal mindre än eller lika med n. Här följer programkoden med kommentarer. Jag benämner i kommentarerna $8 med oddsum och $9 med oddnum. addi $8,$0,0 # Sätt oddsum till 0 addi $9,$0,1 # Sätt oddnum till 1 loop: slt $10,$4,$9 # Om n < oddnum bne $10,$0,finish # Hoppa till finish add $8,$8,$9 # oddsum += oddnum addi $9,$9,2 # oddnum += 2 j loop # Goto loop finish: add $2,$8,$0 # Returnera oddsum 7. Skriv följande C-sats med så få MIPS-instruktioner som möjligt: x = y + c; Anta att x finns på adress 400000010 i minnet, att y finns på adress 400000410 och att c finns i register $8. Adressernas motsvarighet i hexadecimal representation är: 400000010 = 0x3d0900 och 400000410 = 0x3d0904. Här kommer C-satsen som MIPS- instruktioner: lui $10,0x003d # Lägg övre halvan av adr i $10 30
ori $10,$10,0x0900 # Lägg undre halvan av adr i $10 lw $9,0($10) # Läs variabel y från minnet # Load delay slot add $11,$9,$8 # Utför additionen sw $11,4($10) # Skriv variabel x till minnet Eftersom additionsinstruktionen inte kan ha någon operand i minnet måste först variabel y läsas in och variabel x skrivas efter additionen. Valet av register är i denna lösning ganska godtycklig. Vilka som helst av de tillgängliga registren utom register $0 som ju alltid är noll kunde ha använts. 8. Följande program försöker kopiera ord från en plats i minnet vars adress finns i register $4 till en annan plats vars adress finns i $5. Programmet ska sluta kopiera när den stöter på värdet 0, och då ska antalet kopierade ord finnas i register $2. Värdet 0 ska kopieras, men inte räknas. L: lw $3,0($4) addi $2,$2,1 sw $3,0($5) addi $4,$4,1 addi $5,$5,1 bne $3,$0,L # Läs nästa ord från källan # Räkna upp antalet kopierade ord # Skriv det kopierade ordet # Uppdatera pekaren till nästa ord # Detsamma för destinationen # Hoppa till L om läst ord # inte är noll. Det finns flera fel i det här programmet. Din uppgift är att skriva om programmet så att det fungerar. Ändringarna är markerade med fet stil. L: lw $3,0($4) addi $2,$2,1 sw $3,0($5) addi $4,$4,4 addi $5,$5,4 bne $3,$0,L addi $2,$2,-1 # Läs nästa ord från källan # Räkna upp antalet kopierade ord # Skriv det kopierade ordet # Uppdatera pekaren till nästa ord # Detsamma för destinationen # Hoppa till L om läst ord # inte är noll. # Justera för att nollan inte # ska räknas 9. Använd appendix D eller en arkitekturhandledning för MIPS för att beskriva instruktionsformatet för varje instruktion i uppgift (med felen intakta), och skriv den hexadecimala koden för programmet. 31
L: lw $3,0($4) addi $2,$2,1 sw $3,0($5) addi $4,$4,1 addi $5,$5,1 bne $3,$0,L # 0x8c830000 # 0x20420001 # 0xaca30000 # 0x20840001 # 0x20a50001 # 0x1460fffa # 0x00000000 Instruktionen bne ska hoppa sex steg bakåt vilket kodas med tvåkomplementrepresentation som 0xfffa med 16 bitar. 10. Beskriv skillnaderna mellan följande instruktioner: lb, lw, lh och lui. Instruktionerna: lb, lw och lh är instruktioner som läser data från minnet och lägger det i ett register. Instruktionen lui, load upper immediate, däremot, läser inte från minnet utan tar en konstant kodad i instruktionen och lägger den som de 16 mest signifikanta bitarna. De 16 minst signifikanta bitarna sätts till noll. Instruktionerna: lb, lw och lh skiljer sig åt i storleken på det dataelement som hämtas från minnet. Instruktionen lb hämtar en byte 8 bitar, lw ett ord 32 bitar och lh ett halvord 16 bitar. 11. Instruktionen j L1 är en ovillkorlig hoppinstruktion som hoppar till adressen som specificeras av programläget L1. Beskriv hur du kan göra samma sak med instruktionen beq. Kan alla förekomster av j X, där X är en godtycklig adress, ersättas med en beq? Instruktionen j L1 kan i många fall realiseras med beq $0,$0,L1. Villkoret för detta är att det inte får finnas fler än ca 32000 instruktioner mellan beq-instruktionen och programläget L1. Adressen L1 kodas som en differens med 16 bitar i beq-instruktionen. Med 16 bitar kan man representera ett talområde mellan -32768 och 32767. 32
Kapitel 3 1. Omvandla följande decimala tal till 32-bitars binära tal i tvåkomplementrepresentation: 512, -1023, -4000000. 51210 = 000000000000000000000010000000002 = 0x00000200 102310 = 111111111111111111111100000000012 = 0xfffffc01 4000000 = 11111111110000101111011100000000 = 0xffc2f700 10 2. Vilka decimala tal motsvaras av följande binära tal i tvåkomplementrepresentation? 11111111 11111111 11111110 00001100 11111111 1111111111111111 11111111 01111111 1111111111111111 11111111 111111111111111111111110000011002 = 500 111111111111111111111111111111112 = 1 01111111111111111111111111111111 = 2147483647 3. Vilket binära tal representeras av följande hexadecimala tal? 0x7ffffffa Vad är den decimala motsvarigheten? Det binära talet är: 0111 1111 1111 1111 1111 1111 1111 1010 Dess decimala motsvarighet är: 2147483642 4. En bitsekvens för sig själv betyder inget. Man måste ge den ett sammanhang för att tolka den korrekt. Avgör med hjälp av detta kapitel, kapitel 2 och appendix D det antal sätt som följande bitsträng kan tolkas på och beskriv dessa tolkningar: 1000 1111 1110 1111 1100 0000 0000 0000 Som instruktion: lw $15,0xc000($31) Som heltal (tvåkomplementrepresentation): -1880113152 Som positivt heltal: 2414854144 Som reellt tal i enkel precision: -2,36412 2 5. Anta att du har en processor med ordlängden 1 byte. Utför följande beräkningar: 00101101 11111111 00000000 11110111 +01101111 + 11111111-11111111 -11110111 Blir svaret rätt eller fel? Spelar det någon roll om talen tolkas som positiva heltal eller i tvåkomplementrepresentation? 2 33
Här är den första additionen. Minnessiffran skrivs över den översta linjen: 11 1111 00101101 + 01101111 10011100 De ingående talen är 45 och 111 vars summa är 156. Svaret stämmer om de talen tolkas som positiva heltal men inte om de ska vara både positiva och negativa tal i tvåkomplementrepresentation. I tvåkomplementrepresentation betyder svaret -100 vilket är uppenbarligt fel. Här är den andra additionen. 1111111 11111111 + 11111111 11111110 De ingående talen är båda 255 eller -1 om de ska tolkas som tvåkomplementtal. Svaret är 253 eller -2 om det tolkas som tvåkomplementtal. Här är det bara om man tolkar de ingående talen enligt tvåkomplementrepresentationen som resultatet är rätt. Den tredje och fjärde aritemetiska beräkningarna är subtraktioner som omvandlas till addition genom att negera den andra operanden. 00000000 00000000-11111111 + 00000001 00000001 Vi får alltså att 00000000-11111111 = 00000001. Om vi tolkar talen som enbart positiva tal är detta 0-255 = 1 vilket naturligtvis är helt fel. Om vi istället tolkar dem som tal i tvåkomplementrepresentation har vi: 0 - (-1) = 1 vilket är rätt. Den sista beräkningen är: 11110111-11110111 = 00000000. De ingående talen är 247 om de tolkas som positiva heltal eller -9 om de ska tolkas som tal i tvåkomplementrepresentation. I båda fallen är resultatet korrekt. 6. Verifiera att algoritmen för multiplikation i figur 3.8 fungerar genom att skriva ett program som testar den. Här följer ett komplett program som testar multiplikationsrutinen: #include <stdio.h> int mult(int A, int B){ int P, bit, i, n; n = 32; P = 0; bit = 1; for (i = 0; i < (n-1); i++){ if (B & bit){ P = P + A; 34
} A = A << 1; bit = bit << 1; } if (B & bit) P = P - A; return P; } main(){ int a, b, p; a = 3; b = 43; p = mult(a,b); printf("%d * %d = %d (%d)\n", a, b, p, a*b); 7. Verifiera att algoritmen för division i figur 3.10 fungerar genom att skriva ett program som testar den. Här är ett komplett program som testar divisionsalgoritmen. Observera att eftersom täljaren förväntas vara 2n bitar stor sätter vi här n till 16. #include <stdio.h> int div(int A, int B, int *r){ int Q, R, i, n = 16; int D; R = A; D = B; Q = 0; D = D << n; for (i = 0; i < n; i++){ D = D >> 1; R = R - D; if (R < 0) { R = R + D, Q = Q << 1; } else { Q = Q << 1; Q = Q + 1; } } *r = R; return Q; } main(){ int a, b; int q, r; a = 3; b = 43; q = div(a, b, &r); printf("%d / %d = %d (%d), %d %% %d = %d (%d)\n", 35
} a, b, q, a/b, a, b, r, a%b); q = div(b, a, &r); printf("%d / %d = %d (%d), %d %% %d = %d (%d)\n", b, a, q, b/a, b, a, r, b%a); 8. Omvandla följande tal till flyttal i enkel precision: 9, 5/32, -5/32, 6,125 Flyttalet som motsvarar 9 är: 0x41100000 Flyttalet som motsvarar 5/32 är: 0x3e200000 Flyttalet som motsvarar -5/32 är: 0xbe200000 Flyttalet som motsvarar 6,125: 0x40c40000 9. Omvandla följande hexadecimala tal till dess decimala motsvarighet, om vi tolkar dem som flyttal i enkel precision: 0x42e48000, 0x3f880000, 0x00800000, 0xc7f00000 Flyttalet 0x42e48000 motsvarar: 114,25 Flyttalet 0x3f880000 motsvarar: 1,0625 Flyttalet 0x00800000 motsvarar: 1,17549 10-38 Flyttalet 0xc7f00000 motsvarar: -122880 10. Skriv en C-funktion som kan omvandla ett binärt tal, givet som ett flyttal i enkel precision till dess decimala motsvarighet. Använd följande programskelett: float omvandla(unsigned int tal){ /* tal är givet i IEEE 754 representation */ } Tips: använd följande makron som kan definieras i C: #define tecken(x) ((0x80000000 & x) >> 31) #define exponent(x) ((0x7f800000 & x) >> 19) #define signifikand(x) (0x007fffff & x) tecken(x) skiljer ut teckenbiten i x och skiftar den 31 bitar till höger så att den kommer i minst signifikant position. exponent(x) skiljer ut exponenten i x och skiftar den 19 bitar åt höger. signifikand(x) skiljer ut signifikanden i x. Eftersom signifikanden redan tar upp den minst signifikanta positionen i x behöver man inte skifta den. Inte färdigt ännu. 36
Kapitel 4 1. Det finns ett antal syntetiska instruktioner definierade för MIPS vilka underlättar för assemblerprogrammeraren. Titta på följande instruktioner och avgör om det är en syntetisk instruktion eller en vanlig och om det är en syntetisk ska du skriva den eller de MIPS-instruktioner som den syntetiska instruktionen översätts till. Det enda register du får använda förutom de som står i instruktionen är at. Alla konstanter är i decimal notation. move t5,t3 # t5 = t3 clear t5 # t5 = 0 li t5,4567 # t5 = 4567 li t5,67000 # t5 = 67000 lw t5,65000(s0) # t5 = M[s0+65000] addi t5,t3,100000 # t5 = t3 + 100000 Samtliga instruktioner ovan är syntetiska. De första tre instruktionerna översätts till endast en annan instruktion (observera att vi även måste få använda register zero, annars går inte uppgiften att lösa): add t5,zero,t3 # t5 = t3 add t5,zero,zero # t5 = 0 addi t5,zero,4567 # t5 = 4567 De tre följande syntetiska instruktionerna kräver att vi använder ett temporärregister, eftersom de använder sifferkonstanter som är utanför intervallet 32768 till 32767 (det som går att få in i en sextonbitars konstant). lui at,1 # at = 65536 addi t5,at,1464 # t5 = 67000 lui at,1 # at = 65536 add at,at,s0 # at = 65536 + s0 lw t5,-536(at) # t5 = M[s0+65000] lui at,1 # at = 65536 ori at,at,34464 # at = 100000 add t5,t3,at # t5 = t3 + 100000 En alternativ lösning på den sista instruktionen är följande (Observera att även instruktionen addiu tolkar konstanten som ett sextonbitars heltal med tecken!): lui at,2 # at = 131072 addiu at,at,-31072 # at = 100000 add t5,t3,at # t5 = t3 + 100000 2. Skriv följande C-satser i MIPS-assembler: while (save[i] == k) i = i + j; Använd konstruktionen för while-slingor som presenterats i det här kapitlet. Anta att adressen till save finns i register a0. De övriga variablerna finns i register t0, t1 och t2. 37
Antag att variabeln i ligger i register t0, att j ligger i register t1 samt att k ligger i register t2. Antag vidare att save är en vektor som innehåller 32- bitars heltal. Om innehållet består av exempelvis en teckensträng så blir lösningen lite annorlunda. I lösningen antar vi att direktivet.set reorder har givits, för att slippa sätta ut -instruktioner. L0: sll t3, t0, 2 # Lägg 4*i i t3 add t4, a0, t3 # t4 = adressen till save[i] lw t5, 0(t4) bne t2, t5, L1 # Jämför save[i] och k add t0, t0, t1 # i = i + j b L0 L1:... 3. Skriv följande C-satser i MIPS-assembler: for (i = 0; i <= 100; i = i+1) a[i] = b[i] + c; Variabel i finns i register t0. Övriga variabler finns i minnet på adresserna A, B respektive C. Hur många instruktioner kommer att exekveras för dessa satser? Hur många datareferenser görs? Alternativ 1 : add t0,zero,zero # t0=0 la t2,a # Sparar adressen för A i t2 la t3,b # Sparar adressen för B i t3 la t4,c # Sparar adressen för C i t4 lw t4,0x0(t4) # Hämtar värdet på C addi t1,zero,100 # t1=100 for: sll t5,t0,2 # i=i*4 add t6,t2,t5 # Adressen för A[i] add t7,t3,t5 # Adressen för B[i] lw t7,0x0(t7) # Hämtar värdet B[i] add t7,t7,t4 # Skriver över B[i] med B[i]+C sw t7,0x0(t6) # Sparar i A[i] addi t0,t0,1 # i=i+1 ble t0,t1,for # if (i<100) goto for 10 datainstruktioner för en iteration + 6 för start=101*10+6=1016 1+2*i datareferenser=2*101+1=203 Alternativ 2 (något effektivare kod) : la t1,a # Sparar adressen för A i t1 la t2,b # Sparar adressen för B i t2 la t3,c # Sparar adressen för C i t3 38
lw t3,0x0(t3) # Sparar värdet på C i t3 addi t0,t1,400 # Adderar 100 ord på A's adress, lagrar i t0 addi t2,t2,400 # Adderar 100 ord på B's adress for: lw t4,0x0(t2) # Sparar B[i]'s värde i t4 addi t2,t2,-4 # Minskar B[i]'s adress med ett ord add t5,t4,t3 # Sparar B[i]+C i t5 sw t5,0x0(t0) # Sparar resultatet i A bne t1,t0,for # if (t1!=t0) goto for addi t0,t0,-4 # Minskar A's adress med ett ord 6 instruktioner för en iteration + 6 för start=612 1+2*i datareferenser=2*101+1=203 4. Skriv en funktion i MIPS-assembler, bfind, som letar upp den första förekomsten av bokstaven 'b' i en teckensträng (ASCII) som avslutas med tecknet NUL. Adressen till teckensträngen skickas som parameter i register a0 till funktionen och adressen till första förekomsten av bokstaven 'b' ska returneras i register v0. Om det inte fins något 'b' i strängen ska funktionen returnera 0. Vi antar även här att direktivet.set reorder har givits:.globl bfind.ent bfind # Deklarera bfind som funktion bfind: move v0, a0 addi t0, zero, 0x62 # Lägg 'b' i t0 L: lbu a0, 0(v0) # Ladda ett tecken beq a0, t0, L2 # Testa om a0 innehåller 'b' addi v0, v0, 1 # Öka till nästa index bne a0, zero, L # Testa om strängen är slut add v0, zero, zero # Nollställ v0 #(inget b hittades) L2: jr ra # Hoppa tillbaka.end bfind 5. Skriv en funktion i MIPS-assembler som beräknar Fibonacci-tal fib(n). I C kan man göra den här beräkningen rekursivt med följande funktion: int fib(int n) { if (n == 0) return 0; else if (n == 1) return 1; else return fib(n-1) + fib(n-2); } 39
Alternativ 1:.set noreorder fib: addi sp,sp, -8 #Gör utrymme på stacken sw ra, 0(sp) #Spara återhoppsadressen på stacken sw s0, 4(sp) #Spara s0 på stacken beq a0, zero, done #Om a0=0 så är vi klara add v0, a0, zero #v0=a0 om a0=0 eller a0=1 addi t0, zero, 1 #Lägg testvärde i t0 beq a0, t0, done #Om a0=1 så är vi klara add v0, a0, zero #v0=a0 om a0=1 addi s0, a0, -2 #Spara kommande inargument i s0 addi a0, a0, -1 #Lägg inargument i a0 jal fib #Kalla fib add a0, s0, zero #Lägg a0-2 som inargument till nästa anrop add s0, v0, zero #Lägg resultatet av det föregående anropet i s0 jal fib #Kalla fib add v0, v0, s0 #Addera resultatet av de två anropen done: lw ra, 0(sp) #Återställ återhoppsadressen lw s0, 4(sp) #Återställ s0 addi sp, sp, 8 #Återställ stackpekaren jr ra #Återvänd Alternativ 2:.set noreorder fib: addi sp,sp, -8 #Gör utrymme på stacken sw ra, 0(sp) #Spara återhoppsadressen på stacken sw s0, 4(sp) #Spara s0 på stacken beq a0, zero, done #Om a0=0 så är vi klara add v0, a0, zero #v0=a0 om a0=0 eller a0=1 addi t0, zero, 1 #Lägg testvärde i t0 beq a0, t0, done #Om a0=1 så är vi klara addi s0, a0, -2 #Spara kommande inargument i s0 jal fib #Kalla fib addi a0, a0, -1 #Lägg inargument i a0 add a0, s0, zero #Lägg a0-2 som inargument till nästa anrop jal fib #Kalla fib add s0, v0, zero #Lägg resultatet av det föregående anropet i s0 add v0, v0, s0 #Addera resultatet av de två anropen 40
done: lw ra, 0(sp) #Återställ återhoppsadressen lw s0, 4(sp) #Återställ s0 jr ra #Återvänd addi sp, sp, 8 #Återställ stackpekaren 41
Kapitel 5 1. Anta att du har åtta in-enheter kopplade till en dator på inportar med adress 0xafc00i0 där i är numret på in-enheten. Ett giltigt värde på inporten signaleras med värdet 1 på bit i på en statusport som finns på adress 0xafc0100. Skriv en funktion i C eller MIPS-assembler som använder cyklisk avfrågning (polling) och som returnerar först när en av in-enheterna har data på inporten. Funktionen ska returnera värdet på den första inporten som har giltig data. Här presenteras en funktion, getvalue, först i MIPS-assembler och sedan i C. I assemblerrutinen antar vi som vanligt att vi har överlåtit på assemblern att själv lägga in -instruktioner där det behövs. Vidare antar vi att portarna är på en byte, åtta bitar, vardera..globl getvalue.ent getvalue getvalue: lui t0,0xafc addi t0,t0,0x100 #ladda adressen för statusport L1: lbu t1,0(t0) #läs från statusport beq t1,zero,l1 #upprepa om ingen giltig data lui t2,0xafc #t2 = adressen för den första addi t2,t2,0x10 # inporten L2: andi t3,t1,1 #undersök vilken port som har bne t3,zero,l3 # giltig indata srl t1,t1,1 addi t2,t2,0x10 #öka adressen med ett steg b L2 L3: lbu v0,0(t2) #lagra resultatet i v0 jr ra.end getvalue Här presenteras en version i C, som använder samma metod som ovan för att hämta värdet: unsigned char getvalue() { /* status = pekare till statusporten */ unsigned char *status = 0xafc0100; /* inport = pekare på dataportarna */ unsigned char *inport = 0xafc0010; unsigned char temp; /* vänta på data */ temp = *status; while (temp == 0) { temp = *status; } /* hitta rätt inport */ 42
} while (temp & 0x01 == 0) { temp = temp >> 1; /* kolla nästa bit */ inport = inport + 0x10; /* nästa inport */ } return *inport; /* returnera värdet på inporten */ 2. Anta att ett program spenderar den mesta tiden att exekvera utan att externa avbrott är tillåtna. Då och då tillåts avbrotten igen genom en rutin som sätter rätt bit i statusregistret men de stängs av lika snabbt igen, så här: enable_interrupts(); disable_interrupts(); Det kan tyckas meningslöst att bara tillåta avbrott under en så kort tidsperiod som mellan dessa anrop. Förklara varför det inte är så? Avbrotten ligger kvar ( interrupt pending ) tills de har kvitterats. På grund av detta kommer alla avbrott att inträffa så fort interrupten sätts på (dvs oavsett hur liten luckan är då avbrotten är på). Inga avbrott förloras om det inte inträffar mer än ett avbrott av varje typ mellan de gånger som avbrotten är på. Denna tekniken fungerar bra om avbrotten sätts på tillräckligt ofta samt att inte den ökade latensen på grund av de försenade avbrotten har betydelse för resultatet. 3. Du ska utöka funktionaliteten hos klockprogrammet som finns beskrivet på sidorna 166-173. Antag att det finns en knapp kopplad till avbrottsingång 5. När denna knapp trycks ned ska en rutin anropas som nollställer klockans alla variabler (tick, hun, sek, min, tim) till noll. a) Skriv den rutin som nollställer klockans variabler. b) Ändra den existerande avbrottsrutinen så att rätt rutin anropas när avbrott 5 kommer. c) Ändra i huvudprogrammet så att avbrott 5 initieras korrekt. a) Denna rutin kan exempelvis se ut så här:.globl resetclk.ent resetclk resetclk: sw zero,tim # Syntetiska instruktioner sw zero,min # (använder at - registret) sw zero,sek sw zero,hun sw zero,tick jr ra.end resetclk b) 43
Vi definierar först en mask för interrupt 5, så här: #define EXT_INT5 0x8000 /* mask för int5 i Cause */ Därefter ändrar vi lite i koden för avbrottsrutinen. Byt ut allt mellan labeln testa_int4 och internal_int med följande: testa_int4: andi t0,k0,ext_int4 beq t0,zero,testa_int5 # det var int4, anropa utskriftsrutinen jal printtime # vi är klara, hoppa till återställningskoden b restore testa_int5: andi t0,k0,ext_int5 beq t0,zero,internal_int # det var int 5, nollställ timervariablerna jal resetclk b restore internal_int: c) Det enda som behöver ändras är raden där vi sätter masken för vilka avbrott som ska tillåtas. Byt därför raden ori t0,t0,ext_int3 EXT_INT4 mot raden ori t0,t0,ext_int3 EXT_INT4 EXT_INT5 så tillåts även avbrott 5. 4. Avbrottsrutinen för klockan i uppgift 3 har den stora nackdelen att avbrotten är avstängda för en relativt lång tidsperiod. Skriv om avbrottsrutinen så att den i sin tur kan avbrytas av ett avbrott med högre prioritet. Avbrotten ska vara avstängda så kort tid som möjligt. När ett avbrott inträffar, så sätts IEc-biten i statusregistret till noll. För att tillåta avbrott igen så måste man sätta denna biten. Om vi inte vill att vårt eget avbrott ska kunna inträffa igen innan vi är klara, så måste detta avbrottet stängas av innan vi tillåter avbrotten igen. Detta sker genom att man manipulerar bitarna i statusregistret. Eftersom vi nu tillåter andra avbrott under tiden som vi är i avbrottshanteraren, så kan vi inte använda registren k0 och k1 lika fritt som förut de kan ju bli överskrivna när vi får ett annat avbrott. En annan sak som är viktig att hålla i minnet är att externa avbrott kommer att ligga kvar ända tills de är kvitterade. Det betyder att om vi glömmer kvittera ett avbrott så kommer vi omedelbart att få ett nytt så snart avbrotten tillåts igen. I denna lösningen antar vi att avbrotten kvitteras genom att skriva till en dataport, nämligen minnesadress 0xbfa90000 för int3, 0xbfa90010 för int4, samt 0xbfa90020 för int5. Hur avbrotten kvitteras beror väldigt mycket på hur hårdvaran är konstruerad, så metoden man får använda kan skilja ganska mycket mellan olika datasystem. Här kommer en lösning som tar dessa saker i beaktning (endast rutinen 44
int_routine har ändrats):.globl int_routine.ent int_routine int_routine: subu sp,sp,17*4 sw ra,12*4(sp) sw v0,11*4(sp) sw v1,10*4(sp) sw t0,9*4(sp) sw t1,8*4(sp) sw t2,7*4(sp) sw t3,6*4(sp) sw t4,5*4(sp) sw t5,4*4(sp) sw t6,3*4(sp) sw t7,2*4(sp) sw t8,1*4(sp) sw t9,0*4(sp) mfc0 k0,c0_sr mfc0 k1,c0_epc sw k0,13*4(sp) sw k1,14*4(sp).set noat sw AT,15*4(sp) # Hämta statusregistret # och EPC, och spara dessa # at måste också sparas.set at # Kontrollera vilket avbrott det var mfc0 k0,c0_cause andi t0,k0,excmask bne t0,zero,internal_int # Ta bort bitar för avbrott som är maskade mfc0 k1,c0_sr andi k1,0xff00 # IP- och SW-bitarna and k0,k0,k1 # Behåll bara påslagna avbrott andi t0,k0,ext_int3 beq t0,zero,testa_int4 # Det var int3, stäng av int3 och tillåt andra mfc0 t1,c0_sr andi t1,t1,~ext_int3 # Maska bort int3 mtc0 t1,c0_sr # Ge masken tid att aktiveras ori t1,t1,1 # Sätt IEc = 1 mtc0 t1,c0_sr # Aktivera avbrott # Från och med nu kan vi inte använda k0 och k1, # eftersom de kan användas av en annan avbrotts- # rutin. jal timer # Anropa timerrutin # Vi är färdiga med int3, kvittera detta och # hoppa till återställningskoden lui t0,0xbfa9 sw zero,0(t0) b restore # Kvittera int3 (se texten ovan) 45