Machine code
Instructions as numbers
Machine code is a program expressed as raw bytes in memory, where each instruction byte splits into an opcode that names an operation and an operand that names its data. Defining that byte-to-meaning mapping is what defines a CPU's instruction set.
The instruction register latches a byte and splits it into a high nibble and a low nibble. This lesson gives those nibbles meaning. A program is nothing more than a list of bytes sitting in memory; what makes a byte an *instruction* is an agreement, the instruction set, that says which bit patterns mean which operations. That agreement is the contract between the hardware and the programs it runs.
Opcode and operand
Recall the split: in our small machine the high 4 bits are the opcode (the operation, *what to do*) and the low 4 bits are the operand (usually *what to do it to*, here a memory address). Four opcode bits name up to sixteen instructions; four operand bits address sixteen memory locations. So one byte carries both the verb and its object.
instruction byte = opcode (bits 4-7) , operand (bits 0-3)
Reading instructions in hex makes this painless, because each nibble is one hex digit: the first digit is the opcode, the second is the operand. The byte
0x1E is opcode 1, operand 0xE (address 14). The byte 0x25 is opcode 2, operand 5. You can read a program's bytes and immediately see the operation and address in each.A tiny instruction set
Here is a four-instruction set, enough to write real programs for the accumulator machine. Each opcode names what the CPU does; the operand is a memory address (ignored by
HALT). ACC is the accumulator, the CPU's working register.| opcode | name | what it does |
|---|---|---|
| 0x1 | LOAD addr | ACC <- memory[addr] |
| 0x2 | ADD addr | ACC <- ACC + memory[addr] |
| 0x3 | STORE addr | memory[addr] <- ACC |
| 0xF | HALT | stop the clock |
addr is the low nibble (a memory address 0-15). With just these, you can load a value, add another, and store the result, the core of any computation.Designing a CPU is choosing this table: which opcodes exist, what each does, and how the bits of a byte are carved into fields. The control unit is then built to make each opcode's row actually happen. Real instruction sets (x86, ARM, RISC-V) are vastly bigger tables with wider instructions, but the idea is identical: a byte (or a few) names an operation and its operands.
Assembling a line of a program
To turn a human-readable instruction into a byte, look up the opcode nibble and append the operand nibble.
ADD 14 is opcode 0x2, address 14 = 0xE, so the byte is 0x2E, or 0010 1110 in binary. That hand-translation from a name to a number is what an assembler automates: it is pure table lookup plus packing nibbles, the inverse of the IR's split.Try it
Assemble
STORE 13 into a single byte, in hex and in binary. (Opcode for STORE is 0x3; 13 is the operand.)Answer
STORE is opcode
0x3; 13 is 0xD (1101). Pack opcode then operand: 0x3D, which is 0011 1101 in binary. Bits 4-7 (0011) are the opcode, bits 0-3 (1101) the address.And to disassemble, split a byte back into its nibbles, exactly what the instruction register does in hardware. The figure below is that IR: load a byte and read the opcode and operand it exposes. Try the bytes from the program above.
IR8): set the eight bits I0-I7 to an instruction, pulse LD, and read the opcode on OP0-OP3 (high nibble) and the operand on AR0-AR3 (low nibble). Before a load the outputs read Z. Open it in the lab, load 0010 1110 (0x2E, "ADD 14"), and read opcode 0010 and operand 1110.You can now read and write the machine's language. What remains is to watch it run: the control unit steps through fetch-decode-execute, and a worked program trace follows a handful of these bytes through the machine, register by register, until they compute a result.
Frequently asked
What is machine code?
Machine code is a program expressed as raw bytes in memory, where each instruction byte carries an opcode (the operation) and an operand (its data). Defining which bit patterns mean which operations is defining the CPU's instruction set, the contract between the hardware and the programs it runs.
What is the difference between an opcode and an operand?
The opcode says *what to do* (load, add, store, halt); the operand says *what to do it to*, here usually a memory address. In this machine the high 4 bits of a byte are the opcode and the low 4 bits are the operand, so
0x2E is opcode 0x2 (ADD) on operand 0xE (address 14).How do you assemble an instruction into a byte?
Look up the opcode nibble and append the operand nibble.
ADD 14 is opcode 0x2 and address 14 = 0xE, so the byte is 0x2E (0010 1110). That table-lookup-plus-packing is exactly what an assembler automates, and the inverse of what the instruction register does when it splits a byte back apart.What instructions are in this CPU's instruction set?
Four:
LOAD addr (0x1, ACC <- memory[addr]), ADD addr (0x2, ACC <- ACC + memory[addr]), STORE addr (0x3, memory[addr] <- ACC), and HALT (0xF, stop the clock). With just load, add, and store you can compute and save a result, and the control unit is built to make each row actually happen.Every lesson here builds toward one thing: a working CPU, from the transistor up.
Open the free lab →