---
tags: computer-arch
---
# Lab2: RISC-V Instruction Set Simulator and System Emulator
## Overview
[rv32emu](https://github.com/sysprog21/rv32emu) is a RISC-V RV32 emulator that faithfully implements the RISC-V instruction set architecture. It serves as both:
- An educational tool for understanding modern RISC processor operations
- A starting point for customization and experimentation
Developed at NCKU. See VMIL'24 paper: "[Accelerate RISC-V Instruction Set Simulation by Tiered JIT Compilation](https://dl.acm.org/doi/10.1145/3689490.3690399)."
## Prerequisites
:::warning
At present, we only verify on Ubuntu GNU/Linux. Please install Ubuntu Linux 24.04-LTS (or later) into your machine. It is fine with virtualization environments such as [VirtualBox](https://www.virtualbox.org/).
:::
**Operating System**: Ubuntu Linux 24.04 LTS or later (virtualization environments like VirtualBox are supported) and macOS 15+.
## Setup
### 1. Install RISC-V GNU Toolchain
Download and install [xPack GNU RISC-V Embedded GCC](https://xpack-dev-tools.github.io/riscv-none-elf-gcc-xpack/): (Change the download link for other host architectures)
```shell
cd /tmp
wget https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-3/xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz
tar zxvf xpack-riscv-none-elf-gcc-14.2.0-3-linux-x64.tar.gz
cp -af xpack-riscv-none-elf-gcc-14.2.0-3 $HOME/riscv-none-elf-gcc
```
Configure environment:
```shell
cd $HOME/riscv-none-elf-gcc
echo "export PATH=`pwd`/bin:\$PATH" > setenv
```
Load the environment (do this each time you start a new shell):
```shell
source $HOME/riscv-none-elf-gcc/setenv
```
Verify installation:
```shell
riscv-none-elf-gcc -v
```
Expected output:
```
gcc version 14.2.0 (xPack GNU RISC-V Embedded GCC x86_64)
```
### 2. Install Dependencies
For SDL2 support (required for graphics/audio applications):
- **Ubuntu/Debian Linux**: `sudo apt install libsdl2-dev libsdl2-mixer-dev`
- **macOS**: `brew install sdl2 sdl2_mixer`
### 3. Build rv32emu
```shell
git clone https://github.com/sysprog21/rv32emu
cd rv32emu
make
```
### 4. Run Tests
```shell
make check
```
Expected output:
```
Running hello.elf ... [OK]
Running puzzle.elf ... [OK]
Running pi.elf ... [OK]
```
Run a test program:
```shell
build/rv32emu build/hello.elf
```
### 5. Run Game Demos
rv32emu can run classic games through SDL2. Make sure you've installed SDL2 dependencies (see step 2).
Run Doom:
```shell
make doom
```

Run Quake (requires RV32F floating-point extension):
```shell
make quake
```

These demos showcase rv32emu's ability to handle complex graphics and audio workloads, demonstrating the emulator's performance and SDL2 integration.
## Inspecting Binaries with GNU Toolchain
### objdump - Disassemble Code
Display assembly instructions from the compiled binary:
```shell
riscv-none-elf-objdump -d build/hello.elf
```
Expected output:
```
build/hello.elf: file format elf32-littleriscv
Disassembly of section .text:
00000000 <.text>:
0: 00000293 li t0,0
4: 00500313 li t1,5
8: 0040006f j 0xc
c: 00000013 nop
10: 02628263 beq t0,t1,0x34
14: 04000893 li a7,64
18: 00100513 li a0,1
1c: 00000597 auipc a1,0x0
20: 02458593 addi a1,a1,36 # 0x40
24: 00d00613 li a2,13
28: 00000073 ecall
2c: 00128293 addi t0,t0,1
30: fe1ff06f j 0x10
34: 05d00893 li a7,93
38: 00000513 li a0,0
3c: 00000073 ecall
40: 6548 .2byte 0x6548
42: 6c6c .2byte 0x6c6c
44: 6f57206f j 0x72f38
48: 6c72 .2byte 0x6c72
4a: 2164 .2byte 0x2164
4c: 000a .2byte 0xa
...
```
### readelf - Examine ELF Headers
Display ELF file header information:
```shell
riscv-none-elf-readelf -h build/hello.elf
```
Expected output:
```
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: RISC-V
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 4240 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 2
Size of section headers: 40 (bytes)
Number of section headers: 4
Section header string table index: 3
```
### size - Display Section Sizes
Show memory usage breakdown by section:
```shell
riscv-none-elf-size build/hello.elf
```
Expected output:
```
text data bss dec hex filename
80 0 0 80 50 build/hello.elf
```
## Building from Source
Rebuild the hello world example from assembly source:
```shell
cd tests/asm-hello
make
```
This generates `hello.elf` using the following compiler flags:
- `-march=rv32i` - Target RV32I instruction set architecture
- `-mabi=ilp32` - Use [ILP32 ABI](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc) (Integer, Long, Pointer all 32-bit)
Change the working directory to the top-level for further commands:
```shell
cd ../..
```
---
## System Emulation
rv32emu supports system emulation, allowing you to run Linux kernels or [bare-metal](https://en.wikipedia.org/wiki/Bare_machine) programs. System mode requires specific build configuration.
### Rebuilding for System Mode
Clean previous build:
```shell
make distclean
```
Build with system emulation enabled:
```shell
make -C tests/system/alignment
make ENABLE_ELF_LOADER=1 ENABLE_EXT_C=0 ENABLE_SYSTEM=1 misalign-in-blk-emu
```
Verify configuration:
```shell
grep -E "ENABLE_SYSTEM|ENABLE_ELF_LOADER" build/.config
```
Expected output:
```
ENABLE_ELF_LOADER=1
ENABLE_SYSTEM=1
```
### Playground Test Suite
Get the source code for playground:
```
git clone https://gist.github.com/jserv/5f682ac880773cab69e3564f4f20d60a tests/system/playground
```
The playground directory contains a comprehensive bare-metal test suite demonstrating:
- [ChaCha20](https://en.wikipedia.org/wiki/ChaCha20-Poly1305) cipher (RFC 7539) in RISC-V assembly
- BFloat16 floating-point arithmetic in software
- Performance counter integration
- Custom startup code and linker scripts
#### Building the Test Suite
From the playground directory:
```shell
cd tests/system/playground
make
```
Build artifacts:
- `test.elf` - Final executable
- `start.o` - Startup code with BSS initialization
- `main.o` - Test harness and implementations
- `perfcounter.o` - CSR access routines
- `chacha20_asm.o` - ChaCha20 cipher implementation
#### Running Tests
```shell
make run
```
The Makefile validates:
1. Emulator binary exists
2. `ENABLE_SYSTEM=1` is configured
3. `ENABLE_ELF_LOADER=1` is configured
Expected output:
```
=== ChaCha20 Tests ===
Test 0: ChaCha20 (RISC-V Assembly)
ChaCha20 RFC 7539: PASSED
Cycles: 6797
Instructions: 6797
=== BFloat16 Tests ===
Test 1: bf16_add - PASSED (Cycles: 432)
Test 2: bf16_sub - PASSED (Cycles: 373)
Test 3: bf16_mul - PASSED (Cycles: 464)
Test 4: bf16_div - PASSED (Cycles: 624)
Test 5: bf16_special_cases - PASSED (Cycles: 239)
=== All Tests Completed ===
```
### Test Components
#### ChaCha20 Cipher Implementation
RFC 7539 compliant stream cipher in pure RISC-V assembly:
**Key Features**:
- 256-bit key and 96-bit nonce
- Quarter-round ARX operations (Add-Rotate-XOR)
- Constant-time execution (no data-dependent branches)
- ~6800 cycles for complete block encryption
**Test Vector**: Validates against official RFC 7539 test vectors
**File**: `chacha20_asm.S`
The implementation uses macro-based quarter-rounds for code clarity while maintaining performance:
```asm
.macro QUARTERROUND a, b, c, d
add \a, \a, \b
xor \d, \d, \a
# ... rotation and additional operations
.endm
```
#### BFloat16 Floating-Point Arithmetic
Software implementation of Brain Floating Point format:
**Format** (16-bit):
- 1 sign bit
- 8 exponent bits (same range as FP32)
- 7 mantissa bits (reduced precision)
**Operations Tested**:
1. **Addition**: IEEE 754-compliant with proper rounding
2. **Subtraction**: Sign negation + addition
3. **Multiplication**: Exponent addition, mantissa multiplication
4. **Division**: Exponent subtraction, mantissa division
5. **Special cases**: Zero, NaN, infinity detection
**Algorithm**:
```
BF16 → FP32 (expand precision)
Perform operation in FP32
FP32 → BF16 (truncate and round)
```
**Performance**:
- Addition: ~430 cycles
- Subtraction: ~370 cycles
- Multiplication: ~460 cycles
- Division: ~620 cycles
#### Performance Counters
Access RISC-V CSRs for cycle-accurate profiling:
**CSRs Used**:
- `cycle` (0xC00) / `cycleh` (0xC80): 64-bit cycle counter
- `instret` (0xC02) / `instreth` (0xC82): 64-bit instruction counter
**Functions**:
```c
uint64_t get_cycles(void); // Read cycle count
uint64_t get_instret(void); // Read instruction count
```
**Implementation** (`perfcounter.S`):
```asm
get_cycles:
rdcycleh a1 # Read high 32 bits
rdcycle a0 # Read low 32 bits
rdcycleh a2 # Re-read high (detect overflow)
bne a1, a2, get_cycles # Retry if overflow occurred
ret
```
This handles the race condition where the low counter overflows between reads.
### Memory Layout
Custom linker script (`linker.ld`) defines bare-metal memory map:
```
MEMORY {
RAM : ORIGIN = 0x80000000, LENGTH = 64K
}
SECTIONS {
.text : { *(.text*) } > RAM
.rodata : { *(.rodata*) } > RAM
.data : { *(.data*) } > RAM
.bss : { *(.bss*) } > RAM
__bss_start = ADDR(.bss);
__bss_end = __bss_start + SIZEOF(.bss);
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
}
```
**Startup Sequence** (`start.S`):
1. Set stack pointer to `__stack_top`
2. Clear BSS section (zero-initialize uninitialized variables)
3. Call `main()`
4. Exit via `ecall` (syscall 93)
### Disassembly and Analysis
View complete disassembly with symbols:
```shell
make dump
```
This invokes:
```shell
riscv-none-elf-objdump -Ds test.elf | less
```
Key sections to examine:
- `.text` section: All code including startup and tests
- Symbol table: Function addresses and sizes
- Relocation entries: How linker resolved references
### Build System Details
**Toolchain Integration**:
```makefile
include ../../../mk/toolchain.mk
```
Automatically detects RISC-V toolchain from:
- `riscv-none-elf-*`
- `riscv32-unknown-elf-*`
- `riscv64-unknown-elf-*`
- `riscv-none-embed-*`
**Compilation Flags**:
- `AFLAGS = -g -march=rv32izicsr`: Debug info + RV32I with CSR extension
- `CFLAGS = -g -march=rv32i_zicsr`: Same for C compiler
- `LDFLAGS = -T $(LINKER_SCRIPT)`: Custom linker script
**Pattern Rules**:
```makefile
%.o: %.S
$(AS) $(AFLAGS) $< -o $@
%.o: %.c
$(CC) $(CFLAGS) $< -o $@ -c
```
Enables incremental builds - only modified files are recompiled.
### Educational Insights
This test suite demonstrates:
1. Bare-Metal Programming
- No OS or runtime support
- Manual BSS initialization
- Custom memory layout via linker script
2. Assembly programming
- Hand-coded ChaCha20 for performance
- Direct CSR access for counters
- Understanding RISC-V instruction encoding
3. Software Floating-Point
- Emulating non-standard formats (BFloat16)
- Precision vs. performance tradeoffs
4. Performance Analysis
- Cycle-accurate profiling via CSRs
- Algorithm characterization
- Optimization opportunities identification
5. Cross-Platform Development
- Building for RISC-V on x86/ARM host
- Toolchain management
- Debugging without target hardware
### Troubleshooting
**Build Errors**:
Error: `riscv-none-elf-gcc: command not found`
- Ensure RISC-V toolchain is in PATH
- Run `source $HOME/riscv-none-elf-gcc/setenv`
**Runtime Errors**:
Error: `ENABLE_SYSTEM=1 not set`
- Rebuild rv32emu: `make distclean && make ENABLE_SYSTEM=1 ENABLE_ELF_LOADER=1`
Error: `EMU not found`
- Build emulator first: `cd ../../../ && make`
**Unexpected Results**:
Wrong cycle counts:
- Verify system mode is enabled in `build/.config`
- Check that CSR emulation is working
- Ensure no optimization flags affecting counters
Linker warning: `RWX permissions`:
- Expected for bare-metal (combined code/data segment)
- Safe to ignore in emulation
### References
- [RISC-V Privileged ISA](https://riscv.org/technical/specifications/): CSR specifications and system mode
- [RFC 7539](https://tools.ietf.org/html/rfc7539): ChaCha20 and Poly1305 specification
- [BFloat16 Format](https://en.wikipedia.org/wiki/Bfloat16_floating-point_format): Brain Floating Point details
- [RISC-V ABI](https://github.com/riscv-non-isa/riscv-elf-psabi-doc): Calling conventions and register usage
- [GNU Linker Scripts](https://sourceware.org/binutils/docs/ld/Scripts.html): Linker script syntax reference