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:

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!

  1. Make a new file in MARS and save it as abc123_lab1.asm where abc123 is your username.
  2. 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.
  3. 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!

  1. 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 as ascii, with a zero 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.
  2. In main (that is, after the main: line):
    • Write la a0, hello_msg
      • la stands for load address. It puts the address of a label into a register.
      • So, this will put the address of hello_msg into a0.
    • Then, do syscall 4.
      • Remember how you do a syscall? Look back at lab 0.
  3. 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 into a0.

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:

  1. 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 and li 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 and ori. It’s fine. Sometimes that happens.
  2. In the Labels panel, you should see one entry: hello_msg 0x10010000
    • That 0x10010000 is the memory address that the assembler gave to the hello_msg string.
    • Memory addresses are virtually always displayed in hexadecimal.
  3. 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.)


2. The temperature converter

Here’s how the temperature converter will work:

  1. Prompt the user to "enter a temperature: "
  2. Read an integer from the user.
  3. Put that integer ssssssomewhere sSSSSSSSSSSSsssSssafe. (Lab 0 again.)
  4. Prompt the user to "enter C to convert C -> F, or F to convert F -> C: "
  5. Read a string from the user.
  6. 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.
  7. 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.
  8. 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.

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:

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-elses 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:

  1. print the first number (the original temperature)
  2. print the " Fahrenheit in Celcius is " string in the middle
  3. print out the converted temperature

And you know how to do all of those things!


Okay, go for it!

Some tips:

A strategy for writing this program:


Submitting

To submit:

  1. On Canvas, go to “Assignments” and click Lab 1.
  2. Click “Start Assignment.”
  3. Under “File Upload,” click the “Browse” button and choose your .asm file.
  4. 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.