Addressing modes
How an instruction names its operand
An addressing mode is the rule a CPU uses to find an instruction's operand: immediate keeps the value inside the instruction, direct (absolute) keeps the operand's memory address in the instruction, indirect keeps the address of the address, and indexed adds a base and an offset to compute the address.
Every instruction in our machine code is an opcode plus an operand, and for
LOAD, ADD, and STORE that operand is a memory address: LOAD 14 means "read the byte at address 14 into the accumulator." The labels lesson let you name those addresses, but we never named the rule itself, that the operand is an address and the CPU should go read the memory there. That rule is one addressing mode, called *direct*, and it is the only one our small machine has. Real processors offer several. This lesson names ours and, honestly, contrasts it with the modes x86, ARM, and RISC-V add, so you can read those instruction sets and see what extra hardware each mode needs.One idea ties every mode together: the effective address (EA), the address the operand actually resolves to. A mode is just a recipe for computing the EA (or, for one mode, for skipping memory altogether). Throughout, take a concrete memory:
memory[14] = 42 is the data we want, memory[5] = 14 is a pointer cell that *holds* the address 14, and imagine two registers R1 = 14 and R2 = 10 for the modes that use them.Direct (absolute): our machine's mode
In direct (also called absolute) addressing, the operand *is* the effective address: the CPU reads the memory at exactly that number.
LOAD 14 sets EA = 14, so the accumulator gets memory[14] = 42. This is precisely what our instruction register and datapath already do, and it assembles to a single byte: opcode 0x1 (LOAD) packed with operand 0xE (14) gives 0x1E. Direct addressing is simple and needs no extra hardware, which is why a first CPU uses it, but the operand field's width caps how far it can reach (our 4-bit operand can only name addresses 0 to 15).Immediate: the operand is the value
In immediate addressing the operand is not an address at all, it *is* the value, sitting right inside the instruction. Written
LOAD #14 (the # marks a literal), it puts 14 straight into the accumulator with no memory read at all. That makes it the fastest mode and the natural way to load a constant (a loop count, a mask, the number 1). Its cost is the mirror image of direct's: the value is fixed when you assemble, and it can only be as wide as the operand field, so a 4-bit immediate can only be 0 to 15.Indirect: an address of an address
In indirect addressing the operand points to a cell that itself holds the real address, an address *of* an address.
LOAD [5] reads memory[5] first to get the address 14, then reads memory[14] = 42. Two memory accesses instead of one, so EA = memory[5] = 14. A common variant is register-indirect, where a register holds the address rather than a memory cell: LOAD (R1) with R1 = 14 gives EA = R1 = 14, again reaching 42. Indirection is what makes pointers possible: change the value in the pointer cell (or register) and the same instruction now operates on a different location, without rewriting the code.Indexed: base plus offset
In indexed (also base-plus-displacement or register-relative) addressing the effective address is a base plus an offset.
LOAD 4(R2) with base register R2 = 10 computes EA = R2 + 4 = 14, so again the accumulator gets 42. This is the mode that makes arrays and loops cheap: point the base at the start of an array, keep the index in a register, and one instruction reads element after element as you bump the index. Its close relative is PC-relative addressing, where the base is the program counter: EA = PC + offset. That is how branches work and how code stays *position-independent* (relocatable), because a jump target is written as a distance rather than a fixed address.The four modes side by side
| mode | example | effective address | ACC gets |
|---|---|---|---|
| immediate | LOAD #14 | none (value in instruction) | 14 |
| direct / absolute | LOAD 14 | 14 | 42 |
| indirect | LOAD [5] | memory[5] = 14 | 42 |
| register-indirect | LOAD (R1) | R1 = 14 | 42 |
| indexed | LOAD 4(R2) | R2 + 4 = 14 | 42 |
memory[14] = 42, memory[5] = 14, R1 = 14, R2 = 10: every mode but immediate resolves to address 14 and loads 42; immediate skips memory and loads the literal 14. Same number in the instruction, different operand, because the mode decides how it is read. LOAD 14 ; direct: ACC = memory[14] = 42 -> 0x1E (valid here)
LOAD #14 ; immediate: ACC = 14, the literal (no read)
LOAD [5] ; indirect: ACC = memory[memory[5]] = memory[14] = 42
LOAD 4(R2) ; indexed: ACC = memory[R2 + 4] = memory[14] = 42 (R2 = 10)The mode, not just the number, decides the operand, and that is what unlocks real programs. Immediate gives you constants; indirect gives you pointers; indexed and PC-relative give you arrays, loops, and relocatable branches. This is also the classic CISC vs RISC tradeoff: CISC designs like x86 offer many rich modes (fewer, more expressive instructions, but harder to decode), while RISC designs like ARM and RISC-V keep only a few (typically register, immediate, and base-plus-offset for loads and stores) to keep decoding simple and pipelines fast. Our machine sits at the far simple end with exactly one mode: direct.
Common mistakes. Do not confuse immediate (the operand is the value) with direct (the operand is the address of the value):
LOAD #14 loads 14, LOAD 14 loads memory[14]. Do not confuse register-indirect (the register holds the *address*) with plain register-direct (the register holds the *value*). When computing an indexed effective address, do not drop the offset: 4(R2) is R2 + 4, not R2 and not 4. And do not assume our built CPU supports these modes: it does direct only. Immediate, indirect, and indexed would each need a new opcode (and, for the register modes, registers we do not have), which is exactly the pressure that grows a real instruction set.Try it
Let
memory[9] = 3, memory[3] = 8, and R1 = 9. What does each of these put in the accumulator: LOAD #9, LOAD 9, LOAD [9], and LOAD (R1)?Answer
LOAD #9 is immediate: ACC = 9, the literal. LOAD 9 is direct: ACC = memory[9] = 3. LOAD [9] is indirect: read memory[9] = 3 to get the address, then ACC = memory[3] = 8. LOAD (R1) is register-indirect with R1 = 9: ACC = memory[9] = 3. Four modes, four routes: 9, 3, 8, 3. The same operand 9 means a value, an address, or a pointer depending only on the mode.Frequently asked
What is an addressing mode?
An addressing mode is the rule a CPU uses to find an instruction's operand. Immediate keeps the value inside the instruction, direct (absolute) keeps the operand's memory address, indirect keeps the address of the address, and indexed adds a base register and an offset. Each mode is a recipe for the effective address the instruction actually acts on.
What is the difference between immediate and direct addressing?
In immediate addressing the operand IS the value:
LOAD #14 loads the number 14 with no memory access. In direct (absolute) addressing the operand is the value's address: LOAD 14 loads memory[14]. Same number in the instruction, but immediate treats it as data and direct treats it as an address.What is indirect addressing?
Indirect addressing means the operand points to a location that itself holds the real address, an address of an address.
LOAD [5] reads memory[5] to find the address (say 14), then reads memory[14]. It costs a second memory access but enables pointers: change the pointer's contents and the same instruction reaches a new location. Register-indirect is the variant where a register, not a memory cell, holds the address.What addressing modes does a simple accumulator CPU use?
Just one: direct addressing. Our LOAD, ADD, and STORE take a memory address as the operand and read or write the byte there, with no immediate, indirect, or indexed modes. Adding those would require new opcodes and, for the register-based modes, registers the accumulator machine does not have, which is why larger ISAs like x86, ARM, and RISC-V grew richer addressing.
Every lesson here builds toward one thing: a working CPU, from the transistor up.
Open the free lab →