Note: Found this code on the web.
It is a good place to start.
To Start Writing A Program.
To start writing programs in RISC-V assembly language, you must establish
an entry point, divide your code into data and text sections, and use a
system call to cleanly exit. Here is a complete, minimal starter template
that performs a basic addition and safely terminates.
The RISC-V Starter Template
# Minimal RISC-V Assembly Program
.data # Section for declared variables and constants
# (Global variables go here)
.text # Section for executable machine instructions
.global _start # Make the entry label visible to the linker
_start: # Execution begins exactly here
# 1. Perform a simple operation (5 + 7)
li t0, 5 # Load immediate value 5 into register t0
li t1, 7 # Load immediate value 7 into register t1
add t2, t0, t1 # Add t0 and t1, saving the sum (12) in t2
# 2. Setup a clean exit system call (Linux ABI)
li a0, 0 # Argument 0: Set exit status code to 0 (Success)
li a7, 93 # Argument 7: System call number 93 is 'exit'
ecall # Trigger the environment call to the OS
Understanding the Blueprint
.global _start: Instructs the assembler and linker where to
point the CPU when the program first loads.
.text and .data: Segments that organize memory.
Code executes in .text, while constants and static strings
are stored in .data.
Registers (t0, a0, a7): RISC-V includes 32 general-purpose registers.
Registers starting with t are temporaries. Registers starting with
a
are used to pass arguments to functions or OS system calls.
li (Load Immediate): A pseudo-instruction that puts a
fixed integer value directly into a target register.
ecall: Halts standard code execution and jumps control over to
the operating system kernel to complete the task defined in
register a7.
Note
In a RISC-V assembler program, _start is the default global entry
point recognized by the linker, while start is just a regular, user-defined
text label with no intrinsic meaning.
Another View/Choice?
In RISC-V assembly language, the main label is written as main: followed by the .global main directive to make it visible to the linker.
Write main: at the start of your primary function code.
Use .global main so external runtimes or linkers can
find the entry point.
Place the label inside the .text assembly section.
.global main
.text
main:
addi sp, sp, -16 # Allocate stack space
sd ra, 0(sp) # Save return address
# Your instructions go here
mv a0, zero # Return 0 status code
ld ra, 0(sp) # Restore return address
addi sp, sp, 16 # Deallocate stack space
ret
Assembler Directives
In RISC-V assembly language, sections are handled using assembler directives. These directives tell the assembler how to organize code and data within
the final Executable and Linkable Format (ELF) object binary.
The primary commands used to define and switch between sections
are .section, .text, .data, .rodata, and .bss.
For standard ELF sections, you do
not need to use the raw .section syntax. The GNU Assembler (GAS) provides
direct, built-in shortcuts:
.text: Switches the assembler to the executable code section. All your
core RISC-V CPU instructions (e.g., addi, lw, beq) belong here. It is marked
as read-only and executable.
.data: Switches to the initialized data section. Use this for global or
static variables that require an initial value (like arrays, tables, or
variables). It is read-write.
.rodata: Switches to the read-only data section. This is ideal for fixed constants and string literals (e.g., "Hello, World\n").
.bss: Switches to the uninitialized data section. It allocates space for
variables that default to zero at program launch. It takes up no space in the
storage binary, only in RAM.