Thirty-two boxes
A MIPS CPU has 32 general registers, numbered $0 to $31. Each holds one
32-bit word. Here they are, with the values the smoke program of §1.2 left in
them. Hover a box to see its number, name and value in decimal.
The 32 general registers, HI, LO and the PC
Which box is $8, and what is it called?
- hardwired / reserved
- argument / result
- temporary (caller-saved)
- saved (callee-saved)
- pointer
- read this step
- written this step
What to notice
Every box is the same size and the same shape. $8 and $t0 are one box
with two names. The colors are a software convention, explained in §2.2. To
the hardware, $8 and $16 are exactly alike. Only $0 is different.
Why 32? Because a register field in the instruction word is 5 bits (§1.3), and 2⁵ = 32. More registers would need wider fields and longer words.
Registers are inside; memory is outside
A register is reached in one step, as part of the instruction itself. Memory
is a row of over four billion bytes outside the CPU. Arithmetic never touches
it. Only lw and sw (and their byte and halfword forms) cross the line.
Inside the CPU: 32 boxes. Outside: a very long row of bytes.
Arithmetic works only on the boxes. Only a load or a store crosses the line.
What to notice
The two cyan arrows are the only roads between the boxes and the bytes. To add two numbers that sit in memory, you load both into registers, add, and store the result back. That is the load/store rule from §1.1, drawn.
Register 0 is always zero
Register $0, written $zero, is the
.
Reading it always gives 0. Writing it does nothing. The hardware computes the
result and then drops it. Carolyne’s description says exactly this: register 0
is a constant, so rename bypasses reads of it and discards writes to it.
Why waste a register on a constant? Because zero is the constant programs need most, and having it in a register gives many instructions for free:
| you write | what it really is | why it works |
|---|---|---|
move $t0,$t1 | or $t0,$t1,$zero | anything OR 0 is itself |
li $t0,5 | addiu $t0,$zero,5 | 0 + 5 |
nop | sll $zero,$zero,0 | a shift whose result is dropped |
beqz $t0,label | beq $t0,$zero,label | compare with 0 |
negu $t0,$t1 | subu $t0,$zero,$t1 | 0 − x |
Watch a write to $zero disappear. The runner pauses once to ask you.
A write to $zero vanishes; the same write to $t0 lands
What happens when an instruction tries to write register 0? And where does a multiply put its answer?
program (address · word · instruction)
| PC → | 0xbfc00000 | 0x24000005 | _start:li $zero,5 |
| 0xbfc00004 | 0x24080005 | li $t0,5 | |
| 0xbfc00008 | 0x01080020 | add $zero,$t0,$t0 | |
| 0xbfc0000c | 0x01080018 | mult $t0,$t0 | |
| 0xbfc00010 | 0x00004810 | mfhi $t1 | |
| 0xbfc00014 | 0x00005012 | mflo $t2 | |
| 0xbfc00018 | 0x3c0b1000 | lui $t3,0x1000 | |
| 0xbfc0001c | 0x356b0ff8 | ori $t3,$t3,0xff8 | |
| 0xbfc00020 | 0xad600000 | sw $zero,0($t3) | |
| 0xbfc00024 | 0x1000ffff | park:b 0xbfc00024 | |
| 0xbfc00028 | 0x00000000 | nop |
registers — read · written this tick
console (what the doors printed)
▮tick 0 of 9
Nothing has run yet. The PC (program counter) points at the first instruction.
Tick 1 and tick 3 both target $zero and nothing turns green. Tick 4 writes no general register at all: only the HI and LO chips change. The PC chip moves by 4 every tick.
What to notice
Tick 1 aims 5 at $zero: no box turns green. Tick 2 aims the same 5 at
$t0: $t0 turns green. Tick 4 is mult: no general register changes, but
the HI and LO chips do. Ticks 5 and 6 copy them out.
Before reading on, decide:
`mult $t0,$t0` ran at tick 4 with $t0 = 5. Which registers changed?
HI and LO: two boxes you cannot name
A multiply of two 32-bit numbers can need 64 bits. MIPS keeps that answer in : the top word in HI, the bottom word in LO. A divide uses the same pair: the quotient goes to LO and the remainder to HI.
You cannot write add $t0,$t0,HI. A register field has 5 bits, and HI has no
number. Four instructions are the only doors: mfhi and mflo copy out,
mthi and mtlo copy in. Chapter 7 is about this pair.
The PC is not a register
The PC (program counter) holds the address of the instruction the CPU is on.
No instruction names it. You cannot move it or add to it. After every
instruction it moves forward by 4 on its own. Only branches and jumps change
it, and only after the delay slot (chapter 5). The one way to see its value is
jal, which saves the return address in $ra.
Go deeper: why MIPS chose HI and LO, and how Carolyne models them
The original MIPS pipeline (1985) could write one register per instruction
through one write port. A 64-bit product did not fit that shape, and a
multiplier is slow. So the designers gave the multiplier its own two output
registers and let it run beside the main pipeline. The program picks the
result up later with mfhi or mflo, and by then the multiplier has
finished. RISC-V, thirty years later, chose the other trade: two instructions,
each writing one ordinary register, and no extra state.
Carolyne describes HI and LO as two register classes with one register each
(hi and lo in reg.py), both renamed like the general file. mult
writes both as one µop’s two destinations; mfhi reads one through an
operand that carries no index, because a one-register class has nothing to
choose. The PC is not a class at all. The µop record carries it, and a branch
reads it off the record.
Can you now explain it?
The state of a MIPS program is 32 general registers, HI, LO and the PC. Register 0 is a constant zero. HI and LO are reached only through four copy instructions. The PC moves by itself.
If you remember only three things
- Thirty-two alike boxes, one of them stuck at zero. A 5-bit field names one of them. $zero gives move, li, nop and beqz for free, because a write to it is dropped.
- HI and LO catch what does not fit. A 64-bit product, or a quotient and a remainder. mfhi and mflo bring the halves into normal registers.
- Memory is outside; the PC is elsewhere. Only loads and stores cross to memory. The PC is not in any register field; it advances by 4 and only branches and jumps change it.
Now try to explain this one yourself: nop is sll $zero,$zero,0. Using
this section, say why that instruction is safe anywhere in a program. Then say
why it still costs one tick.
Go deeper: a model answer, once you have tried
It reads $zero, which always gives 0. It shifts 0 by 0, which is 0. It
writes $zero, and the write is dropped. So no register, no memory word and
neither HI nor LO changes. Nothing else about the program is affected, which
is what makes it safe anywhere. But it is a real word in memory: the CPU
fetches it, decodes it and moves the PC past it. That is one tick, and in a
delay slot (chapter 5) it is a tick the compiler could sometimes have used for
real work.
Check your understanding
0/5 answered1.$t0 holds 3 and $t1 holds 4. What does `add $zero,$t0,$t1` change?
2.Where does `mult $t0,$t1` put its 64-bit answer?
3.Can an instruction add HI directly to $t0, like `add $t0,$t0,HI`?
4.Which statement about the PC (program counter) is true?
5.Two numbers sit in memory at 0x10000000 and 0x10000004. What must a MIPS program do to add them?