RISC-V Memory Instructions

The following are the storing/loading instructions in 32-bit RISC-V. If the arguments of an instruction are not defined, then they are equivalent to the arguments of the first instruction with defined arguments. Instructions in italic are pseudoinstructions:

| Operation | Description |
| :-------: | :----------------------------------------------------------------------------------: |
| lw(u) rd imm(rs1) | Loads a 4-byte “word” of memory into the destination register rd from address rs1 + imm. |
| lb(u) | Loads 1 byte of memory into the destination register rd from address rs1 + imm. |
| lh(u) | Loads a 2-byte “half word” of memory into the destination register rd from address rs1 + imm. |
sw rs2 imm(rs1) | Stores in rs1 + imm a 4-byte “word” address from rs2. |
| sb | Stores in rs2 a 1-byte address starting at rs1 + imm in memory. |
| sh | Stores in rs2 a 2-byte “half word” address starting at rs1 + imm in memory. |
| lui rd imm | Absolute addressing: loads a 20-bit “upper” immediate with the last 12 bits set to 0 (shifted left by 12 bits) into rd (rd = imm << 12). |
| auipc | Relative addressing: adds a 20-bit upper immediate to the current address of the program counter and sets rd to the immediate relative to the program counter (rd = (imm << 12) + PC). |
| li rd imm | Sets the register rd to a 32-bit immediate value. |
| li rd label | Sets the register rd to the 32-bit address of label. |

Note! imm value must be equal to the amount of bytes (and not pointers/elements) you want to shift rs1 by.

Your

Intuition

Let’s talk about the elephant in the room here: lui and auipc. What the heck do these do? Well, they are in fact custom instructions simply made for the li and la pseudoinstructions. Consider the li t0 0x12345678 pseudoinstruction, with a pretty long address. It can be written as:

addi t0, x0, 0x678     // t0 = 0x678
slli t0, t0, 12        // t0 = 0x678 << 12 = 0x678000
addi t0, t0, 0x345     // t0 = 0x678000 + 0x345 = 0x678345
slli t0, t0, 12        // t0 = 0x678345 << 12 = 0x678345000
addi t0, t0, 0x12      // t0 = 0x678345000 + 0x12 = 0x12345678

However, that is pretty inefficient, right? Therefore we need to find a more efficient method to store big immediates. Enter lui.

As stated above, lui rd 0x12345 stores the most significant 20 bits into the 32-bit address of rd. However, 0x12345678 is a 32-bit string! Turns out that addi rd 0x678 can simply do the rest. Then, we get rd = 0x12345000 + 0x678.

The pseudoinstruction la follows a similar approach, but with auipc as its first instruction. Consider la t0 label where the address of label is 0x12345.

  • The auipc instruction takes the current PC value, 0x1000 (for example).
  • It shifts the immediate 0x12345 left by 12 bits to get 0x12345000.
  • It adds the current PC value 0x1000 to this shifted immediate value to get 0x12346000.
  • The result 0x12346000 is stored in register t0.