lw
lw
rd, off(rs1)
rd = mem[rs1 + off]
R
31–25funct7
24–20rs2
19–15rs1
14–12funct3
11–7rd
6–0opcode
I
31–20imm[11:0]
19–15rs1
14–12funct3
11–7rd
6–0opcode
S
31–25imm[11:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:0]
6–0opcode
B
31–25imm[12,10:5]
24–20rs2
19–15rs1
14–12funct3
11–7imm[4:1,11]
6–0opcode
U
31–12imm[31:12]
11–7rd
6–0opcode
J
31–12imm[20,10:1,11,19:12]
11–7rd
6–0opcode

lw brings a value in from memory, so first: what is memory? The 32 registers are the processor's tiny on-hand workspace — fast, but far too few to hold much. Memory is the large store alongside them, a vast row of numbered slots that holds everything else: arrays, text, variables. The number identifying a slot is called its address. Registers are where you compute; memory is where data lives in between.

That split matters because RISC-V can only do arithmetic on registers, never directly on memory. So data must be carried in before use and carried back out after. lw is the carry-in. Its name is short for load word, where a word means a 32-bit value — the size of one register.

The form looks unusual: lw rd, offset(rs1). It reads from the address found by taking the value in rs1 (a register holding a memory address, called the base) and adding offset (a small constant). The result goes into destination register rd. So lw t1, 8(t0) loads the value sitting 8 bytes past the address in t0.

Why add an offset? Because items in memory sit at regular distances. A word is 4 bytes, so consecutive elements of an array are 4 apart — offsets 0, 4, 8, and so on. You typically get the array's starting address into a register with la, then read elements by varying the offset. The example loads two neighbours of an array: offset 0 gives the first value 10, offset 4 gives the next, 20.

.data
arr: .word 10, 20
.text
la t0, arr
lw t1, 0(t0)   # t1 = 10
lw t2, 4(t0)   # t2 = 20