Instructions are numbers
Text like add a0, a1, a2 is for humans. The assembler turns it into machine code: one 32-bit number per instruction, with fields for the opcode, the registers and constants. The CPU's decoder (remember fetch–decode–execute?) reads those fields.
- R-type (add): funct7 | rs2 | rs1 | funct3 | rd | opcode — bits 31–25, 24–20, 19–15, 14–12, 11–7, 6–0.
- I-type (addi): imm[11:0] | rs1 | funct3 | rd | opcode.
- add has opcode 0x33, addi 0x13; both have funct3 = 0 (and add has funct7 = 0).
- Registers are numbered: a0 is x10, a1 is x11, t0 is x5, zero is x0.
Shifting fields into place
function encodeAdd(rd, rs1, rs2) {
return ((0 << 25) | (rs2 << 20) | (rs1 << 15) | (0 << 12) | (rd << 7) | 0x33) >>> 0;
}
function encodeAddi(rd, rs1, imm) {
return (((imm & 0xfff) << 20) | (rs1 << 15) | (0 << 12) | (rd << 7) | 0x13) >>> 0;
}
function decode(word) {
const opcode = word & 0x7f;
const rd = (word >>> 7) & 31;
const rs1 = (word >>> 15) & 31;
if (opcode === 0x33 && ((word >>> 12) & 7) === 0 && word >>> 25 === 0) {
return `add x${rd}, x${rs1}, x${(word >>> 20) & 31}`;
}
if (opcode === 0x13 && ((word >>> 12) & 7) === 0) {
return `addi x${rd}, x${rs1}, ${word >> 20}`;
}
return "unknown";
}