Here's the surprising part: a computer that can run anything needs only about 40 instructions. That's the whole RV32I base — and it's the complete "vocabulary" our CPU must understand. Today we'll meet every instruction, grouped by what it actually does, in plain English. This is the last big ISA lesson before we start building hardware.
From Day 2 we have registers; from Day 3 we have formats. Now we fill in the actual operations. RV32I has roughly 40 instructions — that's it. Loops, if-statements, arrays, function calls, entire operating systems: all built from these few simple commands. They fall into a handful of families:
Take two registers, do math, store the result. The everyday workhorses:
| Instr | Meaning |
|---|---|
| add / sub | rd = rs1 + rs2 / rs1 − rs2 |
| and / or / xor | bitwise AND / OR / XOR |
| sll / srl / sra | shift left / shift right (logical) / shift right (arithmetic, keeps sign) |
| slt / sltu | set rd = 1 if rs1 < rs2 (signed / unsigned), else 0 |
Often one operand is a fixed number, not a register. The immediate versions (note the "i" suffix) bake a 12-bit constant right into the instruction:
| Instr | Meaning |
|---|---|
| addi | rd = rs1 + constant (there's no "subi" — just add a negative) |
| andi / ori / xori | bitwise op with a constant |
| slli / srli / srai | shift by a constant amount |
| slti / sltiu | set 1 if rs1 < constant (signed / unsigned) |
Remember (Day 2): RISC-V is load-store, so these are the only instructions that touch memory. The address is always rs1 + offset:
| Instr | Meaning |
|---|---|
| lw | Load a 32-bit word from memory into rd |
| lh / lb | load a 16-bit half / 8-bit byte (sign-extended) |
| lhu / lbu | load half / byte, unsigned (zero-extended) |
| sw / sh / sb | Store a word / half / byte from rs2 to memory |
Example: lw x5, 8(x10) = "load the word at address (x10 + 8) into x5."
This is how a CPU does "if". A branch compares two registers and jumps only if the condition is true (otherwise it just continues):
| Instr | Jump if… |
|---|---|
| beq / bne | rs1 == rs2 / rs1 != rs2 |
| blt / bge | rs1 < rs2 / rs1 ≥ rs2 (signed) |
| bltu / bgeu | same, unsigned |
| Instr | Meaning |
|---|---|
| jal | Jump and link — jump to a label, save the return address in rd (used to call functions) |
| jalr | jump to an address in a register (used to return from functions, and for computed jumps) |
| lui | Load upper immediate — put a 20-bit constant into the top of a register (for building big numbers) |
| auipc | add a 20-bit constant to the PC (for position-independent addressing) |
System instructions round out the set: ecall (ask the OS for a service), ebreak (debugger trap), and fence (ordering) — we'll only need these much later.
You'll see commands in assembly that aren't in the tables above — like li or mv. Those are pseudo-instructions: convenient shorthands the assembler automatically expands into real RV32I instructions. They make code readable without adding hardware:
| You write | Really becomes |
|---|---|
| li rd, 5 | addi rd, x0, 5 (load a small constant) |
| mv rd, rs | addi rd, rs, 0 (copy a register) |
| nop | addi x0, x0, 0 (do nothing) |
| j label | jal x0, label (jump, don't save return) |
| ret | jalr x0, 0(x1) (return: jump to ra) |
A common worry: "only 40 — is that enough?" Yes! It's like an alphabet: ~26 letters spell every English word. RV32I's small, simple set spells every program — and because each instruction is simple, the hardware to run them is simple too, which is exactly why we can build it ourselves.
Here's a small program that uses arithmetic, an immediate, a branch and a jump — building a loop that sums 1..10. Copy or download it; our CPU will run code exactly like this from Day 15:
# Sum 1..10 into a0, then load/store a value. Shows 5 instruction families.
addi t0, x0, 0 # t0 = 0 (arithmetic-immediate)
addi t1, x0, 1 # t1 = 1 (i)
addi t2, x0, 11 # t2 = 11 (limit)
loop: bge t1, t2, done # if i >= 11, exit (branch)
add t0, t0, t1 # sum += i (arithmetic R-type)
addi t1, t1, 1 # i++ (immediate)
jal x0, loop # repeat (jump)
done: mv a0, t0 # a0 = sum (=55) (pseudo -> addi a0,t0,0)
# memory demo:
sw a0, 0(sp) # store sum to memory at sp (store)
lw a1, 0(sp) # load it back into a1 (load)
RV32I is just ~40 instructions in 7 groups: register arithmetic/logic, immediate versions, loads/stores (the only memory ops), branches (the "if"), jumps (calls/returns), upper-immediate (big constants), and a few system ops — plus pseudo-instructions as friendly shortcuts. That small vocabulary can express any program, and it's the complete set our CPU will execute.
rs1 + offset.mv a0, t0 really compile to?About 40 (47 with all variants) — arithmetic, immediates, loads/stores, branches, jumps, upper-immediate and a few system instructions.
Register arithmetic/logic, immediate versions, loads, stores, branches, jumps, upper-immediate, and system.
A shorthand the assembler expands into real instructions — e.g. mv→addi …,0, nop→addi x0,x0,0.
Yes — loops, conditionals, arrays and functions all build from it. Multiply/float are optional extensions (M, F).