Skip to content
Contents

Thirty-two registers, HI, LO and the PC

Section 2.1 8 min read

  • general register
  • zero register ($zero)
  • HI and LO
  • the PC as state, not a register
  • registers versus memory

Checked against: MIPS32 Vol. I §2.1 (CPU registers) carolyne_ref/isa_mips/reg.py (the r, hi, lo classes) carolyne_ref/smoke_program.py

Before you start

  • §1.2: an instruction reads registers, computes, and writes one place
  • §1.3: a register field is 5 bits, so it names one of 32

By the end, you can

  • Draw the CPU's state: 32 general registers, HI, LO and the PC
  • Explain what happens when an instruction writes $zero, and why that is useful
  • Say which instructions can reach HI and LO
  • State the one difference between a register and a memory word

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?

HI 0x00000000LO 0x0000002aPC 0xbfc00060
  • 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.

the CPU 32 general registers, 32 bits each — reached in one step $0 zero$1 at$2 v0$3 v1 $4 a0$5 a1$6 a2$7 a3 $8 t0$9 t1$10 t2$11 t3 $12 t4$13 t5$14 t6$15 t7 $16 s0$17 s1$18 s2$19 s3 $20 s4$21 s5$22 s6$23 s7 $24 t8$25 t9$26 k0$27 k1 $28 gp$29 sp$30 fp$31 ra = 0 HI LO PC = 0xBFC00028 only mult / div write these; only mfhi / mflo read them no instruction names it; +4 each tick memory 4 294 967 296 byte addresses, one byte each 0x000000000x10000000 (data)0xBFC00000 (the program) …… reached only by loads and stores, one word or byte at a time lw sw the only two roads

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 writewhat it really iswhy it works
move $t0,$t1or $t0,$t1,$zeroanything OR 0 is itself
li $t0,5addiu $t0,$zero,50 + 5
nopsll $zero,$zero,0a shift whose result is dropped
beqz $t0,labelbeq $t0,$zero,labelcompare with 0
negu $t0,$t1subu $t0,$zero,$t10 − 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 →0xbfc000000x24000005_start:li $zero,5
0xbfc000040x24080005li $t0,5
0xbfc000080x01080020add $zero,$t0,$t0
0xbfc0000c0x01080018mult $t0,$t0
0xbfc000100x00004810mfhi $t1
0xbfc000140x00005012mflo $t2
0xbfc000180x3c0b1000lui $t3,0x1000
0xbfc0001c0x356b0ff8ori $t3,$t3,0xff8
0xbfc000200xad600000sw $zero,0($t3)
0xbfc000240x1000ffffpark:b 0xbfc00024
0xbfc000280x00000000nop

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

console (what the doors printed)

▮
cycle 0/91 tick = 1 instruction (not a cycle)

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

  1. 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.
  2. 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.
  3. 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 answered
  1. 1.$t0 holds 3 and $t1 holds 4. What does `add $zero,$t0,$t1` change?

  2. 2.Where does `mult $t0,$t1` put its 64-bit answer?

  3. 3.Can an instruction add HI directly to $t0, like `add $t0,$t0,HI`?

  4. 4.Which statement about the PC (program counter) is true?

  5. 5.Two numbers sit in memory at 0x10000000 and 0x10000004. What must a MIPS program do to add them?


Next: §2.2 — why the disassembly says sp, ra and a0.

Where this comes back

4 later sections build on this one.