Minnes-mappad I/O |
I tidigare uppgifter har vi använt "fusket" syscall för att skriv ut strängar och heltal. Det har nu blivt dags att kolla under huven och se hur vi själva kan konstruera liknande rutiner med hjälp av minnes-mappad I/O.
Senast uppdaterat: Tue Nov 29 17:24:26 CET 2005
~ karl
Innan ni fortsätter skall ni läsa igenom avsnitt A.8 "Input and Output" i "Assemblers, Linkers, and the SPIM Simulator". I detta avsnitt beskrivs hur SPIM simulerar en så kallad minnes-mappad I/O-enhet. Titta även igenom era föreläsningsanteckningar.
Anledningen till att det kallas minnes-mappad I/O är att I/O-enheten kontrolleras av ett antal register. Men, till skillnad från riktiga register (t.ex. $t0) är inte detta några riktiga register utan varje kontrollregister representeras av ett ord i minnet.
Hur går det egentligen till när man läser in en sträng som matas in via tangentbordet? I SPIM är I/0-enheten mycket enkel och kan endast lagra ett tecken åt gången. För att läsa in en hel sträng måste vi alltså läsa in ett tecken i taget.
I spim kontrolleras den minnesmappade I/0-enheten av följande fyra minnesmappade register:
Vid inläsning från tangentbordet använder vi oss av de minnesmappade registren Receiver Controll och Receiver Data. När en tangent trycks ner ändras den minst signifikanta biten i Receiver Controll från 0 till 1 och ASCII-värdet av det inlästa tecknet lagras i den minst signifikanta byten i Receiver Data.
Innan det fans något som ens liknade en bildskärm gick det ändå att mata in data och erhålla resultat från dåtidens "data-maskiner". En vanlig metod var att lagra program och data på så kallde hålkort av papper. För varje punkt på pappret kan en bit lagras (hål eller inte hål). Resultat kunde sedan skrivas ut på någon typ av skrivare.
Ett annat exempel på ett mekaniskt I/0-system är det självspelande pianot eller som det egentligen heter pianola. Indata (noter) kodas som en serie av hål på en pappersrulle. Utdata (musik) uppstår när pianolan avkodar (spelar) de på pappersrullen kodade noterna.
I denna uppgift kommer vi inte att använda oss av avbrottstyrd I/0 och detta innbär att alla interrupt enable-bitar kommer att vara satta till 0.
För att upptäcka när ett tecken hamnat i Receiver Data behöver vi endast kolla om innehållet i Receiver Controll är skillt från noll. Hur och när skall vi kolla om det hamnat ett tecken i Receiver Data?
Om vi först ber användare att mata in en sträng kan vi göra en loop som kollar värdet i Receiver Controll. När detta blir skillt från noll vet vi att ett tecken lästs in. Att med jämna mellanrum kolla värdet av till exempel ett kontrollregister brukar kallas att polla (av engelskans poll = "undersöka").
Om vi till exempel vill lagra talet fyra i register $t0 kan vi göra så här:
addi $t0, $zero, 4
Varje I/O kontrollregister representeras av ett ord i minnet. Till exempel motsvarar Transmitter Data-registret av innehållet på adress 0xffff000c.
addi $t0, $zero, 0xffff000c # Address to Transmitter Data Registret
För att att kontrollera om det skett en tangenttryckning måste vi läsa innehåller i Receiver Controll-registret, dvs läsa innehållet på address 0xffff000c. För att läsa något från en adress behöver vi stoppa in adressen i ett register.
Ni skall bygga vidare på koden nedan. OBS: För att det skall fungera att köra spim med minnesmappad I/O måste spim startas med en speciell flagga:
xspim -mapped_io -file uppgift_fyra.s
############################################################################## ## ## DESCRIPTION: Computer Systems 1 - part 1 (DARK) ## ## Assignment 4: Polled, memory mapped I/0. ## ## ## AUTHOR(s): <student_1234@student.uu.se> ## ## <student_6789@student.uu.se> ## ## ## RUN: xspim -mapped_io -file uppgift_fyra.s ## ## or ## ## spim -mapped_io -file uppgift_fyra.s ## ## ## HISTORY: November XX, 2004. First version. ## ############################################################################## ############################################################################## ## Data Segment ############################################################################## .data # Memory mapped I/0, i.e., each I/O device register appears as a # special memory location. These addresses are to large to use # as an immediate value so we store them as "constants" in memory. RECEIVER_CONTROL: .word 0xffff0000 RECEIVER_DATA: .word 0xffff0004 TRANSMITTER_CONTROL: .word 0xffff0008 TRANSMITTER_DATA: .word 0xffff000c # A 80 character string buffer. BUFF: .space 80 # Some predefined strings: MSG_1: .asciiz "Enter a string: " MSG_2: .asciiz "You entered : " NL: .asciiz "\n" ############################################################################## ## Text Segment ############################################################################## .text .globl main .globl dbg #----------------------------------------------------------------------------- # Main Start #----------------------------------------------------------------------------- main: addi $sp, $sp, -4 # Push return address. sw $ra, 0($sp) start: # "Enter a string:" la $a0, NL jal ouputString la $a0, MSG_1 jal ouputString la $a0, BUFF jal inputString la $a0, NL jal ouputString # "You entered:" la $a0, MSG_2 jal ouputString la $a0, BUFF jal ouputString la $a0, NL jal ouputString j start # This makes the program loop forever. lw $ra, 0($sp) # Pop return adress and return to caller (exit) addiu $sp, $sp, 4 jr $ra #----------------------------------------------------------------------------- # Main End #----------------------------------------------------------------------------- #------------------------------------------------------------------------------ # DESCRIPTION: Reads one character from the console using polled memory # mapped I/O. # # OUTPU: $v0 - ASCII code of the character read from the console. #------------------------------------------------------------------------------ inputCharacter: # Check if the receiver is ready, if not, # simple spin and poll until ready. dbg: jr $ra #------------------------------------------------------------------------------ # DESCRIPTION: Print one character to the display using polled memory # mapped I/O. # # INPUT: $a0 - ASCII code to print #------------------------------------------------------------------------------ printCharacter: # Check if the transmitter is ready, if not, # simple spin and poll until ready jr $ra #------------------------------------------------------------------------------ # DESCRIPTION: Reads a string of characters from the console and store it as # a NULL-terminated string in the buffer pointed to by $a0. # The input string ends when the user presses ENTER. # # This subroutine works by polling the memory mapped input # device. # # DEPENDENCIES: Uses inputCharacter to read characters from the console. # # Uses printCharacter to echo each typed character to # the console. Otherwise the user will no see what he/she # types. # # INPUT: $a0 - Address to first byte in buffer. #------------------------------------------------------------------------------ inputString: jr $ra #------------------------------------------------------------------------------ # DESCRIPTION: Output a NULL-terminated string to the console. # # This subroutine works by polling the memory mapped output # device. # # DEPENDENCIES: Uses printCharacter to print each character one by one. # # INPUT: $a0 - address to first character in null-terminated # string to print #------------------------------------------------------------------------------ ouputString: jr $ra
Om ni kollar i början main ser ni att outputString anropas två gånger. Först en gång med strängen NL som endast innehåller tecknet för ny rad och sedan med strängen MSG_1.
En bra början är fundera igenom hur subrutinen outputString kan tänkas se ut. En sträng är inget annat än en array av bytes där varje byte utgör ett ASCII-kodat tecken. Om vi bara har ett sätt att skriva ut ett tecken kan vi sedan skriva en loop som kollar på varje tecken i strängen och skriver ut ett i taget tills strängen är slut.
För att skriva ut ett tecken anropar vi subrutinen printCharacter. Har vi fått detta att fungera kommer vi fortfarande inte att se något vid en testkörning. Genom att anväda en brytpunkt i loopen (och sedan steppa) kan vi dock kontrollera att loopen fungerar (kontrollera att vi läser rätt ASCII-värden).
Nu är det dags att skriva subrutinen printCharacter. Ni skall polla Transmitter Controll-registret och när transmittern är redo är det dags att skriva ASCII-värdet för det tecken man vill skriva ut till Transmitter Data-registret. Även här kan det vara praktiskt att sätta en brytpunkt, steppa och kolla. Särskillt intressant är att sätta en brytpunkt och kontrollera värdet på innehållet i Transmitter Controll-registret (ready bit).
För att simulera att det tar tid att skriva ut via I/0-enheten gör SPIM så att ready-biten för Transmitter Controll-registret inte blir 1 (ready) omedelbart efter det att ett tecken skrivits ut på konsollen. Tiden som det tar att skriva ut ett tecken mäts i antalet exekverade instruktioner. Om man steppar kan man alltså behöva utföra flera varv i poll-loopen innan ready-biten blir 1.
Ibland uppför sig I/O-enheten i SPIM lite konstigt och ready-biten blir alrdig 1 (ready) oavsett hur länge man kör poll-loopen. Genom att klicka på konsollen och trycka på valfri tangent händer något magiskt inne i SPIM som gör att ready-biten sätts till 1, dvs ready. (bug?).
När ni fått subrutinenrna outputString och printCharacter att fungera skall strängarna skrivas ut på konsollen vid testkörning. Ni blir kanske tvunga att använda tricket ovan.
Har man lyckats skriva en fungerande printCharacter rutin är det en lätt mach att skriva klart inputCharacter, den fungerar i stort sett på samma sätt.
Sedan är det dags att använda inputCharacter och printCharacter för att skriva klart inputString.
Om ni kollar i main ser ni att innan anropet till inputString så sätts $a0 att innehålla adressen till den första byten i bufferten BUFF. Subrutinen inputString skall inte känna till något om main utan endast bero på indata. Alltså skall det fungera lika bra att anropa rutinen med någon annan buffer.
Observera att all kod ni skriver skall vara försedd med lämpliga kommentarer.
En testkörning med det färdiga programmet bör se ut ungefär såhär:
Enter a string: Hej You entered : Hej Enter a string: Monica You entered : Monica Enter a string:
Innan ni lämnar in, visst har ni kollat att allt funkar för några olika strängar, speciellt intressant är förstås fallet tom sträng.
Det är inte tillåtet att använda några syscalls för att lösa uppgiften.