--- 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 ``` ![Doom Gameplay](https://imgur.com/bLc5LG8.gif) Run Quake (requires RV32F floating-point extension): ```shell make quake ``` ![Quake Gameplay](https://imgur.com/gXKb7D0.gif) 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