digiwleeaLearn
Open the lab →

Addressing modes

How an instruction names its operand

7 min read

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

modeexampleeffective addressACC gets
immediateLOAD #14none (value in instruction)14
direct / absoluteLOAD 141442
indirectLOAD [5]memory[5] = 1442
register-indirectLOAD (R1)R1 = 1442
indexedLOAD 4(R2)R2 + 4 = 1442
With 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)
Only the first line assembles on our CPU (to the byte 0x1E). The others are shown to contrast: immediate needs the operand field to carry a value not an address, indirect needs a second memory read, and indexed needs an index register, none of which our accumulator machine has.
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)?

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 →
Found this useful? Share itShare on X