Data-movement instructions in Nios II copy values between registers and memory. They’re the workhorses — every program needs to get data into registers, do something to it, and put results somewhere. The five core ones:

  • mov, movi, movia — register-to-register and immediate moves.
  • ldw, stw — memory loads and stores.

Register-to-register moves

mov dest, src

Copy the contents of one register into another.

mov r3, r2          # r3 ← [r2]

Useful when you need to duplicate a value you’ll modify but want to keep the original around.

In Nios II, mov is actually a pseudo-instruction — the assembler implements it as add dest, src, r0 (since r0 is hardwired to zero). One instruction, one cycle, but encoded under the hood as an add.

movi dest, imm16

Load a 16-bit constant into a register. The constant is sign-extended to 32 bits.

movi r3, 100        # r3 ← 100
movi r4, -1         # r4 ← 0xFFFFFFFF (sign-extended)

Range: to (the signed 16-bit range). Anything bigger needs movia.

movia dest, label_or_32bit_value

Load a full 32-bit value (typically a memory address) into a register.

movia r2, MyData    # r2 ← address of MyData

This is also a pseudo-instruction — the assembler expands it into two real instructions (orhi to load the upper 16 bits, then ori to load the lower 16). Why? Because Nios II instructions are 32 bits and can’t carry a 32-bit immediate field — there’s no room for both the opcode and the constant. So loading a 32-bit value requires two halves combined.

movia is essential because the assembler doesn’t know the final addresses of labels until link time, and labels are 32-bit. You almost always use movia (not movi) when loading a memory address.

Memory access

ldw dest, byte_offset(base)

Read a 32-bit word from memory at address (base + byte_offset) into the destination register.

ldw r3, 0(r2)       # r3 ← memory[r2 + 0]
ldw r4, 8(r2)       # r4 ← memory[r2 + 8]
ldw r5, -4(sp)      # r5 ← memory[sp - 4]

The byte_offset is a signed 16-bit immediate, so it can be negative. The base must be a register holding a memory address (typically loaded earlier with movia).

The address must be word-aligned (a multiple of 4). Misaligned ldw raises an exception. See Word alignment.

stw source, byte_offset(base)

Write the 32-bit value in source to memory at address (base + byte_offset).

stw r3, 0(r2)       # memory[r2 + 0] ← r3
stw ra, 0(sp)       # memory[sp + 0] ← ra (push return address)

Same alignment requirements as ldw.

Pattern: working with a list

The byte-offset form is built for stepping through arrays. To traverse a list of 32-bit words:

movia r2, LIST       # r2 ← address of first element
ldw   r3, 0(r2)      # r3 ← LIST[0]
ldw   r4, 4(r2)      # r4 ← LIST[1]
ldw   r5, 8(r2)      # r5 ← LIST[2]

Or with a loop:

    movia r2, LIST
    movi  r6, 100        # number of elements
loop:
    ldw   r3, 0(r2)      # current element
    # ... process r3 ...
    addi  r2, r2, 4      # advance pointer to next word
    subi  r6, r6, 1      # decrement counter
    bgt   r6, r0, loop   # loop while counter > 0

The addi r2, r2, 4 advances by 4 bytes — one 32-bit word — each iteration. This is the standard idiom for array traversal in Nios II.

For other instruction categories see Nios II arithmetic instructions and Nios II branch instructions.