Three shapes
Every MIPS instruction is one of three formats. The always sits in bits 31 to 26. What follows depends on the format.
| format | fields, from bit 31 down to bit 0 | used for |
|---|---|---|
| R | opcode (6) · rs (5) · rt (5) · rd (5) · sa (5) · funct (6) | register-to-register work: add, sll, mult, jr |
| I | opcode (6) · rs (5) · rt (5) · imm16 (16) | anything with a constant: addiu, lw, sw, beq, lui |
| J | opcode (6) · index (26) | j and jal: jump far |
Their names are , and . Every instruction uses exactly one of them.
Here are three real words from the programs you have seen. Hover any field.
R-format: mult $t2,$t3 from the smoke program (0x014B0018)
Opcode 0 means 'look at funct'. Which field says mult?
What to notice
The opcode is 0. That does not mean “no operation”. It means “this is an
R-type: the answer is in funct”. Here funct = 24, which is mult. The
rd and sa fields are unused, so they are zero.
I-format: addiu $t1,$zero,72 from the smoke program (0x24090048)
Where is the 72, and how wide is it?
What to notice
The opcode alone says addiu. rs is $zero, rt is $t1, and the bottom
16 bits hold 72. There is no rd: an I-format instruction writes rt.
J-format: jal main from hello.c at -O0 (0x0FF00063 at 0xBFC0008C)
How does a 26-bit field name a 32-bit address?
What to notice
Everything under the opcode is one 26-bit number. Multiply it by 4, keep the
top four bits of the PC (program counter), and you get 0xBFC0018C, the address of main.
Chapter 5 explains why that reaches most of memory.
Why the fields are these sizes
- Register fields are 5 bits because 2⁵ = 32. That is why MIPS has exactly 32 registers. One more register would need a sixth bit in three places.
- The opcode is 6 bits, so there are 64 possible opcodes. Three of them
(0, 28, 31) say “look at
funct”, which gives 64 more each. That is how 63 instructions fit with room to spare. - The constant (the immediate) is what remains. After opcode,
rsandrt, 16 bits are left. That is why §1.2 needed two instructions to build a 32-bit address. - A jump index is 26 bits because
jneeds no register. The whole word below the opcode is the target.
is the field to remember. For R-types, the opcode is a table name and funct
is the row.
Decode one by hand
Take 0x014B0018 and write the eight hex digits as bits. Then cut.
| bits | field | binary | value | meaning |
|---|---|---|---|---|
| 31–26 | opcode | 000000 | 0 | SPECIAL: an R-type, read funct |
| 25–21 | rs | 01010 | 10 | $t2 |
| 20–16 | rt | 01011 | 11 | $t3 |
| 15–11 | rd | 00000 | 0 | unused |
| 10–6 | sa | 00000 | 0 | unused |
| 5–0 | funct | 011000 | 24 | mult |
So the word is mult $t2,$t3. You can now read any word in a dump the same
way. In practice you let the tool do it, but once by hand is worth it.
Cut this one yourself first:
`sw $t1,0($t0)` is the word 0xAD090000. Which field holds $t1, the data being stored?
Try it: change a field, watch the bits
Pick a different register or a different constant. The bits that change
light up. Switch the instruction to sll to see the sa field come alive.
Build a word yourself
Which bits move when you change rd? Which move when you change the instruction?
What to notice
Changing a register moves exactly five bits. Changing add to addu moves
one bit, in funct. Changing add to addiu changes the shape: the rd,
sa and funct boxes disappear and a 16-bit constant appears.
nop is all zeros
The nop you saw after every branch is the word 0x00000000. MIPS has no
separate “do nothing” instruction. Opcode 0 and funct 0 is sll, so the
word means sll $zero,$zero,0: shift $zero by zero and write $zero. It is
a real instruction that changes nothing. The assembler prints the alias nop.
The instructions you have met so far
Ten instructions is a good start. Here they are, with their formats and codes. Click a row to see its encoding. The full table of 63 comes in §9.4.
The instructions of chapter 1
Which are R, which are I, and which one is J?
| instruction | format | code | operands | meaning | unit | taught in |
|---|---|---|---|---|---|---|
| ▸ addiu | I | op 9 | rt, rs, imm | rt = rs + sign-extended imm; wraps (what C uses) | alu | §3.2 |
| ▸ ori | I | op 13 | rt, rs, imm | rt = rs OR zero-extended imm | alu | §3.3 |
| ▸ lui | I | op 15 | rt, imm | rt = imm << 16 (the top half; low half zero) | alu | §3.2 |
| ▸ sll | R | op 0 · funct 0 | rd, rt, sa | rd = rt << sa; sll $zero,$zero,0 is nop | alu | §3.3 |
| ▸ lw | I | op 35 | rt, off(rs) | rt = the word at rs+off | mem | §4.2 |
| ▸ sw | I | op 43 | rt, off(rs) | rt → the word at rs+off | mem | §4.2 |
| ▸ beq | I | op 4 | rs, rt, label | if rs == rt go to label (after the delay slot) | control | §5.1 |
| ▸ jal | J | op 3 | target | $ra = PC+8; go to target — the function call | control | §5.3 |
| ▸ mult | R | op 0 · funct 24 | rs, rt | HI:LO = rs × rt, signed 64-bit product | muldiv | §7.1 |
| ▸ mflo | R | op 0 · funct 18 | rd | rd = LO | muldiv | §7.1 |
What to notice
Six of the ten are I-format: they all carry a constant. mult, mflo and
sll are R-format, with opcode 0 and a funct. Only jal is J-format.
Every row’s code column shows the field that names it.
Go deeper: how 63 instructions share 64 opcodes: the four escape tables
The six-bit opcode gives 64 values. MIPS uses four of them as doors to a second table:
- 0 = SPECIAL: the classic R-types, chosen by
funct(add, sub, and, or, sll, jr, mult, mfhi, …). - 1 = REGIMM: branches that compare one register with zero. The
rtfield is the sub-opcode: 0 =bltz, 1 =bgez, 16 =bltzal, 17 =bgezal. - 28 = SPECIAL2:
mul,clz,clo(and themaddfamily, which this book does not use). - 31 = SPECIAL3:
ext,ins,seb,seh.sebandsehsharefunct32 and differ insa(16 versus 24).
The other 59 opcodes name one instruction each: the loads, the stores, the
immediates, j, jal, beq, bne, blez, bgtz, lui. Carolyne’s
mop.py is exactly this list: one matcher per opcode group, and a second
matcher on funct, rt, rs or sa where a group needs it.
Can you now explain it?
A MIPS word is opcode plus fields. R-format names three registers and a funct. I-format names two registers and a 16-bit constant. J-format is one 26-bit target.
If you remember only three things
- The opcode is the top six bits, always. It is a table name. For opcodes 0, 28 and 31 the row is chosen by funct; for opcode 1 by rt; otherwise the opcode is the instruction.
- Five bits name a register; sixteen hold a constant. That is why there are 32 registers and why a 32-bit address takes two instructions.
- Assembly order is not bit order. add rd,rs,rt writes rd, which sits in bits 15–11, after rs and rt. The syntax string of each instruction says which field each operand fills.
Now try to explain this one yourself: the word 0x00000000 is nop,
and 0x00000020 is add $zero,$zero,$zero. Both change nothing. Why did
MIPS choose the shift, not the add, as the official nop?
Go deeper: a model answer, once you have tried
Both words write $zero, which throws the result away, so both are harmless.
The all-zero word is the natural choice for two reasons. First, memory that
has been cleared to zero then reads as a stream of harmless instructions,
which is safer than reading as something else. Second, add can trap on
overflow (§3.1); sll can never trap, so it is safe in every state. funct
0 was given to sll for exactly this reason.
Check your understanding
0/5 answered1.The word 0x3C081000 is `lui $t0,0x1000`. Which format is it?
2.`jal main` is the word 0x0FF00063. Why does the word not simply hold main's full 32-bit address?
3.Why is `nop` the all-zero word 0x00000000?
4.Register fields are 5 bits wide. How many registers can such a field name?
5.Where is the opcode in a MIPS word, and where is it in a RISC-V word?