Instruction sets and addressing modes
Opcodes, operands and instruction formats; immediate, direct, indirect and indexed addressing.
Do this lesson in the simulatorAt GCSE you learned that a processor runs machine code. At A level you need to know what a machine code instruction is made of, why code for one processor will not run on another, and how the operand of an instruction can be used in different ways, called addressing modes. Addressing modes are what let a short instruction reach any value in memory, and they are how arrays and pointers work underneath.
The instruction set
A processor's instruction set is the complete list of operations it can carry out, each with its own binary code. Instruction sets are processor specific: the machine code for an Intel x86 processor means nothing to an ARM processor or to the RISC-V cores in BugBot. That is why a program is compiled separately for each kind of processor, and why an app built for one kind of computer may not run on another.
Opcode and operand
Each machine code instruction is split into fields:
- the opcode says what to do. It is made of the basic machine operation (load, add, branch and so on) and, on many processors, the addressing mode, which says how to use the operand;
- the operand is what to do it to: a value, a memory address, or a register number. Some instructions have more than one operand, and some (like
HALT) have none.
Here is a made-up 8-bit instruction format:
| Bits 7 to 5 | Bit 4 | Bits 3 to 0 |
|---|---|---|
| basic operation | addressing mode | operand |
| 3 bits: 8 possible operations | 0 = immediate, 1 = direct | 4 bits: 0 to 15 |
The instruction 01011101 splits into operation 010, mode 1 (direct) and operand 1101, which is 13. If 010 means SUB, the instruction means "subtract the value stored at address 13".
A processor pulls these fields apart with shifts and masks, exactly as Python can:
instruction = 0b01011101
operation = instruction >> 5 # keep the top 3 bits
mode = (instruction >> 4) & 0b1 # bit 4 only
operand = instruction & 0b1111 # the bottom 4 bits
print("operation", format(operation, "03b"), "mode", mode, "operand", operand)
The format is a trade-off. Giving more bits to the opcode allows more different instructions, but leaves fewer bits for the operand, so a direct address can reach less memory and an immediate value must be smaller. With n bits for the operation there can be at most 2n basic operations. Real processors use longer instructions, often 16, 32 or more bits, and many have several formats.
Addressing modes
The addressing mode decides how the processor gets from the operand to the value it actually uses. Take this memory, an index register IX holding 2, and the instruction "load into the accumulator" with operand 20:
| Address | 20 | 21 | 22 | 23 | 24 | 25 |
|---|---|---|---|---|---|---|
| Contents | 23 | 99 | 21 | 47 | 12 | 60 |
| Mode | The operand is | The value loaded | Here |
|---|---|---|---|
| Immediate | the value itself | 20 | 20 |
| Direct | the address of the value | the contents of address 20 | 23 |
| Indirect | the address of a location holding the address of the value | contents of address 20 is 23; contents of address 23 | 47 |
| Indexed | added to the index register to give the address | contents of address 20 + 2 = 22 | 21 |
Each has a reason to exist:
- Immediate is the fastest: no memory access is needed after the fetch. Use it for constants, such as adding 1 to a counter. The size of the value is limited by the operand field.
- Direct is simple: one memory access. But the address must fit in the operand field, so a 4-bit operand can only reach addresses 0 to 15.
- Indirect needs two memory accesses, so it is slower, but the stored address can use a whole word, so it can reach any address. It is how a pointer works: the location holds where the data is, and the program can change that without changing the instruction.
- Indexed computes the address as operand + [IX]. It is made for arrays: the operand is the address of the first element, and a loop adds 1 to IX each time round to step through the elements, with one instruction.
In AQA's assembly language, # marks immediate addressing: MOV R0, #20 puts the number 20 in R0, while LDR R0, 20 loads the contents of address 20. OCR's Little Man Computer, in the next lesson, uses direct addressing for every operand.
memory = {20: 23, 21: 99, 22: 21, 23: 47, 24: 12, 25: 60}
ix = 2
def load(mode, operand):
if mode == "immediate":
return operand
if mode == "direct":
return memory[operand]
if mode == "indirect":
return memory[memory[operand]]
if mode == "indexed":
return memory[operand + ix]
for mode in ["immediate", "direct", "indirect", "indexed"]:
print(mode, load(mode, 20))
Indexed addressing walks an array
Here the array of four readings starts at address 20. One "load indexed 20" instruction, repeated with IX going 0, 1, 2, 3, visits every element:
memory = {20: 31, 21: 18, 22: 44, 23: 27}
total = 0
ix = 0
while ix < 4:
value = memory[20 + ix] # load indexed, operand 20
total = total + value
ix = ix + 1 # increment the index register
print("total", total)
Task: four ways to read an operand
Memory is the list memory (addresses 0 to 9) and the index register ix holds 2. Write operand_value(mode, operand):
modeis one of the strings"immediate","direct","indirect"or"indexed";operandis a whole number from 0 to 9;- it returns the value an instruction would use in that mode, as defined in the table above.
For each operand in [2, 5], and for each mode in the order immediate, direct, indirect, indexed, print a line in the form direct 2 -> 1: the mode, the operand, ->, then the value. That is eight lines. The robot does not move.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
memory = [4, 7, 1, 9, 5, 3, 8, 0, 6, 2]
ix = 2
def operand_value(mode, operand):
return operand
Challenges
- An instruction format has 16 bits: 6 for the operation and 10 for the operand. How many operations can it have, and what is the largest address direct addressing can reach?
- Using the task's memory, find an operand for which indirect and immediate give the same value.
- Explain why indirect addressing is slower than direct addressing, in terms of the fetch-decode-execute cycle.