Skip to content
Contents

Your first MIPS program, run by hand

Section 1.2 8 min read

  • register
  • memory-mapped I/O
  • delay slot (first sight)
  • read, compute, write

Checked against: carolyne_ref/smoke_program.py (the 26 words) MIPS32 Vol. II: LUI, ORI, ADDIU, SW, LW, MULT, MFLO, BEQ carolyne_ref/carolyne_io.h (the doors)

Before you start

  • §1.1: a program is a list of 32-bit words, and the PC walks through them

By the end, you can

  • Read a nine-instruction MIPS program and say what each line does
  • Watch a program run and name what each instruction read and wrote
  • State the three things every instruction does
  • Say what happens right after a taken branch on MIPS

The program, in words first

Below is the first program your MIPS core ever executed. It has 26 words. It prints Hi, then 42 twice, then a star, and stops. It uses only nine different instructions.

instructionwhat it does, in plain words
lui $t0,0x1000put 0x1000 in the top half of $t0; the bottom half becomes zero
ori $t0,$t0,0x0ff0OR 0x0ff0 into the bottom half of $t0
addiu $t1,$zero,'H'$t1 = 0 + 72; 72 is the code of the letter H
sw $t1,0($t0)store the word in $t1 at the address $t0 + 0
lw $t6,-16($t0)load the word at the address $t0 − 16 into $t6
mult $t2,$t3multiply; the 64-bit answer lands in the pair HI and LO
mflo $t4copy LO into $t4
beq $t2,$t3,labelif $t2 equals $t3, go to label
nopdo nothing for one tick

The names $t0 … $t6 are . They are registers 8 to 14. The name $t0 and the number $8 mean the same box. $zero is register 0, and it always holds 0. Chapter 2 covers the names.

Here is the program as the assembler sees it. The text after # is a comment.

_start:
  lui   $t0, 0x1000          # $t0 = 0x10000000
  ori   $t0, $t0, 0x0ff0     # $t0 = 0x10000ff0: the "putchar door"
  addiu $t1, $zero, 'H'      # $t1 = 72
  sw    $t1, 0($t0)          # write 72 to the door: prints H
  addiu $t1, $zero, 'i'
  sw    $t1, 0($t0)          # prints i
  addiu $t1, $zero, '\n'
  sw    $t1, 0($t0)          # prints a new line
  addiu $t2, $zero, 6
  addiu $t3, $zero, 7
  mult  $t2, $t3             # HI:LO = 6 * 7 = 42
  mflo  $t4                  # $t4 = 42
  sw    $t4, 4($t0)          # the "putint door": prints 42
  sw    $t4, -16($t0)        # store 42 in ordinary memory
  lw    $t6, -16($t0)        # load it back
  sw    $t6, 4($t0)          # prints 42 again
  beq   $zero, $zero, skip   # always taken
  nop                        # runs anyway (see below)
  sw    $t1, 4($t0)          # skipped
skip:
  beq   $t2, $t3, no_star    # 6 == 7? no: not taken
  nop
  sw    $t4, 0($t0)          # 42 is the code of '*': prints *
no_star:
  addiu $t5, $zero, 0
  sw    $t5, 8($t0)          # the "exit door": stop with code 0

Three small ideas you need before pressing play

The doors. This program prints without any operating system. The test machine watches three addresses. A store to 0x10000FF0 prints one character. A store to 0x10000FF4 prints a number. A store to 0x10000FF8 stops the machine. This is . It is the test machine’s convention, not a MIPS rule. On MIPS, a door is just an address, and sw is the only way to reach it.

A 32-bit address in two steps. One instruction can carry only 16 bits of constant, called an . So 0x10000FF0 needs two: lui sets the top half, ori fills the bottom half. §3.2 explains the sizes.

The nop after a branch. MIPS always runs the instruction after a branch, taken or not. That instruction is the . Here it holds a nop so that nothing surprising happens. §5.4 tells the whole story. For now, just watch it run.

Run it

Press play. One tick is one instruction. Blue registers were read this tick, green ones were written, cyan memory bytes were touched. The runner pauses once, before the branch, to ask you a question.

The first program your MIPS core ever ran

What happens, exactly, when this program runs — one instruction at a time?

program (address · word · instruction)

PC →0xbfc000000x3c081000_start:lui $t0,0x1000
0xbfc000040x35080ff0ori $t0,$t0,0xff0
0xbfc000080x24090048li $t1,72
0xbfc0000c0xad090000sw $t1,0($t0)
0xbfc000100x24090069li $t1,105
0xbfc000140xad090000sw $t1,0($t0)
0xbfc000180x2409000ali $t1,10
0xbfc0001c0xad090000sw $t1,0($t0)
0xbfc000200x240a0006li $t2,6
0xbfc000240x240b0007li $t3,7
0xbfc000280x014b0018mult $t2,$t3
0xbfc0002c0x00006012mflo $t4
0xbfc000300xad0c0004sw $t4,4($t0)
0xbfc000340xad0cfff0sw $t4,-16($t0)
0xbfc000380x8d0efff0lw $t6,-16($t0)
0xbfc0003c0xad0e0004sw $t6,4($t0)
0xbfc000400x10000002b 0xbfc0004c
0xbfc000440x00000000nop
0xbfc000480xad090004sw $t1,4($t0)
0xbfc0004c0x114b0002skip:beq $t2,$t3,0xbfc00058
0xbfc000500x00000000nop
0xbfc000540xad0c0000sw $t4,0($t0)
0xbfc000580x240d0000no_star:li $t5,0
0xbfc0005c0xad0d0008sw $t5,8($t0)
0xbfc000600x1000ffffpark:b 0xbfc00060
0xbfc000640x00000000nop

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

memory 0x10000fe0 … 0x10000ffb — touched this tick

000fe0
001
002
003
000fe4
005
006
007
000fe8
009
00a
00b
000fec
00d
00e
00f
000ff0putchar door
001
002
003
000ff4putint door
005
006
007
000ff8exit door
009
00a
00b

console (what the doors printed)

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

tick 0 of 23

Nothing has run yet. The PC (program counter) points at the first instruction.

Every instruction reads some registers (blue), computes, and writes one register (green) or one memory word (cyan). The PC arrow moves by 4 each tick, except after a branch.

What to notice

Each tick changes exactly one place: a register, a memory word, or the pair HI and LO. The PC arrow moves down by 4 every tick, until the branch. At the branch, the orange line still runs. Only after it does the arrow jump.

Scrub back to tick 4 and decide:

Tick 4 is `sw $t1,0($t0)`. Which register did it change?

Three things every instruction does

Look at any tick in the runner and you will find the same shape. The instruction reads a few registers, computes something small, and writes one place.

instructionreadscomputeswrites
addiu $t1,$zero,72$zero0 + 72$t1
sw $t1,0($t0)$t0, $t1the address $t0 + 0one memory word
lw $t6,-16($t0)$t0the address $t0 − 16$t6
mult $t2,$t3$t2, $t36 × 7HI and LO (the one exception: two places)
beq $t2,$t3,skip$t2, $t3is 6 equal to 7?the PC, after the delay slot

That is why the register pane has only two colors. Blue is what was read. Green is what was written. A store writes no register, so no box turns green.

Go deeper: how this program ran on your real core: 23 instructions, 27 cycles

The runner counts instructions. Your core counts cycles, and it took 27 to reach the exit door. The core can retire two instructions in one cycle, so a perfect run of 23 instructions would take about twelve. It took more because the core guesses “not taken” for every branch. The taken beq at tick 17 was a wrong guess. The core threw away the work it had started past the slot, fetched from skip, and continued. Each sw to a door also waits its turn in the store buffer. Chapter 9 looks at cycle counts for compiled programs.

The doors are at the top 16 bytes of the data memory because the harness watches the store port for exactly those addresses. carolyne_io.h gives C programs the same three doors as putchar, putint and exit.

Can you now explain it?

A program runs as a sequence of ticks. Each tick reads registers, computes, and writes one place. The PC moves by 4, except after a branch — and even then, the next instruction runs first.

If you remember only three things

  1. Read, compute, write one place. Blue boxes were read, the green box or the cyan bytes were written. A store writes memory and no register; a load does the opposite.
  2. Constants are small, addresses are big. An instruction carries 16 bits of constant, so a 32-bit address takes lui and ori. Every address you build in MIPS looks like this pair.
  3. The instruction after a branch runs. Taken or not. Here it is a nop, so nothing visible happens — but it costs a tick and it is not skipped.

Now try to explain this one yourself: at tick 15, lw $t6,-16($t0) brings back the 42 stored at tick 14. Where was the number between those two ticks? And why does $t4 still hold 42 as well?

Go deeper: a model answer, once you have tried

Tick 14 copied the value of $t4 into memory at 0x10000FE0, four bytes below the doors. The register was read, not emptied: a store copies. So between tick 14 and tick 15 the number 42 existed in two places, $t4 and the memory word. Tick 15 read the memory word and wrote a third copy into $t6. Nothing ever moves in MIPS; every load and store is a copy. That is why $t4, the memory word and $t6 all show 0x0000002A at tick 16.

Check your understanding

0/5 answered
  1. 1.After `mult $t2,$t3` with $t2 = 6 and $t3 = 7, what does `mflo $t4` put in $t4?

  2. 2.What does the console show right after tick 13, the first `sw $t4,4($t0)`?

    Scrub the runner to tick 13 if you want to check.

  3. 3.$t0 holds 0x10000FF0. Which door does `sw $t2,4($t0)` hit?

  4. 4.`beq $zero,$zero,skip` is always taken. Which instruction runs immediately after it?

  5. 5.Why does the program use `lui` and then `ori` to put 0x10000FF0 into $t0, instead of one instruction?


Next: §1.3 — the same words as bits: the three instruction formats.

Where this comes back

9 later sections build on this one.