I am perfectly aware that this lab can be solved by ChatGPT. I don’t care if ChatGPT can solve it. I want to know if you can solve it. Don’t be a cheater.
Continuing the theme of “CS0007 but in MIPS and really accelerated,” this lab will have you write a simple temperature conversion program. With this lab, you’ll learn:
- how to print out strings
- how to get string input from the user
- how to make decisions and do simple control flow
- how to do more complex mathematical computations
Also, if you feel confused or uncertain or “out of your depth” during this lab, that is intentional. It’s totally expected that you feel weird when learning to write asm. Just keep this in mind: the solution is probably a lot simpler than you’re expecting. It might be tedious and long-winded, but it’ll still simple. The computer is a dumb machine.
1. Getting started and printing strings
Maybe you should open up your lab 0 in MARS to refer back to it!
- Make a new file in MARS and save it as
abc123_lab1.asm
whereabc123
is your username. - Just like lab 0, put your full name and username in comments at the top, and make your
main
function label.- Don’t forget the
.global main
.
- Don’t forget the
- Assemble to make sure you got the syntax right.
What are strings, anyway?
The first 128 characters of Unicode are ASCII, so technically any ASCII string is also Unicode.
Strings are arrays of characters, and each character is really a number with an agreed-upon meaning. That “agreement” is called a character encoding and says things like “the number 97 means lowercase a
.” The most widespread encoding today is Unicode, but there is an older encoding called ASCII which many programs in the English-speaking world use. Go have a look at this table, paying attention to the Dec
and Chr
columns.
This means that "abc"
is really an array of 3 numbers: {97, 98, 99}
. But you also need to know how long the string is, or at least where the end of it is. The convention in both C and MARS is to use a zero terminator: a character with the value 0 at the end of the string. That means "abc"
is represented like this in memory:
97 |
98 |
99 |
00 |
In ASCII, each character - that is, each number - is one Byte. (That’s how many bits?)
Can you put a string in a register?
I said in lab 0 that registers are like the CPU’s hands. Just like your hands, they’re limited in size. You can’t fit a car in your hands. But you can use your hands to point to a car. 👉🚗 You can use registers to point to something too.
Strings are arrays, and arrays are too big to fit in reigsters. Instead, we can only put the address of a string in a register. The address is where the string is located in memory, and it is a 32-bit number in the version of MIPS that we’re using. Conveniently, our registers are 32 bits too!
So no, you can’t put a string in a register, but you can put a string’s address in one.
Hello world at last
In Java, you can just write strings anywhere you like, and the compiler takes care of all of that crap for you. But in asm, we have to be a little more… literal.
Did you just scroll down to this and skip all the stuff above? Of course you didn’t! You totally read all the stuff I wrote above. All that information I could definitely ask about on an exam. Yep. You read it!
- Before your
.global main
line:- Add a
.data
line. This tells the assembler, “switch to the data segment.”- The data segment is the part of memory where we put… data. Variables, strings, etc.
- After that line, write this:
hello_msg: .asciiz "hello, world!\n"
.asciiz
says, “encode the following string asascii
, with az
ero terminator.”- You know what
\n
does, right?
- After that line, put a
.text
line. This tells the assembler, “switch to the text segment.”- The text segment is where code goes.
- Add a
- In
main
(that is, after themain:
line):- Write
la a0, hello_msg
la
stands forl
oada
ddress. It puts the address of a label into a register.- So, this will put the address of
hello_msg
intoa0
.
- Then, do syscall 4.
- Remember how you do a syscall? Look back at lab 0.
- Write
- Assemble and run.
- It should print out your
"hello, world!"
message. - You can also assemble and step one instruction at a time - if you turn on “Hexidecimal Values”, you can see the address
0x10010000
get put intoa0
.
- It should print out your
Try this: what error do you get if you comment out the .text
line? Well now you know what to do if you see that error. :) Similarly, try commenting out the .data
line. Those directives are easy to forget.
A few things to notice
While you are on the Execute tab, there are three things I want you to look at:
- In the Text Segment panel at the top, you can see the instructions that you wrote, but you can also see the “Basic” column looks a little different.
- If you can’t see the instructions in the Text Segment panel… make MARS wider. Lol.
- Both
la
andli
are pseudoinstructions: “fake” instructions that the assembler accepts and rewrites to simpler instructions that a MIPS CPU can actually understand. la
is rewritten to two instructions,lui
andori
. It’s fine. Sometimes that happens.
- In the Labels panel, you should see one entry:
hello_msg 0x10010000
- That
0x10010000
is the memory address that the assembler gave to thehello_msg
string. - Memory addresses are virtually always displayed in hexadecimal.
- That
- If you check the
ASCII
box at the bottom of the Data Segment panel, you can see your string at the top-left!- You can see
0x10010000
- the address - on the left side. - You can also see that after the end of the string is
\0
- that’s the zero terminator. (There are many more\0
bytes in memory after it, but that one was put there on purpose by the assembler.)
- You can see
2. The temperature converter
Here’s how the temperature converter will work:
- Prompt the user to
"enter a temperature: "
- Read an integer from the user.
- Put that integer ssssssomewhere sSSSSSSSSSSSsssSssafe. (Lab 0 again.)
- Prompt the user to
"enter C to convert C -> F, or F to convert F -> C: "
- Read a string from the user.
- If the first character of that string is
F
:- Convert the temperature from Fahrenheit to Celcius:
C = (F - 32) * 5 / 9
- Display the conversion in the form
"X Fahrenheit in Celcius is Y"
- Exit.
- Convert the temperature from Fahrenheit to Celcius:
- Else, if the first character of that string is
C
:- Convert the temperature from Celcius to Fahrenheit:
F = (C * 9 / 5) + 32
- Display the conversion in the form
"X Celcius in Fahrenheit is Y"
- Exit.
- Convert the temperature from Celcius to Fahrenheit:
- Else, go back to step 4.
I’m sure you could write this in Java in your sleep by now, but it’s gonna be a lot more code in MIPS. In fact, you could go write it in Java right now, just to get a feel for the “shape” of the program. You don’t have to, but you could. (Aaaaaaaand now 0% of people will do it.)
Here are some example interactions with my implementation. Yours should work the same way.
-
Converting Fahrenheit to Celcius:
enter a temperature: 50 enter C to convert C -> F, or F to convert F -> C: F 50 Fahrenheit in Celcius is 10 -- program is finished running --
-
Converting Celcius to Fahrenheit:
enter a temperature: 100 enter C to convert C -> F, or F to convert F -> C: C 100 Celcius in Fahrenheit is 212 -- program is finished running --
-
Typing something other than C or F:
enter a temperature: 37 enter C to convert C -> F, or F to convert F -> C: c enter C to convert C -> F, or F to convert F -> C: f enter C to convert C -> F, or F to convert F -> C: K enter C to convert C -> F, or F to convert F -> C: C 37 Celcius in Fahrenheit is 98 -- program is finished running --
Here’s what I’m gonna do: I’ll teach you how to do the important bits, and then I’ll leave it up to you to put them all together to make it work correctly.
Reading an integer from the user
You did this on lab 0. Go look at it. Just remember like it says in the program description, you have to move that value somewhere safe.
Reading a string from the user
This one is more tricky. In Java, you would use Scanner.nextLine()
which returns a string, but “returning a string” is kind of complicated in a low-level language. So instead, the way this works is, you make space for a string, then you tell the syscall where that space is, and it will put what the user typed in that space.
So to read a string from the user:
- In the
.data
segment, make space for a string by doing something likeinput_buffer: .space 10
.- This sets aside 10 bytes starting at
input_buffer
. - This could hold a string up to 9 characters long + the zero terminator character.
- This sets aside 10 bytes starting at
- Use syscall 8 to read into the string. It takes two arguments:
a0
is the address ofinput_buffer
. (la
again)a1
is the length of the input buffer. (10 in this case)- Remember that you have to put these things in the
a0
anda1
registers before doing theli v0, 8
andsyscall
!
- After the
syscall
line, what the user typed will be ininput_buffer
.- To get the first character of the string, you can use
lb
(l
oadb
yte). - e.g.
lb t0, input_buffer
loads the first character out of memory and intot0
.
- To get the first character of the string, you can use
Try this out first. Just follow the instructions above, and see if you can read the string in, and see it in the Data Segment view (with the ASCII
box checked).
Control flow
In Java, we might implement the decision-making like so:
while(true) {
// ...prompt the user for a string here...
String str = scanner.nextLine();
if(str.charAt(0) == 'F') {
// ...do F -> C conversion...
System.exit(0);
} else if(str.charAt(0) == 'C') {
// ...do C -> F conversion...
System.exit(0);
}
// else, we loop back around
}
Well, we don’t have while
loops and if-else
s in asm. Instead, we have a few much simpler pieces to build control flow out of.
First are labels, which give a name to a memory address. You’ve already used labels a few times: that’s what main:
and hello_msg:
and input_buffer:
are. A name followed by a colon is a label.
Next are jumps, which tell the CPU to jump to a specific label and start running code from there. j _label
(notice the space!!!!!!!!!!!!!!!!) will take you there.
Finally are branches, which is how the CPU makes decisions. We’ll just need to use beq
for this lab. beq a, b, _label
tells the CPU if(a == b) jump to _label; else go to the next line
.
Here are some examples to demonstrate. Copy and paste these into new files to assemble and run. It’s a good habit to try new things in little test programs before trying to incorporate them into your real program.
You didn’t just scroll down to this part to copy and paste this code into your lab, right? That wouldn’t make any sense! Haha. Just a silly idea. Who would do that? Because these aren’t part of your lab.
.data
hello_msg: .asciiz "hello, world!\n"
.text
.global main
main:
_loop:
# print "hello, world!\n"
la a0, hello_msg
li v0, 4
syscall
# jump back up to the "la" line... FOREVER
# this is an INFINITE loop, so you'll have to
# hit the stop button to end this program
j _loop
And a branch:
.data
its_30_msg: .asciiz "it's 30!\n"
its_not_30_msg: .asciiz "it's not 30...\n"
.text
.global main
main:
# ask for a number
li v0, 5
syscall
# see if v0 (the return Value) is EQual to 30, and if it is, jump to _its_30
beq v0, 30, _its_30
# here, v0 is NOT 30, so print that message out
la a0, its_not_30_msg
li v0, 4
syscall
# huh, that's weird. if it's not 30, it prints both messages.
# how could you solve by putting a jump
# <-- here? where would you jump to?
_its_30:
la a0, its_30_msg
li v0, 4
syscall
Doing longer sequences of computations
In lab 0, you just had to add
two numbers together and print their sum. Now you need to do more complex calculations with multiple steps, like C = (F - 32) * 5 / 9
. But the CPU can only do one arithmetic operation at a time.
For doing computations like this, you have to remember the order of operations (PEMDAS/BODMAS), and you also need to put the intermediate values into registers. We’ll talk more about this in class, but here’s the idea for the above computation:
# we asked the user for the temperature and put it SSSSSomewhere SSSSssafe, like s0
# so F is represented by s0 here.
# parentheses, then multiplication and division from left to right.
sub a0, s0, 32 # a0 = s0 - 32 (we want the result to end up in a0 ultimately)
mul a0, a0, 5 # a0 = a0 * 5
div a0, a0, 9 # a0 = a0 / 9 (now the final value is in a0, ready to be printed)
Printing out the converted temperature
In Java, printing out "X Fahrenheit in Celcius is Y"
might be done like:
System.out.println(original + " Fahrenheit in Celcius is " + converted);
or even:
System.out.printf("%d Fahrenheit in Celcius is %d\n", original, converted);
Neither way exists in asm. So you just have to print it out in 3 pieces:
- print the first number (the original temperature)
- print the
" Fahrenheit in Celcius is "
string in the middle - print out the converted temperature
And you know how to do all of those things!
Okay, go for it!
Some tips:
- Don’t try to write the whole program at once.
- Write a little bit of code at a time, assemble it, run it, and make sure it works correctly.
- If it doesn’t work correctly, fix it.
- If you can’t fix it, get help. never spend more than 20 minutes on a problem that you don’t know how to fix. Don’t look on youtube, Don’t look on stackoverflow, don’t ask ChatGPT. Ask me or a TA. You are not dumb. You are learning.
- If your program gets stuck in an infinite loop, hit the stop button.
- You can write characters in single quotes in the
beq
instructions. Like:beq t0, 'F', _whatever
- ift0 == 'F'
, jump to_whatever
- Never be afraid to single-step through your program to make sure it does what you expect at each step.
A strategy for writing this program:
- Do steps 1-5 first (prompting and getting the temperature and string).
- Make sure it works!!!
- Then do just the control flow for checking the first character of the string.
- Like, make sure that
F
does one thing,C
does another, and anything else prompts them again. - This is definitely going to be the trickiest part of the assignment!
- You can put in some test prints in each of the cases to print like
"in the F -> C case"
or something, to make sure the code is doing the right thing.
- Like, make sure that
- Then add the code for printing and converting temperatures.
- That should be pretty straightforward after doing the above - just replace the test prints if you have any.
- Finally, make sure your program behaves the same way that mine does.
- This will make it much easier for us to grade.
Submitting
To submit:
- On Canvas, go to “Assignments” and click Lab 1.
- Click “Start Assignment.”
- Under “File Upload,” click the “Browse” button and choose your
.asm
file. - Click “Submit Assignment.”
If you need to resubmit, that’s fine, just click “New Attempt” on the assignment page and upload it again. Yes, the filename will have a -1
or -2
or whatever appended to it. No, that’s not a problem.