A Python simulator for stepping through RISC-V machine code in both non-pipelined and five-stage pipelined execution modes.
This project is useful when you want to see how instructions move through fetch, decode, execute, memory access and writeback, while watching registers, memory, branch behavior and cache statistics change as the program runs.
- A 32-bit RV32-style subset of RISC-V.
- Non-pipelined execution with a shared cache model.
- Pipelined execution with separate instruction and data cache models.
- Optional forwarding for the pipelined simulator.
- Stage-by-stage console traces for instruction flow and control signals.
- Register, memory and cache-state dumps after execution.
R format - add, sub, mul, div, rem, and, or, xor, sll, srl, sra, slt
I format - addi, andi, ori, xori, slli, srli, srai, slti, lb, lh, lw, lbu, lhu, jalr
S format - sb, sh, sw
SB format - beq, bne, blt, bge, bltu, bgeu
U format - auipc, lui
UJ format - jal
The simulator uses RV32-width architectural semantics: arithmetic and register writes wrap to 32 bits, signed loads are sign-extended, unsigned loads are zero-extended, byte and halfword stores keep only the low 8 or 16 bits, and jalr clears the low target bit.
Clone the repository, then run one of the sample programs from the project root.
python3 main.py ./TestCase/factorial.mc \
--cache_size 2048 \
--num_blocks_per_set 8 \
--block_size 64 \
--pipelined 1 \
--forwarding 1 \
--print_registers 2python3 main.py ./TestCase/factorial.mc \
--cache_size 1024 \
--num_blocks_per_set 4 \
--block_size 64 \
--pipelined 2| Option | Required | Description |
|---|---|---|
test_file |
Yes | Path to the .mc machine-code input file. |
--cache_size |
Yes | Cache size in bytes. |
--num_blocks_per_set |
Yes | Number of blocks per cache set. |
--block_size |
Yes | Cache block size in bytes. |
--pipelined |
Yes | 1 for pipelined mode, 2 for non-pipelined mode. |
--forwarding |
Pipelined only | 1 enables forwarding, 2 disables forwarding. |
--print_registers |
Pipelined only | 1 prints registers every cycle, 2 suppresses cycle-by-cycle register printing. |
Simulator dumps are written next to the input file. Output filenames include the execution mode and cache configuration.
Input files use the .mc format:
<address> <machine_code_or_data_byte>
Example:
0x0 0x10000517
0x4 0x00052503
0x54 0xFFFFFFFF
0x10000000 0x07
Rules and address assumptions:
- The text segment comes first.
- The data segment follows the termination instruction.
- Text starts at
0x00000000. - Data starts at
0x10000000. - Stack starts at
0x7FFFFFFC. - Heap starts at
0x10007FE8. - The text segment must end with
0xFFFFFFFF. - Data entries are loaded byte-by-byte.
The TestCase/ directory contains ready-to-run machine-code programs:
| File | Behavior |
|---|---|
factorial.mc |
Computes factorial for the input value and leaves the result in x10. |
sumtilln.mc |
Computes the sum of positive integers through n and leaves the result in x10. |
fibonacci.mc |
Writes a Fibonacci sequence into data memory. |
bubblesort.mc |
Sorts the byte array stored in data memory. |
The simulator prints a detailed trace as it executes:
FETCH: memory access through the PMI and the fetched instruction register (IR).DECODE: instruction fields, format detection, immediate values and control signals.EXECUTE: register reads, ALU operands and ALU result.MEMORY ACCESS: load/store activity,RYselection and PC updates.REGISTER UPDATE: register writeback when enabled.- Pipeline details: cycle completion, stalls, flushes and optional register snapshots.
- Cache statistics: accesses, hits, misses and victim blocks.
Pipelined output files also include run statistics such as total cycles, instruction count, CPI, hazard counts, stalls and branch mispredictions.
The repository includes a pytest regression suite for both simulator implementations.
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt
.venv/bin/python -m pytest -qThe tests cover:
- All sample
.mcprograms. - RV32 wrapping and sign behavior.
- Shifts,
lui,auipc,jal,jalrand branches. - Signed
div/rem, including divide-by-zero and overflow edge cases. - Signed and unsigned loads.
- Byte, halfword and word stores.
- Pipelined forwarding and hazard-sensitive branch behavior.
- Cache block accounting and pipelined CPI.
- Rejection of unsupported ALU encodings.
.
├── main.py
├── riscv_utils.py
├── requirements-dev.txt
├── TestCase/
│ ├── bubblesort.mc
│ ├── factorial.mc
│ ├── fibonacci.mc
│ └── sumtilln.mc
├── non_pipelined/
│ ├── ALU.py
│ ├── IAG.py
│ ├── control.py
│ ├── memory.py
│ └── register.py
├── pipelined/
│ ├── ALU.py
│ ├── IAG.py
│ ├── buffer.py
│ ├── control.py
│ ├── memory.py
│ └── register.py
└── tests/
├── conftest.py
└── test_correctness.py
- The simulator is intentionally verbose; the trace is part of the learning/debugging experience.
- Infinite recursion or programs without a reachable termination instruction can run indefinitely or crash.
- The implemented instruction set is a project subset, not a full RV32IM compliance suite.