Three billion times a second
The laptop on a help desk runs about three billion steps a second. Every one of them is the same small routine.
Read the next instruction, work out what it asks for, do it, and move on. Once you can follow that routine for one instruction, you can follow it for any program.
By the end of this lesson
Note
I can follow fetch, decode and execute for one instruction.
Key words
| Word | What it means | Example |
|---|---|---|
| Program counter | the register holding the address of the next instruction | pc |
| Instruction register | where the instruction just fetched is held | |
| Fetch | reading the next instruction from memory | |
| Decode | splitting the instruction into its fields | |
| Execute | doing the work the instruction asks for | |
| Opcode | the field that says which kind of instruction it is |
The cells students fill in are left blank in this preview.
One routine, over and over
The program counter, pc, holds the address of the next instruction. To fetch, the processor reads the word at that address into the instruction register and adds 4 to pc.
To decode, it splits the word into fields: what kind of instruction, which registers. To execute, it does the work and writes the result back. Then it fetches again.
Go to the architecture folder
cd ~/archShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Assemble the lab's add.s
riscv64-linux-gnu-as add.s -o add.oShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Link it
riscv64-linux-gnu-ld add.o -o addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Watch the emulator fetch and decode
qemu-riscv64 -d in_asm ./addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the emulator fetched
qemu fetched five words from memory and decoded each one. Each line shows the address, the word in hex and what it decoded the word as.
The addresses go up by 4, from 0x000100b0 to 0x000100c0, because every instruction here is 4 bytes. Notice li has gone: the processor only ever sees addi a0,zero,2.
Decoding splits the word into fields
An add is 32 bits cut into six fields. The lowest seven bits are the opcode: 51, which means arithmetic on two registers.
Then come five bits for rd, the destination register, three bits for more detail, and five bits each for rs1 and rs2, the inputs. Register a0 is x10 and a1 is x11. So 00b50533 says: x10 = x10 + x11.
Worked example: the fields of 00b50533
| Bits | Field | Value | Meaning |
|---|---|---|---|
| 31 to 25 | funct7 | 0000000 | Plain add, not subtract |
| 24 to 20 | rs2 | 01011 (11) | Second input: x11, a1 |
| 19 to 15 | rs1 | 01010 (10) | First input: x10, a0 |
| 14 to 12 | funct3 | 000 | Add or subtract |
| 11 to 7 | rd | 01010 (10) | Answer goes to x10, a0 |
| 6 to 0 | opcode | 0110011 (51) | Arithmetic on two registers |
The cells students fill in are left blank in this preview.
The instruction as 32 bits
python3 -c "print(format(0x00b50533, '032b'))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Pull out opcode, rd, rs1 and rs2
python3 -c "print(0x00b50533 & 127, (0x00b50533 >> 7) & 31, (0x00b50533 >> 15) & 31, (0x00b50533 >> 20) & 31)"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Execute, then the pc moves on
Execute does the work: an add, a load, a compare. Most instructions leave pc where fetch put it, 4 further on. A branch that is taken writes a new address into pc instead, and that is all a jump is.
With -d exec, qemu prints one line for every instruction it runs. The second number in the square brackets is the RISC-V pc.
One line for each instruction run
qemu-riscv64 -one-insn-per-tb -d exec,nochain ./addShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think the program counter counts how many instructions have run. It does not count anything. It holds an address: where the next instruction is. In a loop it goes back to the same addresses again and again.
Stepping this machine's own processor
gdb can stop a native program and run one instruction at a time. Typed by hand, it waits for you. With -batch and a list of commands it runs them and ends, so a Run can use it.
The Run below stops at main, prints pc, runs one instruction with stepi and prints pc again. On an Arm processor the address moves by 4. On an x86 processor instructions are 1 to 15 bytes long, so the step varies.
Build the lab's loop program
gcc -O2 -o loop loop.cShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Stop at main and step once
gdb -batch -iex 'set debuginfod enabled off' -ex 'break main' -ex run -ex 'info registers pc' -ex stepi -ex 'info registers pc' ./loopShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Going further: count a loop's instructions
Write and build sum.s from the instructions lesson, or ask a neighbour for the printf line. Before you run anything, work out how many instructions it runs: three before the loop, three each time round, ten times, and two after.
Then type qemu-riscv64 -one-insn-per-tb -d exec,nochain ./sum 2>&1 | wc -l and compare. Which addresses appear ten times?