In this lab, you’ll be making a very simple interactive program: a four-function calculator.

Since you’re very new to control flow in MIPS, I’m giving you a Java implementation of the lab so you can see how your program will work.

Right click and save the Java solution from this link. Read the code, compile it, and run it. Basically, you’ll be translating the main function into MIPS. (If you’re already familiar with MIPS, just go for it, but maybe read the instructions below anyway. You might learn something new!)

How it works

Just like a real calculator, it remembers one value at a time, and everything you do operates on that value. In the Java program, it’s the static variable display.

The program accepts a command, and then optionally an operand. The commands are:

Here’s an example interaction with the Java program (and your program will work the same way):

Welcome to CALCY THE CALCULATOR!
0
Operation (=,+,-,*,/,c,q): =
Value: 55
55
Operation (=,+,-,*,/,c,q): *
Value: 123
6765
Operation (=,+,-,*,/,c,q): /
Value: 4
1691
Operation (=,+,-,*,/,c,q): /
Value: 0
Attempting to divide by 0!
1691
Operation (=,+,-,*,/,c,q): c
0
Operation (=,+,-,*,/,c,q): x
Huh?
0
Operation (=,+,-,*,/,c,q): q


0. Getting started

I’ve written a macro to make it easier to print messages to the output. Copy and paste this code into a new asm file:

# YOUR NAME
# YOUR USERNAME

# THIS MACRO WILL OVERWRITE WHATEVER IS IN THE a0 AND v0 REGISTERS.
.macro print_str %str
	.data
	print_str_message: .asciiz %str
	.text
	la	a0, print_str_message
	li	v0, 4
	syscall
.end_macro

.globl main
main:

1. Named constants and variables

Never write a program all at once and then assemble it. Write a little at a time and let the assembler help you along the way. Then test it! Do this in other languages like C and Java, too!

In the Java file, you’ll see this:

// This named constant will be declared with .eqv
static final int INPUT_SIZE = 3;

Named constants are a very good programming practice. Whenever you have some meaningful number, make a named constant for it. Don’t copy and paste that number everywhere you need it!

  1. At the top of your asm file (before the .macro line), write this:

     .eqv INPUT_SIZE 3
    

    You can now use INPUT_SIZE anywhere you would normally write an integer.

    e.g. li t0, INPUT_SIZE or .space INPUT_SIZE.

  2. **After the .end_macro line, you’re going to declare two variables. You have to switch to the .data segment, then declare the variables, and then switch back to .text.

    Those two static variables are what you will translate to MIPS.

    • display is a Java int, so how will you declare it in MIPS?
    • input is an array of characters. To make space for an array, we can use .space. Write:
     input: .space INPUT_SIZE
    
  3. Now assemble your code. This program doesn’t do anything yet, but it’s good to check your syntax and see if you’re on the right track.

    In the Labels window, you should see:

    • main at 0x00400000
    • display at 0x10010000
    • input at 0x10010004
      • display and input could be swapped if you declare input before display.

2. main and the main loop

A note on label names: In HLLs, you don’t have to name your control structures, but in asm you do. Because of this, it’s a good practice to come up with a convention to name labels so that it’s clear what they are. Here are the rules I use; you can adopt them:

  1. In main, first print out a welcome message using print_str. You use it like this:

     print_str "Hello! Welcome!\n"
    

    Don’t forget the \n at the end of the string if you want a newline.

  2. Now make an infinite loop. Name it _main_loop, like so:

     _main_loop:
    
         j _main_loop
    

    The code inside the loop will go between that label and the j instruction.

  3. Assemble and run. It should print your welcome message once, and then…

    Well, it’s in an infinite loop. It will run forever. Hit the stop button to make it stop. Otherwise your computer fans will start whirring. ;)

    Well, maybe don’t run it. Many people are reporting MARS hanging up and being unable to stop it, so you have to kill it from the task manager/activity monitor.


3. Input and output

From here on, I’m leaving it up to you to assemble and test your program after each step. Get in the habit now while the programs are still simple.

Inside the main loop:

  1. Print the contents of the display variable using syscall #1.
    • Use lw to put its value into a0.
  2. Print the prompt string with print_str: "\nOperation (=,+,-,*,/,c,q): "
  3. Use syscall #8 to read the command into input. For its arguments:
    • use la a0, input to give it the address of the input array.
    • use li a1, INPUT_SIZE to give it the length.

When you run it now, it should prompt you to type something, and you can type a letter and hit enter (in the Run I/O box at the bottom of MARS). Then it should loop and keep asking.


4. Checking what command it is

input is an array of characters, but we’re only interested in the first one.

Each character is one byte, so we’ll use the lb instruction to load a byte.

  1. After you get the user’s input, do lb t0, input to load the first character of that array into t0.
    • A character is just an integer, so it fits into a register. (Remember the ASCII table?)
  2. Now we can do something like a switch-case. We’ll use the beq instruction to see if the character in t0 is equal to anything. Try this:

    In MIPS, you can use single-quoted characters like 'q' in place of integer constants. In languages like Java and C, single-quoted characters are of type char.

         beq t0, 'q', _quit
    
     _quit:
         li v0, 10
         syscall
    

    If you run this, and you type “q”, it quits!

    But if you try other commands, something weird happens. It always quits. That’s wrong.

    This is a very, very common mistake when writing conditionals in asm. beq can go to one of two places: _quit if the condition is true, or the next instruction if the condition is false.

    The computer does not see or care about your labels, and here the next instruction is li v0, 10. So in this code, the beq does nothing because we always end up at _quit!

  3. So let’s fix that. After the beq and before _quit:, put this:

         print_str "Huh?\n"
         j _main_loop
    

    Now it should quit when you enter q and say “Huh?” and loop when you say anything else.

  4. Now let’s implement the clear (c) command. After that first beq put another:

         beq t0, 'c', _clear
    

    Then, add a new _clear: label after the exit syscall. There, you want to set the display variable to 0. You can do this with one instruction:

    • What instruction stores into a word variable?
    • What register holds 0, always?

    You can test this command by temporarily changing the display variable declaration so it holds some nonzero value, then running the program, using c, and it should change to 0 (and print it).

From here on, you can keep adding new commands by adding more beqs after those first two beqs. This is kind of how we do switch-cases in MIPS!


5. The commands with an operand

Now you’ll implement =, +, -, *, and /. Notice in the Java code, this line:

case '+': case '-': case '*': case '/': case '=':

This means “for all of these characters, run the following code.”

  1. Add beqs for those commands in the switch you just made, and have them all go to a new label, like _get_operand. Put _get_operand: inside the main loop.

  2. After that label, translate the Java code for prompting the user and reading an int.

    • After doing syscall #5, the return value (what the user typed) will come out in the return value register. Remember which one?
  3. Do another switch-case on the first character of input. Make labels for each of the five operations but don’t fill them in yet.
    • Since you already know that the character is one of + - * / =, you don’t have to check for an invalid character here.
  4. After the label you made for the = command, store what the user typed in into display, and then jump back to the _main_loop.
    • In the Java code, there’s break;. Here, jumping to the main loop will do the same thing.

Now test it. You should be able to enter the = command, then type a value, and the display should change to that value. Then you should be able to clear it back to 0 with c. For example:

Welcome to CALCY THE CALCULATOR!
0
Operation (=,+,-,*,/,c,q): =
Value: 50
50
Operation (=,+,-,*,/,c,q): c
0
Operation (=,+,-,*,/,c,q):

If it doesn’t work, DO NOT MOVE ONTO THE NEXT SECTION. Don’t “come back and fix it.” This is the easiest way to get completely overwhelmed and discouraged. Try single-stepping through your program, seeing where it goes, and comparing that to where you expect it to go. And if you can’t figure it out, ask for help.


6. Finishing it off

All that’s left are the arithmetic commands, + - * /.

  1. Fill out the code for each of those operations after the labels you made.
    • You will load display, modify that value, and store it back into display.
    • Don’t forget to j _main_loop in each of those four cases, just like you break; in Java.
  2. Try it out.
    • Try dividing by 0. What happens?
  3. To avoid that, let’s add one more condition inside the division case:
    • you can use beq to test if the value the user typed in is 0.
    • if so, print an error message.
    • if not, divide as normal.

And that’s it! It should work just like the Java program now.


Submitting

First, be sure to test your program thoroughly.

Then, make sure your file is named username_lab2.asm, like jfb42_lab2.asm.

Submit here.

Drag your asm file into your browser to upload. If you can see your file in the folder, you uploaded it correctly!

You can also re-upload if you made a mistake and need to fix it.

Try this: If you want to go further, here are some things to try: