Loading header...

Addressing Modes as Hardware - How the CPU Computes Where

An assembly instruction is not only an operation. It is also a compact set of clues that tells the CPU where each operand comes from. The addressing mode is the hardware answer to that question.

In a small microcontroller, addressing modes are implemented with register-file read ports, immediate fields, multiplexers, address adders, incrementers, the program counter, and the memory bus. A mode that looks like a tiny syntax change in assembly can add a real mux input, an adder path, a write-back path, or an extra memory access.

Learning Objectives

By the end of this lesson, you should be able to:

  • explain effective address calculation as real datapath hardware;
  • compare immediate, register, direct, indirect, indexed, and PC-relative modes;
  • identify why some modes cost more cycles than others;
  • connect AVR and ARM examples to mux, adder, and memory-bus behavior;
  • choose efficient addressing forms in simple embedded code.

The Two Questions Behind Every Operand

The decoder first identifies the operation: add, load, store, branch, compare, and so on. Then the addressing-mode hardware decides where the operand is:

  • inside the instruction word itself;
  • in a register;
  • at an absolute memory address;
  • at the memory address stored in a register;
  • at a base address plus an offset;
  • at a target computed relative to the program counter.
flowchart LR IR["Instruction register"] --> DEC["Decoder"] DEC --> CTRL["Mode control"] RF["Register file"] --> MUX["Address/data mux"] PC["Program counter"] --> MUX IMM["Immediate bits"] --> MUX MUX --> EA["Effective-address adder"] EA --> ABUS["Address bus"] ABUS --> MEM["Memory or I/O"] MEM --> DBUS["Data bus"] DBUS --> RF

The important point is that "addressing mode" is not a documentation category. It is a selected path through the datapath.

Immediate Addressing: The Value Is in the Instruction

Immediate mode carries the operand value in the instruction word.

; AVR
LDI R16, 0x42     ; R16 = 0x42

; ARM Thumb
MOVS R0, #4       ; R0 = 4, flags updated

There is no data-memory read. The immediate field is routed to an ALU input or register write path. This is fast and code-dense when the constant fits the instruction's immediate field.

Hardware path: instruction register -> immediate extract/extend -> mux -> ALU or register file.

Common limits: the immediate may be narrow, may be split across bit fields, may be signed or unsigned, and may only target a subset of registers. Classic AVR LDI, for example, loads only R16 through R31.

Register Direct: The Operand Is Already On-Chip

Register direct mode names a register. The instruction contains a register index, not a memory address.

; AVR
ADD R1, R2        ; R1 = R1 + R2

; ARM Thumb
ADDS R0, R1, R2   ; R0 = R1 + R2, flags updated

The register-file decoder selects the requested source registers. No external memory address has to be formed.

Hardware path: instruction register -> register index fields -> register-file read ports -> ALU.

This is why compilers try to keep hot variables in registers. A register operand is available inside the CPU, while a memory operand requires address calculation and a bus access.

Direct or Absolute Addressing: The Instruction Names Memory

Direct addressing embeds the memory address, or part of it, in the instruction stream.

; AVR
LDS R16, 0x0150   ; load R16 from SRAM address 0x0150

On classic AVR, LDS is commonly a 32-bit instruction: one word for the opcode and register field, and another word for the address. That extra program-memory word must be fetched before the data-memory read can happen.

Hardware path: instruction stream -> address field -> address bus -> memory -> data bus -> register file.

Direct addressing is easy to understand and useful for fixed hardware registers, but it can cost more code space and cycles than a register pointer.

Register-Indirect: A Register Holds the Address

In register-indirect mode, a register or register pair contains the memory address.

; AVR
LD R16, X         ; X is R27:R26, load byte at address X

; ARM
LDR R0, [R1]      ; load word from address held in R1

The instruction names the pointer register. The register content drives the address bus.

Hardware path: pointer register -> address mux -> address bus -> memory -> data bus -> destination register.

This is the hardware basis for pointers in C. When C code says value = *p;, the generated instruction usually uses a register-indirect load.

Indexed Addressing: Base Plus Offset

Indexed addressing adds a constant or register offset to a base pointer.

; ARM
LDR R0, [R1, #4]  ; R0 = memory[R1 + 4]

; AVR
LDD R16, Y+3      ; load from address Y + 3, on devices that support it
LD  R17, X+       ; load from X, then increment X

The CPU must either add base plus offset or update the pointer after the access.

flowchart LR BASE["Base register"] --> ADD["Address adder"] OFF["Offset field"] --> ADD ADD --> ABUS["Address bus"] ABUS --> MEM["Data memory"] MEM --> DEST["Destination register"] ADD -. "optional pointer update" .-> WB["Write back"]

Indexed modes are valuable for arrays, structures, stacks, buffers, and table lookups. The cost is extra datapath support: an adder, offset extension logic, and sometimes pointer write-back.

PC-Relative Addressing: The Program Counter Is the Base

Branches usually do not store full target addresses. They store a signed offset from the current program counter.

; AVR
RJMP +10          ; PC = PC + 1 + signed offset

; ARM Thumb
B label           ; PC-relative branch

For an AVR RJMP, the 12-bit offset is a signed word offset added to PC + 1. A 12-bit signed value gives a range of -2048 to +2047 words from the next instruction.

Hardware path: PC -> branch adder; signed offset -> branch adder; selected result -> PC input.

PC-relative addressing supports relocatable code and compact branches. The trade-off is limited reach: if the target is too far away, the assembler must use a longer branch sequence or an absolute jump.

Worked Example: Array Access in C

Consider this C statement on a 32-bit microcontroller:

uint32_t y = samples[i];

If samples is in register R1 and i is in R2, the CPU may form:

effective address = base + index * element_size
effective address = R1 + R2 * 4

Some architectures have scaled-index hardware. Small microcontrollers often do not, so the compiler may emit a shift plus an add:

LSLS R2, R2, #2   ; i * 4
LDR  R0, [R1, R2] ; load samples[i]

The addressing mode therefore influences both speed and code size. If the hardware cannot perform the exact address calculation in one instruction, the compiler builds it from simpler datapath operations.

Cycle Cost and Hardware Cost

Mode Example Main hardware Memory reads before data Typical cost
Immediate LDI R16, 0x42 immediate mux 0 very low
Register direct ADD R1, R2 register-file ports 0 very low
Direct/absolute LDS R16, 0x0150 address field path often 1 extra program word higher
Register indirect LD R16, X pointer register path 0 moderate
Indexed LDR R0, [R1,#4] address adder 0 moderate
Auto-increment LD R16, X+ address path plus write-back 0 moderate
PC-relative RJMP k PC adder 0 low, reach-limited

Do not memorize cycle counts from this table for a specific chip. Always check the device datasheet or architecture manual. The table's purpose is to show the reason: extra fetches, address adders, and write-back paths are not free.

Practical Design Checks

When reading generated assembly, ask:

  • Is this operand a literal, register, pointer, absolute address, or PC-relative offset?
  • Does the instruction need a data-memory access?
  • Does it fetch an extra instruction word for an address or long immediate?
  • Does the pointer get updated as a side effect?
  • Is the branch target within the encoded offset range?
  • Is the load/store width aligned as the architecture requires?

Common Mistakes

  • Treating an immediate as an address. #4 means value 4 on many assemblers; [R1,#4] means address R1 + 4.
  • Forgetting AVR LDI targets only high registers on classic AVR cores.
  • Assuming all loads cost the same. Absolute loads, indirect loads, and indexed loads may have different timings.
  • Ignoring side effects of pre-decrement and post-increment modes.
  • Confusing byte offsets and word offsets in branch instructions.
  • Assuming a C pointer access always becomes one instruction.

Summary

Addressing modes are hardware choices. Immediate and register modes stay mostly inside the CPU. Direct and indirect modes involve the memory interface. Indexed and PC-relative modes use adders to compute effective addresses. The syntax is small, but the hardware consequences affect cycle count, code size, and compiler output.

Further Reading

  • Microchip, AVR Instruction Set Manual, addressing modes and instruction timing.
  • Arm, Armv7-M Architecture Reference Manual, load/store and branch encodings.
  • Patterson and Hennessy, Computer Organization and Design, datapath and control chapters.
  • Bryant and O'Hallaron, Computer Systems: A Programmer's Perspective, machine-level data and control flow.

Mind Map

mindmap root((Addressing Modes)) Core idea Operand location Effective address Mux and adder path Main modes Immediate value in instruction Register direct on chip Absolute address field Indirect pointer register Indexed base plus offset PC relative branch Calculations EA equals base plus offset Branch target equals PC plus signed offset Array byte address equals base plus i times size Design rules Keep hot data in registers Prefer pointer loops for arrays Check branch range Check alignment Practical checks Extra memory word Pointer side effect Load store width Datasheet cycles Common mistakes Immediate vs address Wrong register subset Byte vs word offset Hidden auto increment

-> Interrupts