RISC-V Instruction Set
RISC-V Instruction Set(ISA)
RISC
- Fixed-length instructions
- Load-store architecture: Only load (
lw) and store (sw) instructions access memory- All ALU operations work on registers only.
Data Store
Register Operands
RISC-V has a 32 $\times$ 64-bit register file: x0 to x31
- Use for frequently accessed data
- word: 32-bit data
- double word: 64-bit data
| Register(s) | Purpose | Notes |
|---|---|---|
| x0 | the constant value 0 | 0 |
| x1 | return address | ra |
| x2 | stack pointer | sp |
| x3 | global pointer | |
| x4 | thread pointer | |
| x5-x7 | temporaries | t0-t2 |
| x8 | frame pointer | |
| x9 | saved registers | |
| x10-x11 | function arguments/results | |
| x12-x17 | function arguments | |
| x18-x27 | saved registers | |
| x28-x31 | temporaries |
- x5 – x7, x28 – x31: temporary registers
- Not preserved by the callee
- x8 – x9, x18 – x27: saved registers
- If used, the callee saves and restores them
Memory Operands
- Main memory used for composite data
- Arrays, structures, dynamic data
- To apply arithmetic operations
- Load values from memory into registers
- Store result from register to memory
- Memory is byte addressed
- Each address identifies an 8-bit byte
- RISC-V is Little Endian
- Least-significant byte at least address of a word
- RISC-V does not require words to be aligned in memory
ld x9, 64(x22)
sd x9, 46(x22)
Compare Registers and Memory:
- Registers are faster to access than memory
- Operating on memory data requires loads and stores
- Compiler must use registers for variables as much as possible
Format of Instructions
All RISC-V base instructions are 32 bits long, encoded in one standard formats.
| Format | Purpose | Key Fields (bit layout, [31:0]) | Immediate Reassembly | Opcode (hex) | Opcode (bin) | Mnemonic Examples |
|---|---|---|---|---|---|---|
| R-type | Register-register ALU ops (寄存器-寄存器运算) |
[funct7][rs2][rs1][funct3][rd][opcode] (7)(5)(5)(3)(5)(7) |
(no immediate) | 0x33 |
0110011 |
add, sub, and… |
| I-type | Immediate ALU, loads (立即数运算、加载) |
[imm[11:0]][rs1][funct3][rd][opcode] (12)(5)(3)(5)(7) |
imm = sign_extend(imm[11:0]) |
0x13 |
0010011 |
addi, ori, lw |
| S-type | Store instructions (存储指令) |
[imm[11:5]][rs2][rs1][funct3][imm[4:0]][opcode] (7)(5)(5)(3)(5)(7) |
imm = sign_extend(imm[11:5] | imm[4:0]) |
0x23 |
0100011 |
sw, sh, sb |
| SB-type | Branches (条件分支) |
[imm[12]][imm[10:5]][rs2][rs1][funct3][imm[4:1]][imm[11]][opcode] (1)(6)(5)(5)(3)(4)(1)(7) |
imm = sign_extend(imm[12] | imm[11] | imm[10:5] | imm[4:1] | 0) → lowest bit is always 0(对齐到半字) |
0x63 |
1100011 |
beq, bne, blt… |
| U-type | LUI, AUIPC (加载高 20 位) |
[imm[31:12]][rd][opcode] (20)(5)(7) |
imm = imm[31:12] << 12 |
0x37 / 0x17 |
0110111 / 0010111 |
lui / auipc |
| UJ-type | JAL (jump and link) (无条件跳转并链接) |
[imm[20]][imm[10:1]][imm[11]][imm[19:12]][rd][opcode] (1)(10)(1)(8)(5)(7) |
imm = sign_extend(imm[20] | imm[19:12] | imm[11] | imm[10:1] | 0) → lowest bit is always 0 |
0x6F |
1101111 |
jal |
R-format
![[images/Pasted image 20251126161123.png]]
| Fields | Purpose |
|---|---|
| opcode | operation code |
| rd | destination register number |
| funct3 | 3-bit function code (additional opcode) |
| rs1 | the first source register number |
| rs2 | the second source register number |
| funct7 | 7-bit function code (addtional opcode) |
add x9, x20, x21
![[images/Pasted image 20251126161335.png]]
I-format
![[images/Pasted image 20251126161414.png]]
Immediate arithmetic and load instructions
| Fields | Purpose |
|---|---|
| rs1 | source or base address register number |
| immediate | constant operand, or offset added to base address 2s-complement, sign extended |
S-format
![[images/Pasted image 20251126161558.png]]
Different immediate format for store instructions
| Fields | Purpose |
|---|---|
| rs1 | base address register number |
| rs2 | source operand register number |
| immediate | offset added to base address |
Core Instructions
Arithmetic & Logic
R-type
add x1, x2, x3 # x1 = x2 + x3
sub x1, x2, x3 # x1 = x2 - x3
mul x1, x2, x3 # x1 = x2 * x3
and x1, x2, x3 # bitwise AND
or x1, x2, x3 # bitwise OR
slt x1, x2, x3 # set if less than (signed): x1 = (x2 < x3) ? 1 : 0
sltu x1, x2, x3 # set if less than (unsigned)
- Uses two source registers (
rs1,rs2) and one destination (rd). funct3/funct7distinguish betweenadd/sub,slt/sltu
Immediate Operands
I-type
Constant data specified in an instruction
addi x1, x2, 10 # x1 = x2 + 10
andi x1, x2, 0xF # x1 = x2 & 0xF
ori x1, x2, 1 # x1 = x2 | 1
slli x1, x2, 3 # x1 = x2 << 3
srli x1, x2, 3 # x1 = x2 >> 3
Memory Access
Load Word
I-type
lw x1, 8(x2) # x1 = Mem[x2 + 8] as int32, load word, need sign extension
ld x1, 8(x2) # x1 = Mem[x2 + 8] as int64, load double word
Store Word
S-type
sw x1, 8(x2) # Mem[x2 + 8] = x1, load low 32-bit to memory
sd x1, 8(x2) # Mem[x2 + 8] = x1
Important: RISC-V uses base + offset addressing. No register indirect or scaled indexing in base ISA.
Load 扩展看符号,Store 原样写内存
Control Flow
Conditional Branches
SB-type
beq x1, x2, Label # branch if x1 == x2
bne x1, x2, Label # branch if x1 != x2
blt x1, x2, Label # branch if x1 < x2 (signed)
bge x1, x2, Label # branch if x1 >= x2
bltu x1, x2, Label # unsigned comparison
- Offset is PC-relative, in multiples of 2 bytes (must be even).
- Encoded as a 12-bit signed offset, shifted left by 1 (so actual range: ±4 KiB).
Unconditional Jumps
jal x1, Label # jump and link: x1 = PC+4; PC = Label
jalr x0, 0(x1) # jump register (often used for return, x1 is ra): PC = x1 + 0
Recursive
addi sp, sp, -16 # Allocate 16 bytes on stack
sd ra, 8(sp) # Save return address at sp + 8
sd s0, 0(sp) # Save callee-saved register s0 at sp + 0
ld ra, 8(sp) # Restore return address
ld s0, 0(sp) # Restore s0
addi sp, sp, 16 # Deallocate 16 bytes (move sp back up)
- When your function calls another function (e.g., recursively), the
jalinstruction overwritesra. - So you must save the original
rabefore making the call. - On RV64, pointers/addresses are 8 bytes → needs 8 bytes in memory.
long long int fact(long long int n) {
if (n < 1) return 1;
else return n * fact(n - 1);
}
To assmebly:
fact:
addi sp,sp,-16 # Save return address and n on stack
sd x1,8(sp)
sd x10,0(sp)
addi x5,x10,-1 # x5 = n - 1
bge x5,x0,L1 # if n >= 1, go to L1
addi x10,x0,1 # Else, set return value to 1
addi sp,sp,16 # Pop stack, don’t bother restoring values
jalr x0,0(x1) # Return
L1: addi x10,x10,-1 # n = n - 1
jal x1,fact # call fact(n-1)
addi x6,x10,0 # move result of fact(n - 1) to x6
ld x10,0(sp) # Restore caller’s n
ld x1,8(sp) # Restore caller’s return address
addi sp,sp,16 # Pop stack
mul x10,x10,x6 # return n * fact(n-1)
jalr x0,0(x1) # return
递归时需要手动移动栈顶指针,存储函数返回地址(x1),以及函数的返回值(在这里指定为 x10)
Number Representation
Two’s Complement
- Unsigned: Range $0$ to $2^n−1$
- Two’s Complement Signed(补码有符号整数): Range $−2^{n−1}$ to $2^{n−1}−1$
Sign Extension
- When loading a byte into a 64-bit register:
lb(load byte): sign-extends the byte → preserves signed valuelbu(load byte unsigned): zero-extends → treats as unsigned
# x2 = 0xFF
lb x1, 0(x2) # x1 = 0xFFFFFFFFFFFFFFFF (= -1)
lbu x1, 0(x2) # x1 = 0x00000000000000FF (= 255)
Hexadecimal
- Instructions are stored in memory as 32-bit binary words.
- For readability, we use hexadecimal(十六进制):
- Each hex digit = 4 bits → 8 hex digits per instruction
- Example:
0x00128313might decode toaddi x6, x5, 1
Little/Big-Endian
Little-Endian:
- Least Significant Byte (LSB) is stored at the lowest memory address.
- Most Significant Byte (MSB) is stored at the highest memory address.
- Used by: x86, x86-64, RISC-V (by default), ARM (configurable but often little-endian).
Big-Endian:
- Most Significant Byte (MSB) is stored at the lowest memory address.
- Least Significant Byte (LSB) is stored at the highest memory address.
- Used by: Motorola 68k, SPARC (historically), network protocols (TCP/IP = “network byte order” = big-endian).
Show how the value 0xabcdef12 would be arranged in memory of a little-endian and a big-endian machine. Assume the data are stored starting at address 0 and that the word size is 4 Bytes.
First, split the value to: AB, CD, EF, 12
In little-endian, it would be:
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 12 | EF | CD | AB |
In big-endian, it would be:
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| AB | CD | EF | 12 |
Exam
Translate the following C code to RISC-V. Assume that the variables f , g , h , i , and j are assigned to registers x5, x6, x7, x28, and x29, respectively. Assume that the base address of the arrays A and B are in registers x10 and x11, respectively. Assume that the elements of the arrays A and B are 8-byte words:
B[8] = A[i] + A[j];
we need:
ld: load double word from arrays A and B. Notes:ld rd, offset(rs1),offset 必须是 12 位有符号立即数add: calculate the result.sd: store double word to array B.slli: convert index to offset. offset = index $\times$ 8. Can be replaced bymul
We can use x5 - x7 / x28 - x31 to store temporary variables.
# get offset
slli x30, x28, 3 # x30 = x28 << 3 = x28 * 8
slli x31, x29, 3 # x31 = x29 << 3 = x29 * 8
# get address
add x30, x10, x30 # x30 = x10 + x30 = &A + x30 = &A[i]
add x31, x10, x31 # x31 = &A[j]
# load from arrays
ld x5, 0(x30) # x5 = A[i]
ld x6, 0(x31) # x6 = A[j]
# add
add x7, x5, x6 # x7 = x5 + x6
# store to arrays
sd x7, 64(x11) # B[8] = x7
Translate the following RISC-V code to C. Assume that the variables f , g , h , i , and j are assigned to registers x5, x6, x7, x28, and x29 , respectively. Assume that the base address of the arrays A and B are in registers x10 and x11 , respectively.
addi x30, x10, 8
addi x31, x10, 0
sd x31, 0(x30)
ld x30, 0(x30)
add x5,x30, x31
long long int x30 = &A[1];
long long int x31 = &A[0];
x31 = *(x30);
x30 = *(x30);
f = x30 + x31;
Find the shortest sequence of RISC-V instructions that extracts bits 16 down to 11 from register x5 and uses the value of this field to replace bits 31 down to 26 in register x6 without changing the other bits of registers x5 or x6 . (Be sure to test your code using x5 = 0 and x6 = 0xffffffffffffffff . Doing so may reveal a common oversight.)
srli a0, x5, 11
andi a0, a0, 0x3f
slli a0, a0, 26
li a1, 0x3f
slli a1, a1, 26
not a1, a1
and x6, x6, a1
or x6, x6, a0
Suppose the program counter (PC) is set to 0x20000000 .
a) What range of addresses can be reached using the RISC-V jump-and-link ( jal ) instruction? (In other words, what is the set of possible values for the PC after the jump instruction executes?)
b) What range of addresses can be reached using the RISC-V branch if equal ( beq ) instruction? (In other words, what is the set of possible values for the PC after the branch instruction executes?)
jal 使用 20-bit 有符号立即数,跳转范围为 $[-2^{19},2^{19}-1]$,立即数左移 1 位,得
$$
[-2^{20},2^{20}-2]
$$
beq 使用 12-bit 有符号立即数,跳转范围为 $[-2^{11}, 2^{11}-1]$,立即数左移 1 位,得
$$
[-2^{12},2^{12}-2]
$$