This cheat sheet provides a handy guide to 32-bit RISC-V instructions. I’ve aimed it at software developers, so group instructions by purpose and include common pseudoinstructions. Clicking on a Guide link takes you to the relevant section of the Project F RISC-V assembler guide for instruction explanation and examples.
Instructions are from the base integer instruction set (RV32I) unless otherwise noted.
Share your thoughts with @WillFlux on Mastodon or X. If you like what I do, sponsor me. 🙏
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| add | Add | add rd, rs1, rs2 | rd = rs1 + rs2 | arithmetic | 
| addi | Add Immediate | addi rd, rs1, imm | rd = rs1 + imm | arithmetic | 
| neg | Negate (p) | neg rd, rs2 | rd = -rs2 | arithmetic | 
| sub | Subtract | sub rd, rs1, rs2 | rd = rs1 - rs2 | arithmetic | 
| mul | Multiply | mul rd, rs1, rs2 | rd = (rs1 * rs2)[31:0] | multiply | 
| mulh | Multiply High | mulh rd, rs1, rs2 | rd = (rs1 * rs2)[63:32] | multiply | 
| mulhu | Multiply High Unsigned | mulhu rd, rs1, rs2 | rd = (rs1 * rs2)[63:32] | multiply | 
| mulhsu | Multiply High Signed Unsigned | mulhsu rd, rs1, rs2 | rd = (rs1 * rs2)[63:32] | multiply | 
| div | Divide | div rd, rs1, rs2 | rd = rs1 / rs2 | divide | 
| rem | Remainder | rem rd, rs1, rs2 | rd = rs1 % rs2 | divide | 
Use addi for subtract immediate too. Multiply and divide instructions require the M extension.
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| and | AND | and rd, rs1, rs2 | rd = rs1 & rs2 | logical | 
| andi | AND Immediate | andi rd, rs1, imm | rd = rs1 & imm | logical | 
| not | NOT (p) | not rd, rs1 | rd = ~rs1 | logical | 
| or | OR | or rd, rs1, rs2 | rd = rs1 | rs2 | logical | 
| ori | OR Immediate | ori rd, rs1, imm | rd = rs1 | imm | logical | 
| xor | XOR | xor rd, rs1, rs2 | rd = rs1 ^ rs2 | logical | 
| xori | XOR Immediate | xori rd, rs1, imm | rd = rs1 ^ imm | logical | 
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| sll | Shift Left Logical | sll rd, rs1, rs2 | rd = rs1 | shift | 
| slli | Shift Left Logical Immediate | slli rd, rs1, imm | rd = rs1 | shift | 
| srl | Shift Right Logical | srl rd, rs1, rs2 | rd = rs1 >> rs2 | shift | 
| srli | Shift Right Logical Immediate | srli rd, rs1, imm | rd = rs1 >> imm | shift | 
| sra | Shift Right Arithmetic | sra rd, rs1, rs2 | rd = rs1 >>> rs2 | shift | 
| srai | Shift Right Arithmetic Immediate | srai rd, rs1, imm | rd = rs1 >>> imm | shift | 
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| li | Load Immediate (p) | li rd, imm | rd = imm | arithmetic | 
| lui | Load Upper Immediate | lui rd, imm | rd = imm | arithmetic | 
| auipc | Add Upper Immediate to PC | auipc rd, imm | rd = pc + (imm | branch | 
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| lw | Load Word | lw rd, imm(rs1) | rd = mem[rs1+imm] | load | 
| lh | Load Half | lh rd, imm(rs1) | rd = mem[rs1+imm][0:15] | load | 
| lhu | Load Half Unsigned | lhu rd, imm(rs1) | rd = mem[rs1+imm][0:15] | load | 
| lb | Load Byte | lb rd, imm(rs1) | rd = mem[rs1+imm][0:7] | load | 
| lbu | Load Byte Unsigned | lbu rd, imm(rs1) | rd = mem[rs1+imm][0:7] | load | 
| la | Load Symbol Address (p) | la rd, symbol | rd = &symbol | load | 
| sw | Store Word | sw rs2, imm(rs1) | mem[rs1+imm] = rs2 | store | 
| sh | Store Half | sh rs2, imm(rs1) | mem[rs1+imm][0:15] = rs2 | store | 
| sb | Store Byte | sb rs2, imm(rs1) | mem[rs1+imm][0:7] = rs2 | store | 
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| j | Jump (p) | j imm | pc += imm | jump | 
| jal | Jump and Link | jal rd, imm | rd = pc+4; pc += imm | jump | 
| jalr | Jump and Link Register | jalr rd, rs1, imm | rd = pc+4; pc = rs1+imm | jump | 
| call | Call Function (p) | call symbol | ra = pc+4; pc = &symbol | function | 
| ret | Return from Function (p) | ret | pc = ra | function | 
You can use a label in place of a jump immediate, for example: j label_name
This page lists all branch instructions but you may prefer the branch instruction summary.
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| beq | Branch Equal | beq rs1, rs2, imm | if(rs1 == rs2) pc += imm | branch | 
| beqz | Branch Equal Zero (p) | beqz rs1, imm | if(rs1 == 0) pc += imm | branch | 
| bne | Branch Not Equal | bne rs1, rs2, imm | if(rs1 ≠ rs2) pc += imm | branch | 
| bnez | Branch Not Equal Zero (p) | bnez rs1, rs2, imm | if(rs1 ≠ 0) pc += imm | branch | 
| blt | Branch Less Than | blt rs1, rs2, imm | if(rs1 < rs2) pc += imm | branch | 
| bltu | Branch Less Than Unsigned | bltu rs1, rs2, imm | if(rs1 < rs2) pc += imm | branch | 
| bltz | Branch Less Than Zero (p) | bltz rs1, imm | if(rs1 < 0) pc += imm | branch | 
| bgt | Branch Greater Than (p) | bgt rs1, rs2, imm | if(rs1 > rs2) pc += imm | branch | 
| bgtu | Branch Greater Than Unsigned (p) | bgtu rs1, rs2, imm | if(rs1 > rs2) pc += imm | branch | 
| bgtz | Branch Greater Than Zero (p) | bgtz rs1, imm | if(rs1 > 0) pc += imm | branch | 
| ble | Branch Less or Equal (p) | ble rs1, rs2, imm | if(rs1 ≤ rs2) pc += imm | branch | 
| bleu | Branch Less or Equal Unsigned (p) | bleu rs1, rs2, imm | if(rs1 ≤ rs2) pc += imm | branch | 
| blez | Branch Less or Equal Zero (p) | blez rs1, imm | if(rs1 ≤ 0) pc += imm | branch | 
| bge | Branch Greater or Equal | bge rs1, rs2, imm | if(rs1 ≥ rs2) pc += imm | branch | 
| bgeu | Branch Greater or Equal Unsigned | bgeu rs1, rs2, imm | if(rs1 ≥ rs2) pc += imm | branch | 
| bgez | Branch Greater or Equal Zero (p) | bgez rs1, imm | if(rs1 ≥ 0) pc += imm | branch | 
You can use a label in place of a branch immediate, for example: beq t0, t1, label_name
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| slt | Set Less Than | slt rd, rs1, rs2 | rd = (rs1 < rs2) | set | 
| slti | Set Less Than Immediate | slti rd, rs1, imm | rd = (rs1 < imm) | set | 
| sltu | Set Less Than Unsigned | sltu rd, rs1, rs2 | rd = (rs1 < rs2) | set | 
| sltiu | Set Less Than Immediate Unsigned | sltui rd, rs1, imm | rd = (rs1 < imm) | set | 
| seqz | Set Equal Zero | seqz rd, rs1 | rd = (rs1 == 0) | set | 
| snez | Set Not Equal Zero | snez rd, rs1 | rd = (rs1 ≠ 0) | set | 
| sltz | Set Less Than Zero | sltz rd, rs1 | rd = (rs < 0) | set | 
| sgtz | Set Greater Than Zero | sgtz rd, rs1 | rd = (rs1 > 0) | set | 
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| rdcycle | CPU Cycle Count (p) | rdcycle rd | rd = csr_cycle[31:0] | not yet avail | 
| rdcycleh | CPU Cycle Count High (p) | rdcycleh rd | rd = csr_cycle[63:32] | not yet avail | 
| rdtime | Current Time (p) | rdtime rd | rd = csr_time[31:0] | not yet avail | 
| rdtimeh | Current Time High (p) | rdtimeh rd | rd = csr_time[63:32] | not yet avail | 
| rdinstret | CPU Instructions Retired (p) | rdinstret rd | rd = csr_instret[31:0] | not yet avail | 
| rdinstreth | CPU Instructions Retired High (p) | rdinstreth rd | rd = csr_instret[63:32] | not yet avail | 
The counter instructions require the Zicsr extension but were originally part of the base instruction set.
| Instr | Description | Use | Result | Guide | 
|---|---|---|---|---|
| ebreak | Environment Break (Debugger Call) | ebreak | - | not yet avail | 
| ecall | Environment Call (OS Function) | ecall | - | not yet avail | 
| fence | I/O Ordering | fence | - | not yet avail | 
| mv | Copy Register (p) | mv rd, rs1 | rd = rs1 | arithmetic | 
| nop | No Operation (p) | nop | - | arithmetic | 
The fence instruction requires the Zifencei extension but was originally part of the base instruction set.
Check out all my FPGA & RISC-V Tutorials and my series on early Macintosh History.
If you enjoyed this post, please sponsor me. Sponsors help me create more FPGA and RISC-V projects for everyone, and they get early access to blog posts and source code. 🙏