RISC-V Assembler Cheat Sheet
This cheat sheet provides a handy reference to 32-bit RISC-V instructions, registers, and concepts. Aimed at software developers, it groups instructions by purpose and includes 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.
Arithmetic | Bitwise Logic | Shift | Load Immediate | Load and Store | Jump and Function | Branch | Set | Counters | Misc Instructions | Instruction Terminology | RV32 ABI Registers | RISC-V Concepts
Instructions are from the base integer instruction set (RV32I) unless otherwise noted.
Arithmetic
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.
Bitwise Logic
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 |
Shift
Instr | Description | Use | Result | Guide |
---|---|---|---|---|
sll | Shift Left Logical | sll rd, rs1, rs2 | rd = rs1 << rs2 | shift |
slli | Shift Left Logical Immediate | slli rd, rs1, imm | rd = rs1 << imm | 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 |
Load Immediate
Instr | Description | Use | Result | Guide |
---|---|---|---|---|
li | Load Immediate (p) | li rd, imm | rd = imm | arithmetic |
lui | Load Upper Immediate | lui rd, imm | rd = imm << 12 | arithmetic |
auipc | Add Upper Immediate to PC | auipc rd, imm | rd = pc + (imm << 12) | branch |
Load and Store
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 |
Jump and Function
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
Branch
This page lists all branch instructions but you may prefer the branch instruction summary table.
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
Set
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 (p) | seqz rd, rs1 | rd = (rs1 == 0) | set |
snez | Set Not Equal Zero (p) | snez rd, rs1 | rd = (rs1 ≠ 0) | set |
sltz | Set Less Than Zero (p) | sltz rd, rs1 | rd = (rs < 0) | set |
sgtz | Set Greater Than Zero (p) | sgtz rd, rs1 | rd = (rs1 > 0) | set |
Counters
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 Zicntr and Zicsr extensions but were originally part of the base instruction set.
Misc Instructions
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.
Instruction Terminology
- imm - immediate value (normally sign extended)
- mem - memory
- (p) - pseudoinstruction
- pc - program counter
- pc+4 - next instruction on RV32
- ra - return address register (x1)
- rd - destination register
- rs1 - first source register
- rs2 - second source register
- symbol - symbol (may be label in asm)
RV32 ABI Registers
ABI Name | Register | Description | Preserved |
---|---|---|---|
zero | x0 | always 0 (zero) | n/a |
ra | x1 | return address | no |
sp | x2 | stack pointer | yes |
gp | x3 | global pointer* | n/a |
tp | x4 | thread pointer* | n/a |
t0 | x5 | temporary | no |
t1 | x6 | temporary | no |
t2 | x7 | temporary | no |
fp (s0) | x8 | frame pointer† | yes |
s1 | x9 | saved register | yes |
a0 | x10 | function argument‡ | no |
a1 | x11 | function argument‡ | no |
a2 | x12 | function argument | no |
a3 | x13 | function argument | no |
a4 | x14 | function argument | no |
a5 | x15 | function argument | no |
a6 | x16 | function argument | no |
a7 | x17 | function argument | no |
s2 | x18 | saved register | yes |
s3 | x19 | saved register | yes |
s4 | x20 | saved register | yes |
s5 | x21 | saved register | yes |
s6 | x22 | saved register | yes |
s7 | x23 | saved register | yes |
s8 | x24 | saved register | yes |
s9 | x25 | saved register | yes |
s10 | x26 | saved register | yes |
s11 | x27 | saved register | yes |
t3 | x28 | temporary | no |
t4 | x29 | temporary | no |
t5 | x30 | temporary | no |
t6 | x31 | temporary | no |
*Let the compiler/linker use the global gp and thread tp pointers; ignore them in your own code.
†The frame pointer fp supports local variables but can be used as a regular saved register.
‡Argument registers a0 and a1 also handle the function return value.
RISC-V Concepts
Important RISC-V concepts, briefly explained.
- Data Sizes (word, half, byte)
- Extensions (customising RISC-V cores)
- Load-Store Architecture
- Preserved Registers (function calls)
- Pseudoinstructions
- Sign Extension
- Stack
- Zero Register
What’s Next?
Check out Compiler Explorer, my FPGA & RISC-V Tutorials, and series on early Macintosh History.
Get in touch on Mastodon, Bluesky, or X. If you enjoy my work, please sponsor me. 🙏
References
- RISC-V Technical Specifications (riscv.org)