Assignment Chef icon Assignment Chef
All English tutorials

Programming lesson

Building a Dollar Store Calculator in MIPS Assembly: A Step-by-Step Tutorial

Learn how to write a MIPS assembly program that simulates a simple dollar store calculator using the MARS tool. This tutorial covers keypad input, LCD output, state machine design, and arithmetic operations without multiplication/division instructions.

MIPS assembly calculator MARS tool calculator assembly language tutorial dollar store calculator simulation MIPS state machine calculator.asm MIPS I/O programming assembly arithmetic without multiply MIPS keypad input MIPS LCD display embedded systems assembly low-level programming example MIPS register usage assembly shift and add multiplication MIPS polling loop calculator finite state machine

Introduction: Why Build a Calculator in MIPS Assembly?

In the world of low-level programming, understanding how hardware interacts with software is crucial. The MIPS assembly language offers a unique opportunity to control registers directly, similar to how a gaming console or AI accelerator manages data. In this tutorial, we'll create a dollar store calculator simulation using the MARS tool's Simple Calculator plugin. This project mirrors real-world embedded systems, like the keypad and display in a vending machine or a smart home device. By the end, you'll have a solid grasp of input/output handling, state machines, and arithmetic without high-level instructions.

Understanding the Hardware: Simple Calculator Tool

The MARS tool provides a virtual calculator with a 16-button keypad (0-9, +, -, *, /, =, C) and an LCD display. The display shows the value stored in register $t8, while the keypad writes to $t9. When a button is pressed, bit 31 of $t9 becomes 1, and the lower 4 bits encode the button (see Table 1). Your program must poll $t9 until it's non-zero, then decode the input and reset $t9 to 0. This is similar to how a game console reads controller inputs—polling registers for button presses.

Table 1: Button Encoding

  • 0: 0000, 1: 0001, 2: 0010, 3: 0011, 4: 0100, 5: 0101, 6: 0110, 7: 0111, 8: 1000, 9: 1001, +: 1010, -: 1011, *: 1100, /: 1101, =: 1110, C: 1111

Note: You cannot use $t8 or $t9 for any other purpose. Your program must only use registers $s0-$s7 and $t0-$t7. No syscalls, functions, memory references, or multiplication/division instructions are allowed. Instead, use loops and shift operations.

State Machine Design: The Calculator's Brain

Your calculator will follow a finite state machine with four states (0 to 3) plus a result state (4). The state determines how input is processed. For example, when entering the first operand (State 1), each digit multiplies the current operand by 10 and adds the new digit. An operator key transitions to State 2, where the second operand is entered. Pressing '=' computes the result and goes to State 4. The 'C' key resets everything to State 0. This design is similar to the logic in a cash register or a fitness tracker's step counter.

State 0: Initialization

At start or after 'C', set operand1 = 0, operand2 = 0, operator = 0 (or a sentinel), and display operand1 (0). Then go to State 1.

State 1: First Operand

If a digit (0-9) is pressed, update operand1 = operand1 * 10 + digit and display it. If an operator (+, -, *, /) is pressed, store the operator and go to State 2. If '=', compute result = operand1 (since no second operand) and display it, then go to State 4. If 'C', go to State 0.

State 2: Operator (after first operand)

If a digit is pressed, start building operand2 and go to State 3. If another operator is pressed, update the operator (use the last one). If '=', compute result using operand1 and the operator (with operand2 = 0), display, go to State 4. If 'C', go to State 0.

State 3: Second Operand

If a digit is pressed, update operand2 = operand2 * 10 + digit and display. If an operator is pressed, compute the result using operand1 and operand2 with the current operator, set operand1 = result, reset operand2 = 0, store the new operator, and go to State 2. If '=', compute final result, display, go to State 4. If 'C', go to State 0.

State 4: Display Result

Wait for 'C' to go to State 0 (or allow new calculation). In a dollar store calculator, pressing a digit after result might start a new number. For simplicity, we'll require 'C' to clear.

Step 1: Polling for Keypad Input

Your main loop should continuously check $t9. Use a label like poll_loop:

poll_loop:
    lw $t0, 0($t9)   # Load $t9 value (but we can't use memory! Use move? Actually, we read $t9 directly)
    # Since we can't use lw, we treat $t9 as a register. In MARS, you can read $t9 directly.
    # So: beq $t9, $zero, poll_loop   # Wait until button pressed

But wait—the assignment says no memory references. $t9 is a register, so you can use it directly. The tool updates it. So simply check if $t9 is zero. If not, process the input.

poll:
    beq $t9, $zero, poll   # busy wait
    # Now $t9 has value with MSB=1, lower 4 bits = button
    # Extract lower 4 bits: andi $t0, $t9, 0x0F
    # Reset $t9 to 0: add $t9, $zero, $zero

After processing, set $t9 = 0 to enable next press.

Step 2: Displaying Numbers

To show a number on the LCD, simply move the value to $t8. For example, to display operand1 (stored in $s0):

move $t8, $s0

But move is a pseudo-instruction. Use add $t8, $s0, $zero instead.

Step 3: Building a Multi-Digit Number

When a digit key (0-9) is pressed, you need to update the current operand. For example, if current value is 52 and you press 9, the new value should be 529. Multiply by 10 and add the digit. Multiply by 10 can be done by shifting left 3 (multiply by 8) then adding twice the original (2x) or using a loop. But the hint says: shift left 3 then add original twice. Actually, n * 10 = (n << 3) + (n << 1)? Let's check: n << 3 = n * 8, n << 1 = n * 2, sum = n * 10. So:

sll $t1, $s0, 3   # $t1 = operand1 * 8
sll $t2, $s0, 1   # $t2 = operand1 * 2
add $s0, $t1, $t2 # operand1 = operand1 * 10
add $s0, $s0, $t0 # add the digit (in $t0)

Then display $s0 via $t8.

Step 4: Implementing Arithmetic (Without Multiply/Divide Instructions)

Since you cannot use mult or div, you must use loops. For multiplication, add the multiplicand repeatedly. For division, subtract repeatedly and count. Example: multiply $s0 (operand1) by $s1 (operand2) using a loop:

# Assume $s0 = multiplicand, $s1 = multiplier, $s2 = result = 0
mult_loop:
    beq $s1, $zero, mult_done
    add $s2, $s2, $s0
    addi $s1, $s1, -1
    j mult_loop
mult_done:

For division: dividend / divisor = quotient. Use repeated subtraction:

# $s0 = dividend, $s1 = divisor, $s2 = quotient = 0
# Ensure divisor > 0, handle negatives
# Use slt to check if dividend < divisor

Remember to handle negative numbers. Since the calculator supports negative display (via $t8), you can compute sign separately.

Step 5: Handling the 'C' (Clear) Button

When 'C' is pressed (code 0x0F), reset all state variables to 0, go to State 0, and display 0.

Step 6: Putting It All Together

Your program will have a main loop that polls for input, decodes the button, and calls the appropriate state handler using jumps (j) and labels. Use registers:

  • $s0: operand1
  • $s1: operand2
  • $s2: operator (use a code: 1=+,2=-,3=*,4=/)
  • $s3: state (0-4)
  • $t0-$t7: temporary values

Example skeleton:

.text
main:
    add $s0, $zero, $zero   # operand1 = 0
    add $s1, $zero, $zero   # operand2 = 0
    add $s2, $zero, $zero   # operator = 0
    add $s3, $zero, $zero   # state = 0
    add $t8, $zero, $zero   # display 0
poll:
    beq $t9, $zero, poll
    andi $t0, $t9, 0x0F    # button code
    add $t9, $zero, $zero   # reset
    # state machine dispatch
    beq $s3, 0, state0
    beq $s3, 1, state1
    ...
    j poll

Implement each state as a label. For example, state1: handles digit or operator input. Use slt and beq to compare button codes.

Example: Handling a Digit in State 1

state1:
    # if digit (0-9): button < 10
    slti $t1, $t0, 10
    beq $t1, $zero, not_digit
    # build operand1
    sll $t2, $s0, 3
    sll $t3, $s0, 1
    add $s0, $t2, $t3
    add $s0, $s0, $t0
    add $t8, $s0, $zero   # display
    j poll
not_digit:
    # check operator buttons
    # ...

Testing and Debugging

Run your program in MARS with the Simple Calculator tool. Test sequences like:

  • Press 5, 2, 9 → display 529
  • Press 5, *, -, 9, = → should give -4 (since 5 - 9 = -4)
  • Press C, -, 5, 2, *, 4, 9, = → should give -2548 (since (-52)*49 = -2548? Actually 52*49=2548, negative)

Remember: The calculator uses integer division (5/2=2).

Common Pitfalls

  • Forgetting to reset $t9 after reading input.
  • Using pseudo-instructions like move or li. Stick to add, addi, sll, srl, slt, beq, bne, j.
  • Not handling multiple operator presses correctly (use the last operator).
  • Incorrect multiplication by 10: use shift and add, not a loop.

Conclusion

By building this calculator, you've practiced low-level I/O, state machines, and arithmetic without high-level instructions. These skills are directly applicable to embedded systems, IoT devices, and even retro gaming emulation. The MIPS assembly you've written is a stepping stone to understanding how processors execute code at the hardware level. Happy coding!