The MIPS Processor

History

MIPS is one of a number of processors developed in the early 1980s, partly in response to existing instruction sets like IBM 360, VAX, and Intel 8086. The other most prominent architecture of the time is probably RISC, developed by Patterson at UC Berkeley. MIPS was developed by Hennessy at Stanford. Both of these instruction sets were developed with a goal of being able to implement them using the technology of the time with a CPI of one instruction per cycle. Both came pretty close.

MIPS, oddly enough, is not an acronym for ``Millions of Instructions Per Second.'' When originally developed, it was actually an acronym for ``Microprocessor without Interlocking Pipeline Stages'' (surely one of the more tortured acronyms that's been developed!). We'll see later that this means that it was the programmer's (actually the compiler's) responsibility to avoid some weird behavior in the pipeline. Interlocks were later added, so now MIPS isn't an acronym, it's just a name (like RCA is no longer an acronym for Radio Corporation of America).

Register Set

If you're accustomed to a machine like the Intel 32-bit architecture (IA-32) or the HC11, the first thing that strikes you about the MIPS is the number of registers it has: 32 general-purpose integer registers. Unlike IA-32 or HC11, there are very few special-purpose registers -- there is no specific stack pointer, nothing reserved for pointers, and so forth.

Special Purpose Registers

There are a few registers that do have a special purpose: PC, register $0, register $31, and the hi and lo registers.

PC
The Program Counter always points to the next instruction to be executed. As with most architectures, it isn't one of the 32 numbered registers.
$0
Register $0 always contains 0. If you try to write something else to R0 the write silently fails. As screwy as this sounds, it actually turns out to be very handy: we don't need instructions like mov $1, $2 (copy register $2 into $1) because we can do a add $1, $2, $0 instead. In effect, a decision was made to add the functionality of several two-operand instructions by reducing the size of the register set slightly instead of by increasing the size of the instruction set.
$31
Instead of pushing procedure return addresses on the stack, MIPS puts it in $31. If you're going to make another call, it's up to you to push the old return address first.
hi and lo
A general problem with integer multiplication is that the result is twice as wide as the operands. The basic solutions to this problem are either to assume the result will fit (and use the oVerflow bit if it doesn't), or to provide a double-wide place to put the result. MIPS takes the latter approach; the result of a multiply is placed in two special registers called hi and lo; special instructions exist to get the two halves of the result and move them to ordinary registers. We won't be concerned about these registers in the course of this class.
$f0 through $f31
MIPS uses a separate set of registers for floating point operands. This seems awkward, but has the virtue that it doubles the number of registers without requiring an extra bit to address them. While it takes extra instructions, it doesn't come anywhere near to doubling the number of instructions in the instruction set!

Three-operand Load-Store Instruction Set

It has all those registers because the intent (unlike HC11 or IA-32) is that you should do as much arithmetic as possible in the registers. In particular, local variables for a procedure will almost always be in registers. It uses a three-operand instruction set; when you do arithmetic, you can specify two source registers and a destination register, as in

add  $1, $2, $3
to add register $2 to register $3, and put the result into register $1.

On the other hand, all arithmetic has to involve registers or 16-bit integers. You can't add a memory location to a register; you can't add a large constant integer to a register (you can add a 16-bit constant integer to a register, though -- and that includes virtually all constants actually used in a program).

Register Use Conventions

While the 30 registers other than $0 and $31 don't have architected special purposes, there are conventions regarding their use and aliases used by the assembler, as given in the following table:
RegisterAliasCustomary Use
$0$zeroConstant 0 (architected)
$1$atReserved for assembler
$2$v0Function result
$3$v1
$4$a0Function argument
$5$a1
$6$a2
$7$a3
$8$t0Temporary (not preserved)
$9$t1
$10$t2
$11$t3
$12$t4
$13$t5
$14$t6
$15$t7
$16$s0Temporary (preserved)
$17$s1
$18$s2
$19$s3
$20$s4
$21$s5
$22$s6
$23$s7
$24$t8Temporary (not preserved)
$25$t9
$26$k0Reserved for kernel
$27$k1
$28$gpPointer to global variables
$29$spStack pointer
$30$fpFrame pointer
$31$raReturn address (architected)

Loads and Stores

There is only one addressing mode: indexed. If you want to do something fancy like pushing something onto the stack in memory, you have to do it yourself. You can also do immediate mode operations using a 16 bit operand. How do you generate a 32-bit operand? That actually takes two instructions: one (a lui) to load the high-order 16 bits, and one to load the low-order 16 bits.

MIPS is a byte-addressed machine, but loading or storing a word-size data element requires it to be word-aligned. This simplifies memory system design.

Branches

No condition codes! Instead, the branch family works by comparing two registers against each other for equality (or inequality), or comparing a register against 0. This is done rather than a general comparison of two registers against each other as it can be implemented more quickly.

Instruction Formats

Two features about the instruction formats immediately show MIPS to be a RISC, not a CISC:

  1. All instructions are the same size
  2. There are only a small number of instruction formats

In the case of the MIPS, all instructions are 32 bits. The three formats are:

Field Size6 bits5 bits5 bits5 bits5 bits6 bits
R-formatoprsrtrdshamtfunct
I-formatoprsrtaddress/immediate
J-formatoptarget address

Some notes:

An Example

Here's a little bit of code; how would we code it for MIPS?

j = 0;
for (i = 0; i < 0x12345678; ++i)
    j = i + j;
  

Our goal here is more to show off the instruction set than to write good code, so we won't work really hard at creating good code. In fact, we'll produce pretty bad code! We'll assume j is a local variable in memory (see what I mean about not creating good code?) and i is in a register. We'll reconstruct our constant every time through the loop. We'll ignore the pipeline-exposing weirdness of the MIPS (we'll pretend there's no delayed load or delayed branch). We'll use the aliases (like $zero) for register names instead of the real names (like $0).

As usual, our first step will be to translate the for loop into an equivalent while loop, giving us:

j = 0;
i = 0;
while (i < 0x12345678) {
    j = i + j;
    i = i + 1;
}

Now then: we need to set j to zero. If we assume a symbol j containing the offset of the variable from the frame pointer $fp we can do this with

        sw   $zero, j($fp)
  

Next, set i to zero. We'll assume (for no good reason) that i is in register $t3, and we can clear it with

        add  $t3, $zero, $zero

Now we go on to the while loop. We'll need to set a label so we can get back to it, and construct a branch around the loop if we're done. To do this latter, we need to

  1. Construct a constant in a register. This can be done with

    loop:
            lui  $t4, 0x1234
            ori  $t4, $t4, 0x5678
    

  2. Compare i to that constant.

            slt  $t5, $t3, $t4
    

  3. Take the branch if i is greater.

            beq  $t5, $zero, done
    		

Finally, we have the loop body and a branch back to the top. In the loop body, we have to read j, add i to it, store it back, and increment i. We get

        lw   $t4, j($fp)
        add  $t4, $t3, $t4
        sw   $t4, j($fp)
        addi $t3, $t3, 1
        beq  $zero, $zero, loop
done:
  


Last modified: Mon Sep 20 10:07:44 MDT 2004