5.1.7 · D2Instruction Set Architecture (ISA)

Visual walkthrough — RISC-V base ISA (RV32I - RV64I)

2,157 words10 min readBack to topic

Step 1 — A number is just a row of boxes

WHAT. Before any instruction, let's agree what a "bit pattern" even means. Draw 32 little boxes in a row. Each box is a bit (binary digit): it holds either 0 or 1. We number them from the right, starting at 0, up to 31 on the left.

WHY number from the right? Because the rightmost box is the "ones" box — the smallest amount you can add. Each box to its left is worth twice the one before it: 1, 2, 4, 8, 16, … The box at position is worth .

PICTURE. Look at the figure: the green box (position 0) is worth ; the lavender box (position 3) is worth . The number the whole row represents is just the sum of the "worth" of every box that holds a 1.

Figure — RISC-V base ISA (RV32I - RV64I)

Step 2 — Cut the 32 boxes into named fields

WHAT. A raw 32-bit row means nothing on its own. The CPU slices it into fields — fixed groups of boxes, each with a job. We'll use the R-type layout (register–register, e.g. add) as our worked example.

WHY these exact cuts? Because the designers promised: rs1, rs2, rd never move. Wherever an instruction has a source or destination register, it sits in the same boxes every time. That lets the decoder grab the register numbers immediately, before it even knows what the instruction does.

PICTURE. The strip is split into 6 coloured chunks. Read them right-to-left because the rightmost field (opcode) is the first thing hardware inspects.

Figure — RISC-V base ISA (RV32I - RV64I)

Step 3 — Decode one concrete R-type: add x5, x6, x7

WHAT. Let's fill the boxes for a real instruction: add x5, x6, x7 (put into ). We plug the numbers into the fields from Step 2.

WHY do it by hand? So you see that "assembly" and "32 boxes" are the same object written two ways. The assembler is just doing this table lookup.

PICTURE. Each field lights up with its value. Follow the arrows from the human-readable line down into its boxes.

Figure — RISC-V base ISA (RV32I - RV64I)

So the full 32-bit word is 0000000 00111 00110 000 00101 0110011. That is add x5, x6, x7. Nothing more mysterious than filling in a form.


Step 4 — Now the interesting kind: an immediate

WHAT. Register ops only combine buckets. But we constantly need constants — "add 4", "subtract 1". These live inside the instruction as an immediate (a number baked into the code). We switch to the I-type format, used by addi, lw, jalr.

WHY a new format? We need room for the constant. I-type sacrifices the rs2 and funct7 boxes (12 boxes total) and hands them to a single 12-box immediate field at the top [31:20].

PICTURE. Compare with Step 2: the top 12 boxes are now one purple imm block. Crucially, rs1, funct3, rd, opcode are still in the same places — the promise holds.

Figure — RISC-V base ISA (RV32I - RV64I)

Step 5 — The heart of it: sign-extension

WHAT. The immediate is only 12 boxes, but a register is 32 boxes (XLEN). To add them we must first stretch the 12-box number to 32 boxes. The rule is sign-extension: copy the top box of the immediate (its sign box, position 11) into all the new boxes above it.

WHY copy the top bit and not just pad with zeros? Because of Two's Complement: in that scheme the top box means "negative". If we padded with zeros, -1 (stored as twelve 1s) would suddenly become a big positive number. Copying the sign box keeps the value the same. This is the single most important idea on the page — watch it in the figure.

PICTURE. Two cases side by side:

  • Top box = 0 (positive): new boxes filled with 0. Value unchanged, e.g. +5 stays +5.
  • Top box = 1 (negative): new boxes filled with 1. 1111 1111 1111 () becomes 1111…1111 (still ).
Figure — RISC-V base ISA (RV32I - RV64I)

Step 6 — Watch addi x5, x6, -1 execute

WHAT. Combine everything: decode the fields, sign-extend the immediate, add, store. This produces with no subtract-immediate instruction in the whole ISA.

WHY does one instruction cover both add and subtract? Because a negative immediate is subtraction. Sign-extension makes -1 behave like -1 across all 32 boxes, so x6 + (−1) is exactly x6 − 1.

PICTURE. A little pipeline: boxes → decode fields → sign-extend → adder → result bucket.

Figure — RISC-V base ISA (RV32I - RV64I)

Step 7 — Edge case: the scattered immediate (B-type branch)

WHAT. For branches (beq, bne) the immediate is split across non-adjacent boxes — it looks scrambled. We now show why, and how the decoder reassembles it.

WHY scatter it? Two reasons, both about speed:

  1. Keep rs1, rs2 fixed (the promise) — the leftover boxes for the immediate are whatever's not used by registers, so they're wherever they land.
  2. Bit 0 is not stored. Instructions are always at even addresses (≥ 2-byte aligned), so the lowest bit of a branch target is always 0. Storing it would waste a box. By dropping it and treating the field as "steps of 2", a 12-box field reaches twice as far.

PICTURE. The scattered boxes (mint, coral, butter) are shown in the instruction, then arrows drag each one into its true position in the reassembled offset — with a fixed 0 slotted into bit 0.

Figure — RISC-V base ISA (RV32I - RV64I)

Step 8 — Edge case: a constant too big for 12 boxes

WHAT. We can only carry 12 immediate boxes, so we can't load 0xDEADBEEF (32 boxes) in one go. We use two instructions: lui fills the upper 20 boxes, addi fills the lower 12. But the signed nature of addi forces a correction.

WHY the "+1" correction? addi's low 12 boxes are signed. Our low part 0xEEF has its top box (bit 11) set → it counts as negative (). When addi sign-extends and adds a negative, it borrows from the upper part. To land exactly on target we pre-compensate: round the upper part up by 1.

PICTURE. Number line: lui lands us at 0xDEADC000; the negative addi (−273) walks us left down to 0xDEADBEEF. Had we used 0xDEADB000, the leftward walk would undershoot.

Figure — RISC-V base ISA (RV32I - RV64I)

The one-picture summary

Everything above, compressed: 32 boxes → slice into fixed fields → the immediate is sign-extended (copy bit 11) → feed the adder / program counter. The scatter and the "+1" trick are just consequences of two rules: registers never move and immediates are signed.

Figure — RISC-V base ISA (RV32I - RV64I)
Recall Feynman retelling — say it to a friend

A RISC-V instruction is a row of 32 light-switches. The CPU always looks at the same switches to find which buckets to use — those switches never move, no matter the instruction, so it can start reading buckets instantly. When an instruction carries a small number inside it (an "immediate"), that number is only 12 switches wide, but buckets are 32 (or 64) wide — so the CPU stretches the number by copying its leftmost switch all the way up. That copy trick is the whole secret: it keeps negative numbers negative, which is why add with a negative number is subtraction, and why there's no separate subtract-immediate. For branches the number's switches are sprinkled around (to keep the bucket switches fixed) and the lowest switch is left out because instructions always sit at even addresses — dropping it doubles how far you can jump. And when a constant is too big for 12 switches, you load the top with lui, then add the bottom with addi — but since the bottom is signed, you bump the top up by 1 to cancel the borrow. Two rules — buckets stay put, numbers are signed — explain every quirk.

Related: Two's Complement · Addressing Modes · Assembler & Pseudo-instructions · RISC vs CISC · Pipelining · back to RISC-V base ISA (RV32I - RV64I).