I am attempting to write a program in MIPS that uses polling to read a character from the keyboard and then displays it using the builtin Keyboard and Display MMIO Simulator. Unfortunately, I am having trouble grasping the concept behind the registers used and the control bits, but have been trying to figure it out from examples online.
Here is what I have written so far:
.data
.text
.globl main
main:
.eqv RCR 0xffff0000 # Receiver Control Register (Ready Bit)
.eqv RDR 0xffff0004 # Receiver Data Register (Key Pressed - ASCII)
.eqv TCR 0xffff0008 # Transmitter Control Register (Ready Bit)
.eqv TDR 0xffff000c # Transmitter Data Register (Key Displayed- ASCII)
keyWait: lw $t0, RCR
andi $t0, $t0, 1
beq $t0, $zero, keyWait
lb $a0, RDR
What I believe is happening here is that keyWait continues to loop until the ready bit is set to 1 (*), and then the ASCII value of the key is stored in $a0. Is this correct?
If this is correct, how can I then print the character on the display using the same polling technique with the TCR and TDR?
*I don't understand why this is needed to be checked with the andi operation - if $t0 is 1 (or 0), it is anded with 1, which equals 1 (or 0), which is then stored in $t0? So the value of $t0 stays as it was prior to this operation regardless?
EDIT
Here is my attempt at passing the key pressed data to the display.
.data
.text
.globl main
main:
.eqv RCR 0xffff0000 # Receiver Control Register (Ready Bit)
.eqv RDR 0xffff0004 # Receiver Data Register (Key Pressed - ASCII)
.eqv TCR 0xffff0008 # Transmitter Control Register (Ready Bit)
.eqv TDR 0xffff000c # Transmitter Data Register (Key Displayed- ASCII)
keyWait: lw $t0, RCR
andi $t0, $t0, 1
beq $t0, $zero, keyWait
lbu $a0, RDR
displayWait: lw $t1, TCR
andi $t1, $t1, 1
beq $t1, $zero, displayWait
sb $a1, TDR
li $v0, 11
syscall
j keyWait
This is just a test program which should print the character on the MMIO Display Simulator, and also in the console within MIPS. It is doing the latter, but not on the display simulator. In the display simulator, Cursor: is incrementing each time a key is pressed, but nothing is being printed. What am I missing?
You probably didn't click "Connect to MIPS".
See this answer: How to print to the screen from MIPS assembly
When I was testing I've found that if you stop simulation, reload your program, you probably have to click on "Disconnect from MIPS" and then "Connect to MIPS" again (i.e. toggle it a bit)
Also, a bug: The data from RDR is in $a0, but you are storing into TDR from $a1
From the link, notice that the style is slightly different than yours. In the link, they use offsets from a base register rather than hardwiring the addresses. This is much more usual for devices [in device drivers in the real world], particularly if the base address of the device could be configured/changed.
Also, in mars, if you look at the actual low level instructions generated from the pseudo-ops in the hardwired vs. base register versions, the base register version is more compact (i.e. fewer injected lui insts)
So, I've restyled your code to use a base register:
.data
.eqv MMIOBASE 0xffff0000
# Receiver Control Register (Ready Bit)
.eqv RCR_ 0x0000
.eqv RCR RCR_($s0)
# Receiver Data Register (Key Pressed - ASCII)
.eqv RDR_ 0x0004
.eqv RDR RDR_($s0)
# Transmitter Control Register (Ready Bit)
.eqv TCR_ 0x0008
.eqv TCR TCR_($s0)
# Transmitter Data Register (Key Displayed- ASCII)
.eqv TDR_ 0x000c
.eqv TDR TDR_($s0)
.text
.globl main
main:
li $s0,MMIOBASE # get base address of MMIO area
keyWait:
lw $t0,RCR # get control reg
andi $t0,$t0,1 # isolate ready bit
beq $t0,$zero,keyWait # is key available? if no, loop
lbu $a0,RDR # get key value
displayWait:
lw $t1,TCR # get control reg
andi $t1,$t1,1 # isolate ready bit
beq $t1,$zero,displayWait # is display ready? if no, loop
sb $a0,TDR # send key to display
li $v0,11
syscall
j keyWait
I did a little "fancy footwork" with the .eqv above. In the general case, even though it's a bit more verbose, it may be clearer to do:
.eqv RCR 0x0000
lw $t0,RCR($s0) # get control reg
Related
I have written a MIPS assembly language code using sw instruction so that I can only replace the 1st character of a string with a character of my choice.
But, what happens is, the instead of only changing one character, the code changes the 1st character plus destroys characters in next three bytes.
How can I get it right?
I have written the following code:
# replace 1st character of a string
.data
string: .asciiz "ABCDEFGH"
.text
main:
# load string's 1st address into the memory
la $a0, string
li $t0, 'X'
#addi $t0,$t0, 48
sw $t0, ($a0)
# print string
la $a0, string # load 1st address of the string
li $v0, 4 # syscall for string print
syscall
# exit program
li $v0, 10
syscall
Input: ABCDEFGH
Expected result: XBCDEFGH
Actual result: X
You incorrectly use sw that stores a word, ie a 4-bte data.
In your algorithm, after the instruction
li $t0, 'X'
you write 'X' as a 32 bits word in your t0 register. Probably your machine is configured as little endian and $t0, that is a 32 bits register holds the value 0x00000058 (0x58 is the ascii code of X).
When you write it to memory with sw $t0, ($a0), all the 32 bits are written and the content of your memory, that was originally "ABCDEFGH" becomes "X\0\0\0EFGH".
When you ask to print it, the '\0' at position string+1 is considered as an end-of-string terminator and you have just 'X' displayed.
The fix is just to replace the line with
sw $t0, ($a0)
with
sb $t0, ($a0)
and only the least significant byte of your register (ie 'X') is written to memory.
Recently while starting to learn MIPS in university, I've come across a problem while trying to print 1 string, accept a user input, and then print another string and accept a user input. Both user inputs should be stored to registers a0 and a1 respectively.
The names of each string are promptD for the Dividend input, and enterD for the Divisor input (you might guess this is an unsigned division calculator program).
In my debugging attempts, I have narrowed the problem to a small snippet of the code, posted below.
I think I am incorrectly offsetting my first .data register to reach my 2nd .data register. The problem I am noticing as I've tried QTspim, xspim, PCspim, and MARS is that all 4 of these give the first string in .data a different initial register address.
For example: The string "Enter Dividend" will be in reg address 0x10010000 in MARS but will start in 0x10000000 in PCspim. The following register address for "Enter Divisor" will be in either 0x10010011 in MARS or 0x10000010 in PCspim.
In its current state thru MARS, the program snippet below asks the user to input dividend, and it will store the value. Immediately after storing to a0, the code will fail due to a line 37 (which is just the 3rd syscall) runtime exception at 0x00400024: address out of range 0x00000004. It is not prompting "Enter Divisor" at all.
To really see the problem in action, I think running this in MARS would help make it more clear. Is it a offsetting issue? Am I clobbering a register without seeing it? I haven't found much MIPS help on here that deals with problems without pseudo-instructions. I realize with them, I could load an address directly (la)...but I can't use them here.
Thanks
.globl main
.data #for the data
promptD: .asciiz "Enter Dividend \n"
enterD: .asciiz "Enter Divisor \n"
# result: .asciiz "Result = "
.text #for the instructions
main:
#for Dividend
addi $v0, $0, 4 #store string instr to v0
lui $a0, 0x1001 #address of promptD
syscall #display promptD
addi $v0, $0, 5 #store input instr to v0
syscall # Get dividend
add $a0, $0, $v0 # Dividend to $a0
#for Divisor
addi $v0, $0, 4 #store string instr to v0
lui $a1, 0x1001 #Where I think the problem is...
#Address of first string followed by add offset?
addi $a1, $a1, 33 #Maybe incorrect offset?
syscall #display enterD
addi $v0, $0, 5 #store input instr to v0
syscall # Get divisor
add $a1, $0, $v0 # Divisor to $a1
#end snippet
Here's the problematic code:
lui $a1, 0x1001 #Where I think the problem is...
#Address of first string followed by add offset?
addi $a1, $a1, 33 #Maybe incorrect offset?
You're using the wrong register. The argument for syscall 4 should be placed in $a0, not $a1.
The offset 33 is incorrect. If you look at the Data Segment viewer in Mars you can see that the NUL-terminator byte for promptD is located at 0x10010010, and that the enterD string begins at 0x10010011 (if you have a hard time reading hexadecimal ASCII codes you can tick the "ASCII" checkbox in the Data Segment viewer to view the data as characters). So the offset you should be using is 0x11 (17 decimal).
I'm trying to write a method to do a caesar shift on a string of text in the MIPS assembly language. My encryption method is as follows:
encryptMessage:
la $s0, message #s0 will hold message that will be iterated through
lw $t1, key #s1 will hold the key to shift by
li $t0, 0 #t0 will be iterator, starting at 0
encryptionLoop:
add $s1, $s0, $t0 #$s1 = message[i]
lb $s2, 0($s1) #Loading char to shift into $s2
beq $s2, $zero, exit #Breaking the loop if we've reached the end: http://stackoverflow.com/questions/12739463/how-to-iterate-a-string-in-mips-assembly
add $s2, $s2, $t1 #Shifting the character by the key amount
la $s1, ($s2) #Changing the character in message to the shifted character
addi $t0, $t0, 1 #i++
j encryptionLoop #Going back to the beginning of the loop
However, in the exit method where I print out the supposedly encrypted message, it just prints out the message as it was originally entered. My code isn't "remembering" that I changed the characters and I can't figure out how to get it to remember. I suspect this line
la $s1, ($s2) #Changing the character in message to the shifted character
has something to do with it but I don't know how to fix it.
Figured it out. That line I suspected should have been
sb $s2 ($s1)
I need to save a data that contains: name, id and rating.
So the insert command is kind this one:
addi $sp, $sp, -12
li $v0,8 # take in input
la $a0, buffer # load byte space into address
move $t0,$a0 # save string to t0
syscall
sw $t0, 0($sp) # name
sw $t1, 4($sp) # id
sw $t2, 8($sp) # rating
addi $t7,$t7,1 # number of elements
The view command is kind this one:
loop:
beq $t6, $t7, end
lw $a0, 0($sp) # name
li $v0, 4
syscall
lw $a0, 4($sp) # id
li $v0, 1
syscall
lw $a0, 8($sp) # rating
li $v0, 1
syscall
addi $t6,$t6, 1
addi $sp,$sp, 12
j loop
When make two elements the first one: aa, 12, 12 second one : bb, 13, 13
After the view command is this: aa, 12, 12 second one: aa, 13, 13
Can you help me solve this problem?
It looks like you're passing the same address (buffer) to the read_string syscall every time. So each call will overwrite the string from the previous call.
You need to have separate buffers for every string. You could for example pass buffer to syscall 8, then calculate the length of the read string, allocate memory for it (including the NULL-terminator) using syscall 9, copy the string from buffer to your newly allocated block of memory, and finally store the address of the allocated memory on the stack instead of buffer's.
I'm trying to print out
Hello, world.
ello, world.H
llo, world.He
lo, world.Hel
o, world.Hell
, world.Hello
world.Hello,
world.Hello,
orld.Hello, w
rld.Hello, wo
ld.Hello, wor
d.Hello, worl
.Hello, world
Hello, world.
using only this string in memory String: .asciiz "Hello, world.". So far this is my code:
.text
String: .asciiz "Hello, world."
x: .float 13
.data
Main: lw $t0,x
jal PrintString
li $v0, 10
syscall
PrintString: la $a0,String
li $v0,4
syscall
bgtz $t0, MoveString
jr $ra
MoveString: (CODE)
but I'm not sure what to put in the MoveString label. I need to shift the characters in String by one, and subtract what is in x by 1. I'm not sure how to go about doing this. Thanks for your help!
A couple notes first:
You have .text and .data mixed up
Go ahead and add a newline to the end of the string, it'll make displaying a bit nicer:
String: .asciiz "Hello, world.\n"
Assuming that 13 is used for the length of the string, you should use an integer instead of a float:
x: .word 13
Now, onto the problem. There's two ways to do this: 1) rotate the string in-place and call syscall 4 each time, or 2) keep the string as it is and print it one letter at a time, playing with the index instead to achieve the rotation effect. You've chosen method 1, so let's go with that.
Start by writing some pseudocode. You need a subroutine that'll rotate the string stored at $a0 to the left by one letter. Immediately, you should be thinking about some kind of loop that iterates over each letter in the string:
for int index from 0 to 12
copy letter from (index + 1) to index
But wait: what happens to the first letter? It'll get clobbered. And what happens when we reach the end? We'll copy something outside the bounds of String to the last letter slot. So let's work around it by storing the first letter to a temporary register:
temp = letter[0]
for int index from 0 to 11
copy letter from (index + 1) to index
letter[12] = temp
That's better; that should do it. Next question: how do we do this in MIPS?
We know that $a0 will hold the address of the string, so let's take that for granted. We need at least one temporary register -- since we're already using $t0 for x, let's use $t1 to hold the first letter:
MoveString:
lb $t1, String
We also need an index register, so let's use $t2 for that. Initialize it to zero:
li $t2, 0
It'd be nice to just increment our address register once for each letter, but we don't want to clobber $a0 because it'll mess up PrintString. So let's copy it to $a1 for our loop:
move $a1, $a0
Lastly, we need to know how long the string is, so let's load another copy of x into $t3:
lb $t3, x
sub $t3, $t3, 1 ; We're only iterating over the first 12 letters,
; since the last letter is done manually with temp
Now we can start the loop. We need to copy the letter (which is just a byte) from $a1 + 1 to $a1:
MoveStringLoop:
lb $t4, 1($a1) ; Load the letter from (address + 1)
sb $t4, 0($a1) ; Store it to (address)
add $a1, $a1, 1 ; Increment our address
add $t2, $t2, 1 ; Increment our index
blt $t2, $t3, MoveStringLoop ; Loop again if index < (length - 1)
sb $t1, 0($a1) ; Copy the temp letter to the end of the string
That should be it. After that, we can decrement x and go back to PrintString:
sub $t0, $t0, 1
b PrintString
This isn't an optimal solution by any means; I'm sure a real compiler and some better coding could get the job done in half the instructions. But since you're learning how to write assembly, don't worry about micro-optimization for now. Here's the final code along with its output:
.data
String: .asciiz "Hello, world.\n"
x: .word 13
.text
Main: lw $t0,x
jal PrintString
li $v0, 10
syscall
PrintString: la $a0,String
li $v0,4
syscall
bgtz $t0, MoveString
jr $ra
MoveString:
lb $t1, String
li $t2, 0
move $a1, $a0
lb $t3, x
sub $t3, $t3, 1
MoveStringLoop:
lb $t4, 1($a1)
sb $t4, 0($a1)
add $a1, $a1, 1
add $t2, $t2, 1
blt $t2, $t3, MoveStringLoop
sb $t1, 0($a1)
sub $t0, $t0, 1
b PrintString
Output:
Hello, world.
ello, world.H
llo, world.He
lo, world.Hel
o, world.Hell
, world.Hello
world.Hello,
world.Hello,
orld.Hello, w
rld.Hello, wo
ld.Hello, wor
d.Hello, worl
.Hello, world
Hello, world.