Project Name: SparseFlow Simulator
Version: 1.0
Date: November 26, 2025
Language: C++17
The SparseFlow Simulator is a custom cycle-accurate simulator for a 5-stage pipelined processor based on a subset of the RISC-V ISA. It introduces custom extensions optimized for sparse data processing (e.g., LNZ - Load Non-Zero, ZMUL - Zero-Skipping Multiply).
The simulator models:
- 5-Stage Pipeline: Fetch (IF), Decode (ID), Execute (EX), Memory (MEM), Writeback (WB).
- Hazard Handling: Data forwarding, Load-Use stalls, and Control hazard flushing.
- Memory Hierarchy: Simple instruction memory and data memory with a simulated cache latency model.
- Branch Prediction: 1-bit dynamic branch predictor.
- Custom ISA Extensions: Specialized instructions for sparse matrix operations.
Project - ASP/
├── src/
│ ├── core/ # Core pipeline logic
│ │ ├── SparseFlowCore.cpp/.h # Main pipeline controller
│ │ └── PipelineRegisters.h # Pipeline register definitions (IF/ID, etc.)
│ ├── hardware/ # Hardware component models
│ │ ├── Memory.cpp/.h # RAM and Cache model
│ │ ├── RegisterFile.cpp/.h # Scalar (x0-x31) and Vector (v0-v7) registers
│ │ ├── BranchPredictor.cpp/.h # Branch prediction logic
│ │ └── Cache.cpp/.h # Cache simulation
│ ├── isa/ # Instruction Set Architecture
│ │ ├── Instruction.h # Opcode enums and Instruction struct
│ │ └── ISA.h # General ISA constants
│ ├── utils/ # Utility classes
│ │ ├── Loader.cpp/.h # Assembly file parser
│ │ ├── Stats.cpp/.h # Performance statistics tracker
│ │ └── TraceLogger.cpp/.h # Cycle-by-cycle CSV logger
│ └── main.cpp # Entry point
├── assignment3_prog1.asm # Test Program 1 (Arithmetic)
├── assignment3_prog2.asm # Test Program 2 (Loop/Threshold)
├── assignment3_func.asm # Test Program 3 (Function Call)
└── simulator.exe # Compiled executable
The core implements a classic 5-stage pipeline.
- Fetch (IF): Fetches instruction from memory at
PC. Uses Branch Predictor forBEQ/JMP. - Decode (ID): Reads registers (
rs1,rs2). Detects Load-Use Hazards and inserts stalls (bubbles) if necessary. - Execute (EX): Performs ALU operations.
- Forwarding Unit: Forwards data from MEM or WB stages to EX to resolve data hazards.
- Zero-Skipping:
ZMULchecks if operands are zero; if so, it skips execution cycles. - Branch Resolution: Verifies branch prediction. If mispredicted, flushes IF/ID and corrects PC.
- Memory (MEM): Accesses data memory.
- Cache Model: Simulates latency (1 cycle hit, 10 cycle miss). Stalls pipeline on miss.
- LNZ Optimization:
LNZchecks if loaded value is zero. If zero, it auto-increments the pointer and skips the value (simulating hardware sparse skipping).
- Writeback (WB): Writes results back to the Register File.
- Instruction Memory: Read-only, indexed by PC.
- Data Memory: 4KB addressable space.
- Cache: Direct-mapped cache simulation.
requestAccess(addr)returns latency cycles.- Core stalls if latency > 1.
- Scalar Registers: 32 x 32-bit registers (
x0-x31).x0is hardwired to 0.- Standard RISC-V ABI names supported (
zero,ra,sp,t0-t6,a0-a7,s0-s1).
- Vector Registers: 8 x 128-bit registers (
v0-v7), modeled as 4 integers each.
The simulator supports a subset of RISC-V plus custom extensions.
| Opcode | Format | Description |
|---|---|---|
ADD rd, rs1, rs2 |
R-Type | rd = rs1 + rs2 |
SUB rd, rs1, rs2 |
R-Type | rd = rs1 - rs2 |
ADDI rd, rs1, imm |
I-Type | rd = rs1 + imm |
LW rd, off(rs1) |
I-Type | rd = Mem[rs1 + off] |
SW rs2, off(rs1) |
S-Type | Mem[rs1 + off] = rs2 |
BEQ rs1, rs2, label |
B-Type | Branch if rs1 == rs2 |
JMP label |
J-Type | Unconditional Jump |
JR rs1 |
I-Type | Jump to address in rs1 (Return) |
SLT rd, rs1, rs2 |
R-Type | rd = (rs1 < rs2) ? 1 : 0 |
| Opcode | Format | Description |
|---|---|---|
LNZ rd, off(rs1) |
I-Type | Load Non-Zero. Loads value. If 0, skips writeback and increments rs1 to next address automatically. |
ZMUL rd, rs1, rs2 |
R-Type | Zero-Skip Multiply. If rs1 or rs2 is 0, result is 0 (1 cycle). Else, performs multiply (4 cycles). |
VLOAD vd, off(rs1) |
I-Type | Vector Load. Loads 4 consecutive words into vector register vd. |
BZERO rs1, label |
B-Type | Branch Zero. Branch if rs1 == 0. Optimized for sparse checks. |
MOV rd, val/reg |
Pseudo | Pseudo-instruction. MOV rd, 5 -> ADDI rd, x0, 5. MOV rd, rs -> ADDI rd, rs, 0. |
HALT |
X-Type | Stops simulation. |
Use g++ with C++17 standard.
Windows (PowerShell):
g++ -std=c++17 -I src/core -I src/utils -I src/hardware -I src/isa src/main.cpp src/core/SparseFlowCore.cpp src/utils/Loader.cpp src/hardware/Memory.cpp src/hardware/RegisterFile.cpp src/hardware/BranchPredictor.cpp src/utils/Stats.cpp src/utils/TraceLogger.cpp src/hardware/Cache.cpp -o simulator./simulator <assembly_file.asm>Example:
./simulator assignment3_prog2.asmThe simulator prints:
- Loader Output: Parsed instructions and labels.
- Cycle-by-Cycle Trace: (Optional debug prints).
- Performance Report:
- Total Cycles
- Instructions Retired
- CPI / IPC
- Stall Cycles
- Branch Mispredictions
- Register Dump: Final state of all registers.
- Load-Use Hazard: Detected in Decode stage. If
IDneeds a register thatEX(which is a Load) is about to write, a bubble is inserted inEX, andPC/IF_IDare stalled. - Control Hazard: Detected in Execute stage. If branch is taken (and not predicted),
IF_IDis flushed, andPCis updated to target.
- EX-to-EX: Forwards ALU result from
EX/MEMpipeline register toID/EXinputs ifrs1orrs2matchesrdof previous instruction. - MEM-to-EX: Forwards Writeback result from
MEM/WB(shadow) toID/EXinputs ifrs1orrs2matchesrdof instruction 2 cycles ago.
- Supports comments (
#,//,;). - Supports labels (
LOOP:). - Supports register aliases (
t0,a0,sp,ra). - Auto-translates
MOVpseudo-instructions.
Tests across matrices with varying sparsity levels demonstrate consistent performance gains:
| Sparsity | Pipelined Cycles | Single-Cycle | Speedup | Zero-Skips |
|---|---|---|---|---|
| 60% | 12,093 | 35,615 | 2.95x | 599 |
| 70% | 11,405 | 33,165 | 2.91x | 700 |
| 80% | 10,708 | 30,785 | 2.87x | 799 |
| 90% | 9,962 | 28,477 | 2.86x | 899 |
| 99% | 9,270 | 26,441 | 2.85x | 989 |
Key Observations:
- Pipeline speedup is consistent (~2.9x) across all sparsity levels
- Zero-skips increase proportionally with sparsity
- Even at 60% sparsity (40% dense), LNZ provides significant benefit
Matrix Size: 262,144 elements | Sparsity: ~99.98% | Non-Zeros: 50
| Metric | Pipelined | Single-Cycle |
|---|---|---|
| Total Cycles | 566,038 | 566,919 |
| Zero-Skips | 262,093 | 262,093 |
| Instructions Retired | 311 | 311 |