RISC-V assembly
RISC-V is a small load-store ISA grown through named extensions. This skill covers user-mode RV32 and RV64 assembly, the psABI register contract, and the QEMU loop.
Contract
| Field |
Bound contract |
| Trigger |
The task writes inline asm or assembly for RV32 or RV64, decodes ABI register names, names an ISA extension string, enables compressed instructions, or debugs RISC-V under QEMU with GDB. |
| Authority |
Read-only. The skill explains and drafts; edits land through the normal coding path. No remote mutation. |
| Side effect |
None. |
| Done |
The drafted assembly assembles for the named base ISA and extensions, or the compiler output under discussion is explained register by register. |
Inputs
- The C or C++ source, assembly fragment, or compiler output: required.
- The base ISA and extensions: required. The
-march string such as rv64gc, written IMAFDC in ISA order.
- The execution environment: required. QEMU
virt for user or system work, hardware otherwise.
Procedure
- Map registers by role. The ABI names are what disassembly and assembly listings show. Done when: every register in the fragment is classified.
| Register |
ABI name |
Role |
x0 |
zero |
Hardwired zero, writes are discarded |
x1 |
ra |
Return address |
x2 |
sp |
Stack pointer |
x3 |
gp |
Global pointer |
x4 |
tp |
Thread pointer |
x5 to x7, x28 to x31 |
t0 to t6 |
Temporaries, caller saved |
x8 |
s0/fp |
Frame pointer or saved register, callee saved |
x9, x18 to x27 |
s1, s2 to s11 |
Saved registers, callee saved |
x10 to x17 |
a0 to a7 |
Arguments and return values, caller saved |
f0 to f7, f28 to f31 |
ft0 to ft11 |
FP temporaries, caller saved |
f8 to f9, f18 to f27 |
fs0 to fs11 |
FP saved, callee saved |
f10 to f17 |
fa0 to fa7 |
FP arguments and returns, caller saved |
Write the calling convention into the code. Args go in a0 to a7, extra args on the stack, return value in a0. Save callee-saved registers before use and restore them before return. Done when: any s register used is saved and restored.
Read the base instructions. Done when: each instruction in the fragment parses.
add a0, a1, a2 # a0 = a1 + a2
sub a0, a1, a2
mul a0, a1, a2 # M extension
div a0, a1, a2 # signed divide, remainder in rem
and a0, a1, a2
or a0, a1, a2
xor a0, a1, a2
sll a0, a1, a2 # shift left by register
slli a0, a1, 3 # shift by immediate
lw a0, 0(a1) # load word, RV32
ld a0, 0(a1) # load doubleword, RV64
lbu a0, 0(a1) # byte, zero-extended
sw a0, 0(a1)
sd a0, 0(a1)
beq a0, a1, label # branch if equal
blt a0, a1, label # signed less than
bge a0, a1, label # unsigned forms are bgeu/bltu with the u suffix
jal ra, func # call
jalr zero, ra, 0 # return, pseudo for ret
la a0, symbol # load address, pseudo
li a0, 42 # load immediate, pseudo
- Write the minimal function shape. A leaf function that fits in registers needs no stack. Done when: every non-leaf function saves and restores what it uses.
.global factorial # RV64 example
factorial:
li a1, 1 # result accumulator
1: beqz a0, 2f
mul a1, a1, a0
addi a0, a0, -1
j 1b
2: mv a0, a1
ret
Name the ISA correctly. Extensions combine into one string in fixed order. rv64gc is the common application profile and expands to IMAFDC plus Zicsr and Zifencei. a brings atomics, m integer multiply and divide, f and d single and double float. Done when: the -march string matches the hardware or QEMU target.
Write inline asm with the right constraints. RISC-V CSR access needs csrr or the csrrs family, and a memory clobber when the instruction has memory side effects. Done when: inputs, outputs, and clobbers are each listed.
static inline uint64_t rdcycle(void) {
uint64_t val;
__asm__ volatile("rdcycle %0" : "=r"(val));
return val;
}
- Use compressed instructions where density matters. The C extension replaces common 32-bit encodings with 16-bit ones; enable it through
-march=rv64gc or disable with -march=rv64ima. Verify with disassembly: compressed instructions print as c.addi, c.ld, and their c. family. Done when: the disassembly shows the intended encoding width.
riscv64-linux-gnu-gcc -march=rv64gc -O2 prog.c -o prog
riscv64-linux-gnu-objdump -d prog | grep -E '\sc\.'
- Debug under QEMU. Run QEMU with GDB waiting, then connect and break. Done when: breakpoints hit and registers read out.
qemu-riscv64 -g 1234 ./prog # user mode
qemu-system-riscv64 -M virt -nographic -kernel fw_jump.elf -gdb tcp::1234 -S
riscv64-linux-gnu-gdb ./prog
(gdb) target remote :1234
(gdb) b main
(gdb) c
Failure and recovery
| Failure class |
Behavior |
f instructions fail to assemble |
The -march string lacks f or d. Extend it, for example rv64gc already carries both. |
| Atomics undefined |
The a extension is missing from -march, or the target truly lacks it. Add a or rewrite with a lock. |
| Corruption across a call |
A callee-saved s register was used without save and restore. Audit the prologue and epilogue. |
| QEMU hangs at boot |
The kernel or firmware image does not match the machine. Re-run with -nographic and read the early console output. |
| GDB cannot connect |
The port disagrees or QEMU lacks -g. Check the QEMU command line first, then the GDB target. |
Output
Annotated assembly or inline asm with register roles, the exact -march string, and for QEMU work the exact launch and GDB commands. The full psABI table, including the floating-point calling variants, is in references/riscv-abi.md.
1---2name: assembly-riscv3description: Use when reading or writing RV32/RV64 assembly, inline asm in C, the RISC-V psABI, IMAFD extension naming, compressed instructions, or QEMU RISC-V debugging.4---56# RISC-V assembly78RISC-V is a small load-store ISA grown through named extensions. This skill covers user-mode RV32 and RV64 assembly, the psABI register contract, and the QEMU loop.910## Contract1112| Field | Bound contract |13|---|---|14| Trigger | The task writes inline asm or assembly for RV32 or RV64, decodes ABI register names, names an ISA extension string, enables compressed instructions, or debugs RISC-V under QEMU with GDB. |15| Authority | Read-only. The skill explains and drafts; edits land through the normal coding path. No remote mutation. |16| Side effect | None. |17| Done | The drafted assembly assembles for the named base ISA and extensions, or the compiler output under discussion is explained register by register. |1819## Inputs2021- The C or C++ source, assembly fragment, or compiler output: required.22- The base ISA and extensions: required. The `-march` string such as `rv64gc`, written `IMAFDC` in ISA order.23- The execution environment: required. QEMU `virt` for user or system work, hardware otherwise.2425## Procedure26271. Map registers by role. The ABI names are what disassembly and assembly listings show. Done when: every register in the fragment is classified.2829| Register | ABI name | Role |30|----------|----------|------|31| `x0` | `zero` | Hardwired zero, writes are discarded |32| `x1` | `ra` | Return address |33| `x2` | `sp` | Stack pointer |34| `x3` | `gp` | Global pointer |35| `x4` | `tp` | Thread pointer |36| `x5` to `x7`, `x28` to `x31` | `t0` to `t6` | Temporaries, caller saved |37| `x8` | `s0`/`fp` | Frame pointer or saved register, callee saved |38| `x9`, `x18` to `x27` | `s1`, `s2` to `s11` | Saved registers, callee saved |39| `x10` to `x17` | `a0` to `a7` | Arguments and return values, caller saved |40| `f0` to `f7`, `f28` to `f31` | `ft0` to `ft11` | FP temporaries, caller saved |41| `f8` to `f9`, `f18` to `f27` | `fs0` to `fs11` | FP saved, callee saved |42| `f10` to `f17` | `fa0` to `fa7` | FP arguments and returns, caller saved |43442. Write the calling convention into the code. Args go in `a0` to `a7`, extra args on the stack, return value in `a0`. Save callee-saved registers before use and restore them before return. Done when: any `s` register used is saved and restored.45463. Read the base instructions. Done when: each instruction in the fragment parses.4748```asm49add a0, a1, a2 # a0 = a1 + a250sub a0, a1, a251mul a0, a1, a2 # M extension52div a0, a1, a2 # signed divide, remainder in rem53and a0, a1, a254or a0, a1, a255xor a0, a1, a256sll a0, a1, a2 # shift left by register57slli a0, a1, 3 # shift by immediate58lw a0, 0(a1) # load word, RV3259ld a0, 0(a1) # load doubleword, RV6460lbu a0, 0(a1) # byte, zero-extended61sw a0, 0(a1)62sd a0, 0(a1)63beq a0, a1, label # branch if equal64blt a0, a1, label # signed less than65bge a0, a1, label # unsigned forms are bgeu/bltu with the u suffix66jal ra, func # call67jalr zero, ra, 0 # return, pseudo for ret68la a0, symbol # load address, pseudo69li a0, 42 # load immediate, pseudo70```71724. Write the minimal function shape. A leaf function that fits in registers needs no stack. Done when: every non-leaf function saves and restores what it uses.7374```asm75.global factorial # RV64 example76factorial:77 li a1, 1 # result accumulator781: beqz a0, 2f79 mul a1, a1, a080 addi a0, a0, -181 j 1b822: mv a0, a183 ret84```85865. Name the ISA correctly. Extensions combine into one string in fixed order. `rv64gc` is the common application profile and expands to `IMAFDC` plus Zicsr and Zifencei. `a` brings atomics, `m` integer multiply and divide, `f` and `d` single and double float. Done when: the `-march` string matches the hardware or QEMU target.87886. Write inline asm with the right constraints. RISC-V CSR access needs `csrr` or the `csrrs` family, and a `memory` clobber when the instruction has memory side effects. Done when: inputs, outputs, and clobbers are each listed.8990```c91static inline uint64_t rdcycle(void) {92 uint64_t val;93 __asm__ volatile("rdcycle %0" : "=r"(val));94 return val;95}96```97987. Use compressed instructions where density matters. The C extension replaces common 32-bit encodings with 16-bit ones; enable it through `-march=rv64gc` or disable with `-march=rv64ima`. Verify with disassembly: compressed instructions print as `c.addi`, `c.ld`, and their `c.` family. Done when: the disassembly shows the intended encoding width.99100```bash101riscv64-linux-gnu-gcc -march=rv64gc -O2 prog.c -o prog102riscv64-linux-gnu-objdump -d prog | grep -E '\sc\.'103```1041058. Debug under QEMU. Run QEMU with GDB waiting, then connect and break. Done when: breakpoints hit and registers read out.106107```bash108qemu-riscv64 -g 1234 ./prog # user mode109qemu-system-riscv64 -M virt -nographic -kernel fw_jump.elf -gdb tcp::1234 -S110riscv64-linux-gnu-gdb ./prog111(gdb) target remote :1234112(gdb) b main113(gdb) c114```115116## Failure and recovery117118| Failure class | Behavior |119|---|---|120| `f` instructions fail to assemble | The `-march` string lacks `f` or `d`. Extend it, for example `rv64gc` already carries both. |121| Atomics undefined | The `a` extension is missing from `-march`, or the target truly lacks it. Add `a` or rewrite with a lock. |122| Corruption across a call | A callee-saved `s` register was used without save and restore. Audit the prologue and epilogue. |123| QEMU hangs at boot | The kernel or firmware image does not match the machine. Re-run with `-nographic` and read the early console output. |124| GDB cannot connect | The port disagrees or QEMU lacks `-g`. Check the QEMU command line first, then the GDB target. |125126## Output127128Annotated assembly or inline asm with register roles, the exact `-march` string, and for QEMU work the exact launch and GDB commands. The full psABI table, including the floating-point calling variants, is in `references/riscv-abi.md`.