A cycle-accurate simulator of the Tomasulo out-of-order execution algorithm, written in C++17. It models a superscalar processor with dynamic scheduling, a reorder buffer, a common data bus, and a 2-bit branch predictor — running a subset of RISC-V assembly.
- Out-of-order execution via Tomasulo's algorithm with register renaming (RAT)
- Reorder Buffer (ROB) for in-order commit and precise exceptions
- Multiple functional units with configurable latencies and reservation station sizes:
- Adder (ADD, SUB, ADDI, SLT, SLTI)
- Multiplier (MUL)
- Divider (DIV, REM)
- Logic unit (AND, OR, XOR, ANDI, ORI, XORI)
- Branch unit (BEQ, BNE, BLT, BLE, J)
- Load/Store Queue (LW, SW)
- Common Data Bus (CDB) for result broadcasting and operand forwarding
- 2-bit saturating counter branch predictor with per-PC state and misprediction recovery (pipeline flush)
- Memory-mapped data section via
.label:declarations in assembly files - Cycle limit flag for bounded simulation
Fetch → Decode → [Reservation Stations] → Execute → CDB Broadcast → Commit
↑ |
RAT ←──────────────────── ROB ─────────────────┘
| Component | Description |
|---|---|
| ARF | 32 architectural registers (x0–x31) |
| RAT | Register Alias Table for renaming to ROB tags |
| ROB | 64-entry circular Reorder Buffer |
| CDB | Broadcasts results to waiting reservation stations |
| LSQ | 32-entry Load/Store Queue with memory disambiguation |
| Branch Predictor | Per-PC 2-bit saturating counter; flushes pipeline on mispredict |
| Unit | Latency (cycles) | RS Capacity |
|---|---|---|
| Logic | 1 | 4 |
| Adder | 2 | 4 |
| Multiplier | 4 | 2 |
| Divider | 5 | 2 |
| Memory | 4 | 32 |
| Branch | 2 | 2 |
Requires g++ with C++17 support.
make compileTo compile with a custom entry file:
make compile FILE=<filename.cpp>This produces the main binary.
./main <program.s>To cap execution at N cycles:
./main <program.s> -cycles NAfter execution completes, the simulator prints:
- All 32 architectural register values
- Branch predictor accuracy (correct / total)
- Exception information (if applicable)
- Full memory contents
Execution halts and the offending instruction's PC is reported if:
| Cause | Instructions |
|---|---|
| Integer overflow (result doesn't fit in 32 bits) | add, sub, addi, mul |
| Division by zero | div, rem |
On exception the pipeline is flushed and the architectural state reflects all instructions committed before the faulting one.
The simulator accepts a RISC-V-style assembly dialect. Registers are written as x0–x31. Comments begin with #.
| Category | Instructions |
|---|---|
| Arithmetic (R-type) | add, sub, mul, div, rem, slt |
| Arithmetic (I-type) | addi, slti |
| Logic (R-type) | and, or, xor |
| Logic (I-type) | andi, ori, xori |
| Memory | lw, sw |
| Branch | beq, bne, blt, ble, j |
x0is always 0 (RISC-V convention); writes to it are ignored.swtakes the source register first:sw xSource, offset(xBase)— the opposite oflw.- Branch offsets and label targets are relative to the instruction's PC (in instruction units, not bytes).
Code labels can appear inline:
loop:
addi x1, x1, 1
blt x1, x2, loopMemory data is declared at the top of the file with .label: syntax:
.arr: 10 20 30 40 50
main:
lw x1, arr(x0) # loads Memory[arr + 0] into x1# Compute sum of 5 elements
.data: 3 7 2 9 4
addi x2, x0, 5 # loop counter
addi x3, x0, 0 # sum accumulator
addi x4, x0, 0 # index
loop:
lw x5, data(x4) # load data[index]
add x3, x3, x5 # sum += data[index]
addi x4, x4, 1 # index++
blt x4, x2, loop # if index < 5, continueRun it:
./main example.sOutput:
[+] Execution complete naturally in 31 cycles.
=== ARCHITECTURAL STATE (CYCLE 31) ===
x0: 0 | x1: 0 | x2: 5 | x3: 25 | x4: 5 | x5: 4 | x6: 0 | x7: 0 |
x8: 0 | x9: 0 | x10: 0 | x11: 0 | x12: 0 | x13: 0 | x14: 0 | x15: 0 |
...
Branch Predictor Stats: 4/5 correct.
3 7 2 9 4 0 0 0 ...
x3 = 25 is the sum of {3, 7, 2, 9, 4}. The branch predictor mispredicted once on the final loop iteration (branch not taken), which is expected behaviour for a 2-bit counter seeing a new PC.
Processor parameters are set in ProcessorConfig (Basics.h) and require recompilation to change. Key fields:
| Field | Default | Description |
|---|---|---|
num_regs |
32 | Number of architectural registers |
rob_size |
64 | Reorder Buffer entries |
mem_size |
1024 | Memory words |
add_lat / mul_lat / div_lat / mem_lat / logic_lat |
2 / 4 / 5 / 4 / 1 | Execution latencies (cycles) |
adder_rs_size / mult_rs_size / div_rs_size / br_rs_size / lsq_rs_size / logic_rs_size |
4 / 2 / 2 / 2 / 32 / 4 | Reservation station sizes per unit |
| File | Responsibility |
|---|---|
Basics.h |
Core structs: Instruction, ROBEntry, RSEntry, ProcessorConfig, opcode/unit enums |
Processor.h / .cpp |
Top-level processor: pipeline stages (Fetch, Decode, Execute, Commit), CDB broadcast, flush |
LoadProgram.cpp |
Assembly parser: tokenization, label resolution, memory section handling |
ExecutionUnit.h / .cpp |
Functional unit with reservation station, cycle countdown, result capture |
LoadStoreQueue.h / .cpp |
Memory access unit; handles load forwarding and store ordering against ROB |
BranchPredictor.h / .cpp |
2-bit saturating counter predictor; updates on commit, triggers flush on mispredict |
main.cpp |
Entry point: argument parsing, run loop, final state dump |
Makefile |
Build rules |
- Manya Jain
- Prabuddha Sinha