Programming in RISC-V assembly and Compiling C to RISC-V assembly Examples simple sequences of instructions moving things around register to register addi x2,x0,11 add x3,x2,x0 register to/from memory addi x5,x0,0x11 # set x5 to 0x11 sw x5, 0x100(x0) # store at address 0x100 lw x6, 0x100(x0) # get from mem addi x6,x6,1 sw x6, 0x104(x0) # store to mem 0x104 assignment a = b + c local variables are stored in registers let x4 - a, x5 -b, x6 - c add x4, x5, x6 global variables are stored in memory let M[100] - a, M[104] - b, M[108] - c let x4 - a, x5 - b, x6 - c must move data from memory to register before perform addition lw x5, 0x104(x0) # get b lw x6, 0x108(x0) # get c add x4, x5, x6 sw x4, 0x100(x0) # store to a access to an array using index addressing, base address points to the beginning of the array ax[10] ax starts at 0x100 base address to get an element at ax[1] let x3 - index, x4 - base, x5 - effective address, x5 - value addi x3,x0,1 # index addi x4,x0,0x100 # base add x5,x3,x4 # compute effective address lw x6,0(x5) # get from memory loop while condition body :loop test condition jump if false to exit body jump loop :exit i = 0 while i < 10 i = i + 1 let x3 - i addi x3,x0,0 # i = 0 addi x4,x0,10 # const 10 loop: bge x3,x4, exit addi x3,x3,1 j loop exit: put it all together sum all elements of an array ax[5] s = 0 i = 0 while i < 5 s = s + ax[i] i = i + 1 let x3 - s, x4 - i, x5 - const 5, x6 - base, x7 - effective address, x8 - offset, x9 - ax[i] addi x3,x0,0 # s = 0 addi x4,x0,0 # i = 0 addi x5,x0,5 # const 5 addi x6,x0,0x100 # base address of ax[] addi x8,x0,0 # offset = 0 loop: bge x4, x5, exit add x7, x6, x8 # compute effective address lw x9, 0(x7) # get ax[i] add x3, x3, x9 # s = s + ax[i] addi x8, x8, 4 # next element addi x4, x4, 1 # increment index j loop exit: Compiling from highlevel language to assembly use godbolt.org (Compiler Explorer) with "RISC_V ricv32gc" compiler the first simple program (C) int a,b,c; int main(){ a = 11; } risc-v assembly output, we put a at address 0x100 li a1, 0x0100 li a0, 11 sw a0, 0(a1) second program: if then else int a,b,c; int main(){ if(a > 0){ b = 22; } } risc-v assembly output, a at 0x100, b at 0x104 li a1, 0x100 lw a1, 0(a1) bge a0, a1, LBB0_2 li a1, 0x104 li a0, 22 sw a0, 0(a1) LBB0_2: call a function to perform a "jump" to subroutine, we need a way to "come back" to where we call. use a register to store "return address" int double(int x){ return( x + x); } int main(){ double(2); } risc-v output is j main twice: # @twice addi sp, sp, -16 # create stack frame with 4 slots sw ra, 12(sp) # first slot keeps return address sw s0, 8(sp) # second slot keeps s0 addi s0, sp, 16 # set s0 to this stack frame sw a0, -12(s0) # store passing value (x) to slot 4 lw a0, -12(s0) # get x add a0, a0, a0 # x + x return value in a0 lw ra, 12(sp) # restore return address lw s0, 8(sp) addi sp, sp, 16 # delete stack frame ret main: # @main addi sp, sp, -16 # stack frame has 4 slots sw ra, 12(sp) sw s0, 8(sp) addi s0, sp, 16 li a0, 2 # pass number 2 in a0 call twice # call twice sw a0, -12(s0) # put return value to a li a0, 0 lw ra, 12(sp) lw s0, 8(sp) addi sp, sp, 16 # restore sp when a function calls another function, they need some information: return address, return value, space for local variable etc. these information are stored in "stack frame". This chunk of memory is pointed to by a register "sp". when "enter" a function, a stack frame is created, by decrement sp with the size of the stack (in this example, four slots, 16 bytes). sp -> 1) return address <- s0 2) old s0 3) ... 4) x sp' -> before the function ends, its stack frame is deleted, by increment sp, then the function return (using return address). Please note that the main function also has its own stack frame. How to pass a parameter to a function This is a simplified version that fits this example. in the above example main call twice(2), how the main sends 2 to the function twice? answer: the caller (main) passes number 2 via a register (a0), also when a function return value it returns that in a0.