An addressing mode is a rule the processor uses to determine the location of an operand. Different modes let instructions reach operands in registers, in memory at fixed addresses, in memory at addresses computed from registers, and so on. The same instruction (Load, Add) can have many possible operand sources depending on its addressing mode.

The effective address (EA) is the actual memory address used by an instruction. It’s computed from the addressing mode plus the instruction’s operands.

Common addressing modes

Immediate addressing

The operand value is encoded in the instruction itself.

addi r3, r2, 5         # r3 ← r2 + 5

The constant 5 is part of the instruction’s bits. No memory access needed for the operand. Limited to whatever immediate field width the instruction has — typically 16 bits in Nios II (addi), so the constant range is to .

Direct (absolute) addressing

The instruction encodes the memory address of the operand directly.

Load R1, A             # R1 ← Memory[A]

Simple but uses a lot of bits (the full address is in the instruction). Modern RISC ISAs avoid this — they use base + offset instead, since 16 bits of offset is much cheaper than 32 bits of absolute address.

Register addressing

The operand is in a register, named directly.

add r3, r1, r2         # r3 ← r1 + r2

Fastest, no memory access. Limited by the number of available registers (32 in Nios II). The standard mode for arithmetic instructions in RISC machines.

Register indirect addressing

A register holds the address of the operand.

ldw r1, 0(r2)          # r1 ← Memory[r2]

R2 acts as a pointer. Equivalent to *r2 in C. The actual operand lives in memory at whatever address r2 happens to hold. Used heavily for traversing data structures, walking arrays, and accessing local variables via the stack pointer.

Indexed (base + offset) addressing

A register holds a base address; an immediate offset is added to it.

ldw r1, 8(r2)          # r1 ← Memory[r2 + 8]

Effective address = r2 + 8. Base register typically points to the start of an array or struct; the offset picks an element or field.

This is the workhorse mode in RISC ISAs. With it you can:

  • Walk arrays: ldw r1, 0(r2); ldw r1, 4(r2); ldw r1, 8(r2); ...
  • Access struct fields: ldw r1, 12(r2) for the field at offset 12.
  • Access stack-frame elements: ldw r1, 0(sp), ldw r1, 4(sp), etc.

In Nios II, the offset is a 16-bit signed value, so it can be negative — useful for accessing local variables above the current stack pointer.

Base + index (with two registers)

Some ISAs offer a mode where the effective address is the sum of two registers (and possibly a constant):

LOAD Rk, X(Ri, Rj)     # Rk ← Memory[Ri + Rj + X]

Useful when the index is itself in a register (e.g., a loop counter). x86 supports this; Nios II’s base ISA doesn’t have a direct form (you’d compute the sum into a temp register first).

Memory indirect addressing

The instruction names a memory location that contains the address of the operand. Two memory accesses: one to fetch the address, one to fetch the operand.

Rare in modern RISC machines. Common in some CISC architectures and in older instruction sets.

Why so many modes

Each mode optimizes a different access pattern:

  • Immediate: small constants known at assembly time.
  • Register: frequently used values in registers.
  • Register indirect: pointer dereference.
  • Indexed: array element / struct field access.
  • Base + index: dynamic array indexing where both pointer and index vary.
  • Direct: global variables (rare in modern ISAs because absolute addresses are too wide).

The compiler picks the right mode for each operand based on what the source code says.

Why RISC has fewer modes

RISC machines deliberately limit the addressing modes. Nios II’s main modes:

  • Immediate.
  • Register.
  • Register indirect / indexed (base + 16-bit signed offset).

That’s pretty much it. The complex modes (memory-indirect, auto-increment, base + index + scale) found in CISC architectures don’t exist.

The tradeoff: simpler hardware (fewer cases to handle in the execute stage), more instructions per task in the source code. The RISC bet — more instructions but each one runs faster — has won out.

Practical constraint: immediate field size

RISC instructions are fixed-length 32 bits. After the opcode, register fields, and other bits, only 16 bits typically remain for the immediate — so absolute addresses can’t fit (32 bits don’t squeeze into 16). For absolute access, RISC requires multiple instructions:

movia r2, MyData        # pseudo-instruction, expands to:
                         # orhi r2, r0, %hi(MyData)   ; load upper 16 bits
                         # ori  r2, r2, %lo(MyData)   ; OR in lower 16 bits
ldw r1, 0(r2)           # then access via base + offset

This is the price of fixed-length instructions: large immediates require composition.