Instruction Sets
Part of Programming Fundamentals
An instruction set is the complete vocabulary of operations a CPU can perform — the fundamental interface between software and hardware.
Why This Matters
The instruction set is the contract between the hardware designer and the programmer. Every program that runs on a CPU is ultimately a sequence of instructions drawn from that set. Understanding the structure, design, and trade-offs of instruction sets is essential for writing efficient low-level code, implementing compilers, and designing new processors.
For rebuilders, instruction set knowledge has two practical dimensions. First, understanding your available CPU deeply lets you write better programs — choosing instructions that accomplish goals in fewer cycles, recognizing when to combine operations cleverly, and avoiding instruction sequences that are slow or have side effects. Second, if you are involved in designing new computing hardware, understanding instruction set design principles helps you make choices that will support the software that must run on the machine.
The instruction set is not just a technical specification — it encodes fundamental decisions about computation that will constrain software development for the lifetime of that architecture.
Instruction Categories
Every CPU instruction set includes instructions from these functional categories:
Data transfer: Move values between registers and memory. Load (memory to register), store (register to memory), move (register to register), push/pop (register to/from stack). These instructions do no computation; they position data where other instructions can work on it.
Arithmetic: Add, subtract, multiply, divide. Integer arithmetic is universal; floating-point arithmetic is common on modern processors and absent or optional on many early 8-bit systems. Many 8-bit CPUs (6502, Z80) have no multiply or divide instruction — you implement these as subroutines using shift and add.
Logical: AND, OR, XOR, NOT. Operate bit by bit on values. Used for masking individual bits, combining flag bits, testing specific bits, and implementing set operations.
Shift and rotate: Move all bits left or right. Left shift by N is equivalent to multiplying by 2^N; right shift by N divides by 2^N (for unsigned integers). Rotate instructions shift bits out one end and back in the other. Essential for implementing multiply/divide on CPUs without hardware multiply, for serializing data, and for bit manipulation.
Comparison: Compare two values by subtracting them (discarding the result) and setting processor flags based on the outcome. The program then uses conditional jumps based on those flags.
Control flow: Unconditional jump, conditional jump, subroutine call, return from subroutine. The mechanism by which programs make decisions and organize code into reusable units.
System: Halt, no-operation (NOP), software interrupt (trap to operating system or monitor), enable/disable interrupts, set flags, read/write special registers. These handle CPU control and interaction with the operating environment.
RISC vs. CISC
Two major instruction set philosophies emerged from the history of processor design:
CISC (Complex Instruction Set Computer): Add many powerful, specialized instructions. The x86 architecture (Intel 8086 and descendants) is the canonical CISC example. CISC instructions vary in size and execution time; a single instruction might perform a load, an arithmetic operation, and a store. The idea is to make each line of assembly code do more work, reducing program size.
RISC (Reduced Instruction Set Computer): Include only simple, uniform instructions. Every instruction takes one clock cycle (ideally). Every instruction is the same size. Load and store are separate from arithmetic. The idea is that simple, uniform instructions are easier to implement in fast hardware, and the compiler can combine them efficiently.
The Z80 and 6502 are CISC designs, though small ones. ARM processors are RISC. Modern CPUs often implement a RISC internal core that translates CISC instructions to internal RISC micro-operations.
For a rebuilding civilization, the distinction is less important than understanding your specific CPU deeply. Whether your processor has complex or simple instructions, the goal is the same: know what each instruction does precisely and use the right tool for each job.
8-bit Instruction Set Examples
The Z80 has a rich instruction set for an 8-bit processor:
; Data transfer
LD A, B ; copy B to A (register to register)
LD A, (HL) ; load A from memory address in HL
LD (HL), A ; store A to memory address in HL
LD HL, 0x1000 ; load immediate 16-bit value into HL
; Arithmetic
ADD A, B ; A = A + B
ADC A, B ; A = A + B + carry flag
SUB B ; A = A - B
INC HL ; increment 16-bit register pair (for pointer arithmetic)
DEC A ; decrement A
; Logic
AND B ; A = A AND B (bitwise)
OR B ; A = A OR B
XOR B ; A = A XOR B
CPL ; A = NOT A (complement all bits)
; Shifts
RLA ; rotate A left through carry
RLCA ; rotate A left, not through carry
SRL A ; shift A right logically (zero in from left)
SRA A ; shift A right arithmetically (sign bit preserved)
; Control flow
JP 0x1000 ; unconditional absolute jump
JR -10 ; relative jump -10 bytes from next instruction
JP Z, 0x2000 ; jump if zero flag is set
CALL 0x3000 ; call subroutine at 0x3000
RET ; return from subroutine
; Block operations (Z80-specific)
LDIR ; copy block: (DE) ← (HL), BC times
CPIR ; search block for byte in A
The 6502 has a simpler set focused on a single accumulator with two index registers (X and Y):
LDA #$42 ; load immediate $42 into accumulator
LDA $1000 ; load from absolute address $1000
LDA $20,X ; load from zero-page address $20 + X register
STA $1000 ; store accumulator to $1000
TAX ; transfer accumulator to X
TXA ; transfer X to accumulator
INX ; increment X
DEY ; decrement Y
CMP #$10 ; compare accumulator with $10 (sets flags)
BEQ $1050 ; branch if equal (zero flag set)
JSR $2000 ; jump to subroutine
RTS ; return from subroutine
Instruction Encoding
Instructions are encoded as sequences of bytes in memory. The first byte (the opcode) identifies the instruction. Subsequent bytes are operands: the values or addresses the instruction operates on.
A simple instruction like Z80 NOP (no operation) is a single byte: 0x00.
A two-byte instruction like LD A, n (load immediate): opcode 0x3E followed by the value byte n.
A three-byte instruction like JP nn (jump to 16-bit address): opcode 0xC3 followed by the low byte and high byte of the address (little-endian on Z80 — low byte first).
Some instructions have a prefix byte that extends the instruction set. Z80 extended instructions start with 0xCB, 0xDD, 0xED, or 0xFD. This allows a much larger vocabulary using prefix bytes without wasting opcode space for rare instructions.
Instruction encoding directly determines program size. On 8-bit processors where memory is scarce, programmers choose instruction sequences that produce correct results in fewer bytes, even if a more obvious sequence would also work.
Practical Notes for Rebuilders
Print the complete instruction set reference for your CPU and keep it at your workstation. Underline or highlight the 20-30 instructions you use most frequently. Annotate unclear entries with your own examples.
Study the irregular cases: instructions that do not set flags, instructions that have unexpected side effects, addressing modes that are not available for all instructions. These are the source of subtle bugs and the gaps between what you expect and what the hardware does.
When writing time-critical code, count cycles. Every instruction has a documented clock cycle count. Sum the cycles in your inner loop and compare to the available budget. If your loop takes 50 cycles and the CPU runs at 4 MHz, the loop executes 80,000 times per second — is that fast enough for your application?
Instruction sets are not accidental. Every design decision was made for a reason. Understanding those reasons — why the Z80 has 16-bit register pairs, why the 6502 has zero-page addressing — deepens your understanding of the hardware and makes you a better programmer.