A C++23 implementation of a RISC-V RV32I CPU simulator built from scratch.
The project is focused on understanding how a processor actually executes instructions, from instruction fetching and decoding through execution, memory access, and register writeback.
Rather than treating the CPU as a collection of instruction handlers, the simulator is being designed around an explicit execution state machine and a modular instruction-dispatch architecture.
The intended execution flow is:
┌──────────────┐
│ Memory │
└──────┬───────┘
│
▼
┌──────────────┐
│ Fetch │
└──────┬───────┘
│
▼
┌──────────────┐
│ Decode │
└──────┬───────┘
│
▼
┌──────────────┐
│ Execute │
└──────┬───────┘
│
▼
┌──────────────┐
│ Memory │
└──────┬───────┘
│
▼
┌──────────────┐
│ Writeback │
└──────┬───────┘
│
▼
Fetch
The simulator separates:
- Architectural CPU state
- Memory
- Instruction decoding
- Instruction execution
- CPU execution state
- Trap/error handling
Implemented:
read8write8read16write16read32write32- Little-endian memory representation
- Load/store alignment checking
- Load/store access-fault checking
Implemented:
- Program counter
- 32 general-purpose registers
x0hardwired to zero- Register read/write operations
- PC updates
Initial fetch/decode/execute architecture:
Fetch
↓
Decode
↓
Execute
↓
Writeback
↓
Fetch
The initial instruction set includes foundational RV32I integer operations such as:
ADDI
ADD
SUB
AND
OR
XOR
The instruction architecture is being extended toward the broader RV32I base instruction set.
The project has several goals:
- Understand RISC-V instruction encoding at the bit level.
- Understand the fetch-decode-execute cycle.
- Model CPU state explicitly.
- Build a deterministic memory subsystem.
- Use explicit architectural traps instead of exceptions for normal CPU faults.
- Keep instruction decoding separate from instruction execution.
- Build an extensible instruction-dispatch mechanism.
- Explore modern C++23 features where they provide real architectural value.
The simulator uses modern C++ features including:
- C++23
std::expectedenum classstd::arrayconstexpr[[nodiscard]]noexcept- fixed-width architectural types
- function-pointer/member-function dispatch
Advanced language features are introduced only when they solve a concrete design problem.
The goal is not to use C++ features for their own sake, but to understand how they can be applied to systems programming and CPU simulation.
The simulator is being designed around explicit CPU execution states:
FETCH
↓
DECODE
↓
EXECUTE
↓
MEMORY
↓
WRITEBACK
↓
FETCH
A member-function pointer can be used to represent the active CPU stage:
using StageHandler = void (Cpu::*)() noexcept;This allows the CPU to transition explicitly between execution stages.
The architecture also leaves room for future experiments involving:
- multi-cycle instructions
- pipeline stages
- stalls
- tracing
- interrupts
- instruction timing
- debugging
Architectural failures are represented explicitly using std::expected.
Examples include:
LoadAccessFault
StoreAccessFault
LoadAddressMisaligned
StoreAddressMisaligned
IllegalInstruction
Normal architectural traps are not implemented using C++ exceptions.
The project uses dedicated tests for:
- Memory operations
- Alignment handling
- Boundary conditions
- Little-endian behavior
- CPU register behavior
x0protection- Instruction execution
- Program-counter behavior
Build:
cmake -S . -B build
cmake --build buildRun CPU tests:
./build/test_cpuRun memory tests:
./build/test_memoryA successful test returns:
0
- Memory abstraction
- CPU state
- Register file
- PC management
- Instruction fetch
- Instruction decoding
- Basic ALU instructions
- Explicit CPU state machine
- Function-pointer stage dispatch
- Instruction dispatch table
- Expanded RV32I ALU instructions
- Instruction tracing
- Improved integration tests
-
LB -
LH -
LW -
LBU -
LHU -
SB -
SH -
SW
- Conditional branches
-
JAL -
JALR - PC-relative execution
- Branch testing
- Illegal-instruction handling
- More complete trap behavior
- Instruction tracing/debugging
- Program loading
- Small executable programs
- RV32I compliance-oriented testing
A small program can eventually be represented as:
ADDI x1, x0, 10
ADDI x2, x0, 20
ADD x3, x1, x2
SUB x4, x3, x1
Expected state:
x1 = 10
x2 = 20
x3 = 30
x4 = 20
The important part is not only the final register values, but the complete execution path:
Instruction
↓
Fetch
↓
Decode
↓
Execute
↓
Register/Memory update
↓
PC update
The implementation is developed alongside study of:
- RISC-V ISA specifications
- Computer architecture concepts
- C++ systems programming
- CPU emulator design
This is an educational and experimental CPU simulator under active development.
The project prioritizes architectural understanding, correctness, testability, and maintainable C++ design over claiming complete RV32I compliance prematurely.