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.
| instruction | what it does, in plain words |
|---|---|
lui $t0,0x1000 | put 0x1000 in the top half of $t0; the bottom half becomes zero |
ori $t0,$t0,0x0ff0 | OR 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,$t3 | multiply; the 64-bit answer lands in the pair HI and LO |
mflo $t4 | copy LO into $t4 |
beq $t2,$t3,label | if $t2 equals $t3, go to label |
nop | do 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 → | 0xbfc00000 | 0x3c081000 | _start:lui $t0,0x1000 |
| 0xbfc00004 | 0x35080ff0 | ori $t0,$t0,0xff0 | |
| 0xbfc00008 | 0x24090048 | li $t1,72 | |
| 0xbfc0000c | 0xad090000 | sw $t1,0($t0) | |
| 0xbfc00010 | 0x24090069 | li $t1,105 | |
| 0xbfc00014 | 0xad090000 | sw $t1,0($t0) | |
| 0xbfc00018 | 0x2409000a | li $t1,10 | |
| 0xbfc0001c | 0xad090000 | sw $t1,0($t0) | |
| 0xbfc00020 | 0x240a0006 | li $t2,6 | |
| 0xbfc00024 | 0x240b0007 | li $t3,7 | |
| 0xbfc00028 | 0x014b0018 | mult $t2,$t3 | |
| 0xbfc0002c | 0x00006012 | mflo $t4 | |
| 0xbfc00030 | 0xad0c0004 | sw $t4,4($t0) | |
| 0xbfc00034 | 0xad0cfff0 | sw $t4,-16($t0) | |
| 0xbfc00038 | 0x8d0efff0 | lw $t6,-16($t0) | |
| 0xbfc0003c | 0xad0e0004 | sw $t6,4($t0) | |
| 0xbfc00040 | 0x10000002 | b 0xbfc0004c | |
| 0xbfc00044 | 0x00000000 | nop | |
| 0xbfc00048 | 0xad090004 | sw $t1,4($t0) | |
| 0xbfc0004c | 0x114b0002 | skip:beq $t2,$t3,0xbfc00058 | |
| 0xbfc00050 | 0x00000000 | nop | |
| 0xbfc00054 | 0xad0c0000 | sw $t4,0($t0) | |
| 0xbfc00058 | 0x240d0000 | no_star:li $t5,0 | |
| 0xbfc0005c | 0xad0d0008 | sw $t5,8($t0) | |
| 0xbfc00060 | 0x1000ffff | park:b 0xbfc00060 | |
| 0xbfc00064 | 0x00000000 | nop |
registers — read · written this tick
memory 0x10000fe0 … 0x10000ffb — touched this tick
console (what the doors printed)
▮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.
| instruction | reads | computes | writes |
|---|---|---|---|
addiu $t1,$zero,72 | $zero | 0 + 72 | $t1 |
sw $t1,0($t0) | $t0, $t1 | the address $t0 + 0 | one memory word |
lw $t6,-16($t0) | $t0 | the address $t0 − 16 | $t6 |
mult $t2,$t3 | $t2, $t3 | 6 × 7 | HI and LO (the one exception: two places) |
beq $t2,$t3,skip | $t2, $t3 | is 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
- 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.
- 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.
- 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 answered1.After `mult $t2,$t3` with $t2 = 6 and $t3 = 7, what does `mflo $t4` put in $t4?
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.$t0 holds 0x10000FF0. Which door does `sw $t2,4($t0)` hit?
4.`beq $zero,$zero,skip` is always taken. Which instruction runs immediately after it?
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.