COMP1521 1,066 words·6 min read

01. Running MIPS

Why MIPS Needs an Emulator#

Your laptop probably contains an x86-64 or ARM64 processor, not a MIPS processor. Although all machine code is ultimately bits, those bits only acquire meaning through an instruction set architecture. A bit pattern encoding addi for MIPS may mean something entirely different—or nothing useful at all—to an x86-64 CPU.

This is not normally a problem for C because C source is portable at the source level. A compiler can translate the same source into machine code for different targets. MIPS assembly, however, has already committed to the MIPS ISA.

To run MIPS code on a non-MIPS host, we use software that reproduces the relevant behaviour of a MIPS machine. COMP1521 uses mipsy, an education-focused MIPS32 emulator.

An assembler and an emulator solve different problems. The assembler translates readable MIPS into MIPS instruction encodings; the emulator then gives those encodings a machine to run on. Mipsy conveniently performs both jobs, which can make the boundary easy to miss.

Practice: Quick Checks#

  1. Why can an x86-64 processor not directly execute a MIPS instruction encoding?

Note

- Answer
Each ISA assigns its own meanings to bit patterns. An x86-64 processor decodes the bits according to x86-64, not MIPS.

  1. What separate jobs do the assembler and emulator perform?

Note

- Answer
The assembler converts MIPS text into instruction encodings. The emulator reproduces the MIPS machine state and executes those encodings on a non-MIPS host.

Mipsy#

Mipsy accepts MIPS assembly, assembles it, and executes it inside a simulated environment. It also tracks registers and memory, implements the course's syscall interface, and provides debugging features intended to catch beginner mistakes.

It helps to separate what happens conceptually even though one tool performs the whole sequence:

assembly source
    ↓ parse labels, directives, and pseudo-instructions
MIPS instruction encodings + initial data
    ↓ create simulated registers and memory
initial machine state
    ↓ fetch, decode, and execute one instruction at a time
changing machine state

The source file is therefore not being executed as text. Mipsy first gives each instruction and data object a concrete representation, then simulates the effects that a MIPS machine would have on the program counter, registers, and memory. A syscall is handled by the simulator's course environment rather than by sending the MIPS syscall number directly to your laptop's native operating system.

This model explains why the debugger is so useful. Mipsy already owns the complete simulated state, so it can pause between instructions, show a register before and after a write, inspect memory, and even retain enough history to step backwards. On physical hardware, obtaining the same view requires a separate debugger controlling a running process.

The fastest route on a CSE machine is:

BASH
1521 mipsy hello.s

You can also use mipsy web, which provides an editor, register display, data view, input/output area, forward stepping, and backwards stepping in the browser.

Warning

Mipsy is the course environment
Mipsy is not a complete or perfectly faithful implementation of every feature in the MIPS32 specification. It intentionally favours useful diagnostics and course-friendly behaviour. In particular, course pseudo-instructions such as push, pop, begin, and end are mipsy conveniences and should not be assumed to work in an arbitrary assembler or emulator.

Your First Run#

Create answer.s:

MIPS
	.text
main:
	li	$t0, 6
	li	$t1, 7
	mul	$t2, $t0, $t1		# int answer = 6 * 7;

	li	$v0, 0
	jr	$ra			# return 0;

Then run:

BASH
1521 mipsy answer.s

The program prints nothing because computing a value and producing output are separate operations. After execution, $t2 contains 42; to print it, we need the syscall workflow explained in Using MIPS.

Debugging Interactively#

Running a program from beginning to end only tells you its final behaviour. Assembly bugs are often easier to find one instruction at a time.

Start mipsy without a filename:

BASH
1521 mipsy

Then use its interactive commands:

[mipsy] load answer.s
[mipsy] step
[mipsy] step 3
[mipsy] print $t2
[mipsy] run
[mipsy] h

h displays the commands supported by the installed version. The precise interface can evolve, so use the built-in help rather than memorising a large command list.

A Productive Debugging Routine#

When a translation fails, do not immediately rewrite it. Instead:

  1. Write down which C variable belongs to each register.
  2. Identify the first point where observed state differs from expected state.
  3. Step to that instruction.
  4. Inspect the relevant registers and, when necessary, memory.
  5. Check the instruction's operand order and the branch condition.
  6. Fix one cause, rerun the smallest failing case, and only then continue.

For a loop, the most useful checkpoints are normally its __init, __cond, __body, and __step labels. For an array, inspect the base address, index, scaled byte offset, and final element address separately. For a function, inspect $a0–$a3, $v0, $ra, $sp, and any saved registers.

The array and function names in that checklist are previews of later notes. You do not need to understand them all yet. The reusable idea is to compare one small state change at a time. For answer.s, a useful trace is:

Instruction just executed $t0 $t1 $t2 What changed?
li $t0, 6 6 unchanged unchanged one value was placed in $t0
li $t1, 7 6 7 unchanged $t0 was not affected
mul $t2, $t0, $t1 6 7 42 the sources were read and the destination changed

If $t2 is already wrong after the third instruction, later output code is not the first cause. If $t2 is 42 but nothing is printed, the calculation succeeded and the missing step is output. Separating those questions is the beginning of instruction-level debugging.

Practice: Tracing#

  1. After the first three instructions in answer.s, what values are in $t0, $t1, and $t2?

Note

- Answer
$t0 = 6, $t1 = 7, and $t2 = 42.

Practice: Writing / Translation#

  1. A loop prints the wrong value on its fourth iteration. What should you inspect before rewriting the loop?

Note

- Answer
Step to the first mismatching instruction and inspect the loop variable, condition operands, branch result, and any registers used by the body. Fix the first incorrect state transition.

Mipsy Web or the Command Line?#

Use whichever helps you reason most clearly:

Tool Particularly useful for
mipsy web Seeing registers, memory, source, and output together; stepping backwards
command-line mipsy Working with course files, terminal input/output, and the same environment used by lab commands

Neither replaces testing. Try boundary cases, zero, negative values where valid, the first and last array element, empty loops, and multiple function calls. Autotests are useful evidence, but your own explanation of why the state changes is what makes the code debuggable.

Note

- Aside: emulation and binary translation
A simple emulator may repeatedly fetch a guest MIPS instruction—the instruction belonging to the emulated machine—decode it, and reproduce its effect using host code which runs on your actual laptop. Faster systems can translate blocks of guest instructions into host instructions and cache the result, a technique known as dynamic binary translation. This is conceptually interesting, but it is not required to write COMP1521 MIPS: mipsy should be treated as the machine your course program runs on.

Flowcharts comparing dynamic binary translation, which translates and caches source instruction blocks, with interpretation, which repeatedly fetches, decodes, simulates, and updates state

Other MIPS Environments#

A conventional toolchain may assemble and link a real MIPS ELF executable, then run it under an emulator such as QEMU:

That environment has a different syscall interface, assembler syntax, ABI details, and supported pseudo-instructions. An application binary interface, or ABI, is the binary-level agreement covering matters such as register roles, function calls, data layout, executable format and system services. Code written specifically for mipsy is therefore not guaranteed to run unchanged. For course work, use the prescribed environment unless you have a particular reason to investigate the differences.

Practice: Challenge#

  1. Why might a program work in mipsy but fail in QEMU even though both can run MIPS code?

Note

- Answer
They may expose different syscall interfaces, ABI assumptions, assembler syntax, memory models, and pseudo-instructions. “MIPS” alone does not specify the whole execution environment.

Sources and Further Reading#