o3
The O3 CPU model is gem5’s most detailed processor model, implementing a superscalar out-of-order pipeline with support for Simultaneous Multi-Threading (SMT). It models a 5-stage pipeline — Fetch, Decode, Rename, Issue/Execute/Writeback (IEW), and Commit — connected through TimeBuffer communication channels, with a Reorder Buffer (ROB) maintaining program order for precise exceptions.
⚙ 5-Stage Pipeline 🔨 Register Renaming 🚀 SMT Support 🎯 Speculative Execution
📚 What is this module?
The o3/ directory implements a detailed out-of-order superscalar CPU that models the behavior of modern high-performance processors like AArch32, AArch64, TrustZone security extensions, SVE/SVE2 vector processing, PMU, and hardware transactional memory." href="../../arch_doc/arm/index.html">ARM Cortex-A76 or Intel Skylake. Instructions can be fetched, decoded, and dispatched out of program order, then executed as soon as their operands are ready, and finally retired in strict program order through the ROB.
💡 Real-world analogy: Airport Security
Think of the O3 pipeline like airport security with multiple parallel lanes. Fetch is the check-in counter handing out boarding passes (instructions) in order. Decode is the document checker verifying each passport. Rename assigns each passenger to an available screening lane (physical register), regardless of their ticket number. IEW is the actual security screening — passengers go through whichever lane is free first, so a passenger with just a backpack (Atomic and Timing Simple CPUs — gem5 Wiki" href="../simple/index.html">simple ALU op) finishes before someone with three suitcases (memory load), even if they arrived later. Commit is the departure gate — passengers must board the plane in their original ticket order, no matter when they cleared security. If security finds a problem (branch misprediction), everyone behind that point is sent back to check-in (squash).
Class Architecture
The O3 CPU model is organized around a central o3::CPU class that owns all five pipeline stages and the communication buffers between them. Each stage is a separate C++ class that implements tick() logic called every cycle by the CPU.
classDiagram
class CPU {
+Fetch fetch
+Decode decode
+Rename rename
+IEW iew
+Commit commit
+ROB rob
+Scoreboard scoreboard
+TimeBuffer fetchQueue
+TimeBuffer decodeQueue
+TimeBuffer renameQueue
+TimeBuffer iewQueue
+tick()
+init()
+startup()
}
class Fetch {
+BPredUnit branchPred
+FTQ ftq
+BAC bac
+IcachePort icachePort
+tick()
+fetch()
+squash()
}
class Decode {
+tick()
+decode()
+squash()
}
class Rename {
+UnifiedRenameMap renameMap
+FreeList freeList
+tick()
+rename()
+squash()
}
class IEW {
+InstructionQueue instQueue
+LSQ ldstQueue
+Scoreboard scoreboard
+tick()
+dispatchInsts()
+executeInsts()
+writebackInsts()
}
class Commit {
+ROB rob
+tick()
+commitInsts()
+squashAll()
}
class ROB {
+list~DynInstPtr~ instList
+insertInst()
+retireHead()
+squash()
+isFull()
+isEmpty()
}
class FreeList {
+getReg()
+addReg()
+numFreeRegs()
}
class RenameMap {
+lookup()
+rename()
+setEntry()
}
CPU --> Fetch
CPU --> Decode
CPU --> Rename
CPU --> IEW
CPU --> Commit
CPU --> ROB
Fetch --> Decode : fetchQueue
Decode --> Rename : decodeQueue
Rename --> IEW : renameQueue
Rename --> ROB : allocate entry
IEW --> Commit : iewQueue
Commit --> ROB : retire/squash
Rename --> RenameMap
Rename --> FreeList
Key Classes
| Class | Header | Responsibility |
|---|---|---|
o3::CPU | o3/cpu.hh | Top-level CPU: owns pipeline stages, TimeBuffers, thread contexts. Drives tick() each cycle, handles squashes and thread scheduling. |
Fetch | o3/fetch.hh | Fetches instructions from ICache, drives branch predictor, creates DynInst objects. Supports SMT fetch policies (RoundRobin, IQCount, Branch). |
Decode | o3/decode.hh | Validates decoded instructions, checks PC-relative branches, handles macro-op expansion. Passes instructions to rename queue. |
Rename | o3/rename.hh | Maps architectural registers to physical registers using RenameMap. Allocates ROB entries. Handles squash by restoring old mappings from rename history. |
IEW | o3/iew.hh | Combined Issue/Execute/Writeback stage. Dispatches to IQ and LSQ, issues ready instructions to functional units, writes results back. |
Commit | o3/commit.hh | Retires instructions in program order from ROB head. Detects mispredictions, initiates squash signals. Handles non-speculative instruction barriers. |
ROB | o3/rob.hh | Reorder Buffer: circular buffer of in-flight DynInsts. Maintains program order for retirement. Drives squash when mispredictions are detected. |
InstructionQueue | o3/inst_queue.hh | Issue queue that tracks operand readiness via dependency graph. Issues instructions to FUs when all sources are ready. Implements age-based priority. |
LSQ | o3/lsq.hh | Load/Store Queue: manages memory ordering, store-to-load forwarding, and memory dependency speculation. Per-thread LSQUnit instances. |
LSQUnit | o3/lsq_unit.hh | Per-thread load/store queue. Handles store buffer, load queue, and memory ordering violations that trigger squashes. |
FreeList | o3/free_list.hh | Pool of available physical registers. Rename allocates from here; Commit returns old mappings back. |
UnifiedRenameMap | o3/rename_map.hh | Maps architectural register indices to physical register indices. Separate maps for int, float, vec, pred, and misc register classes. |
Scoreboard | o3/scoreboard.hh | Tracks which physical registers are ready (have been written). Used by IQ to determine instruction issue readiness. |
DynInst | o3/dyn_inst.hh | Dynamic instruction instance carrying sequence number, PC, physical register mappings, execution result, and status flags. |
FTQ | o3/ftq.hh | Fetch Target Queue: buffers fetch targets from the branch predictor for decoupled fetch. |
BAC | o3/bac.hh | Branch Address Calculator: resolves branch targets early in the fetch stage to reduce misprediction penalty. |
MemDepUnit | o3/mem_dep_unit.hh | Memory dependency predictor using store sets. Predicts which loads depend on prior stores to enable speculative load execution. |
StoreSet | o3/store_set.hh | Store set memory dependency predictor: tracks store-load conflicts and conservatively serializes predicted-dependent loads. |
FUPool | o3/fu_pool.hh | Pool of functional units shared by the IQ. Manages FU availability, latencies, and pipelining. |
Pipeline Data Flow with Stalls and Squashes
This diagram shows the full pipeline interaction including stall signals (back-pressure when structures fill) and squash signals (misprediction recovery):
sequenceDiagram
participant IC as ICache
participant FE as Fetch
participant DE as Decode
participant RN as Rename
participant IQ as InstructionQueue
participant LSQ as LSQ
participant FU as FuncUnits
participant ROB as ROB
participant CM as Commit
Note over FE: Cycle N: Fetch phase
FE->>IC: request cache line at PC
IC-->>FE: return instruction bytes
FE->>FE: create DynInst, predict branch
FE->>DE: push to fetchQueue
Note over DE: Cycle N+1: Decode phase
DE->>DE: validate, expand macros
DE->>RN: push to decodeQueue
Note over RN: Cycle N+2: Rename phase
RN->>RN: lookup RenameMap for src regs
RN->>RN: allocate dst phys reg from FreeList
RN->>ROB: allocate ROB entry
alt ROB full or IQ full or LSQ full
RN-->>DE: stall signal (block)
else resources available
RN->>IQ: dispatch non-memory insts
RN->>LSQ: dispatch loads/stores
end
Note over IQ,FU: Cycle N+3+: Issue/Execute
IQ->>IQ: check Scoreboard for ready ops
IQ->>FU: issue to functional unit
FU->>FU: execute (1+ cycle latency)
FU-->>IQ: writeback result, wake dependents
FU-->>ROB: mark inst completed
Note over LSQ: Memory operations
LSQ->>LSQ: check store-to-load forwarding
LSQ->>IC: send load to DCache
IC-->>LSQ: data response
Note over CM: Cycle N+K: Commit
CM->>ROB: check head entry
alt head is completed
CM->>CM: retire instruction
CM->>RN: free old phys reg
else head is mispredicted branch
CM-->>FE: squash(seqNum, correctPC)
CM-->>DE: squash(seqNum)
CM-->>RN: squash + restore RenameMap
CM-->>IQ: squash younger insts
CM-->>LSQ: squash younger entries
CM->>ROB: squash younger entries
end
Cross-Module Interactions
| Interaction | Source | Target | Mechanism |
|---|---|---|---|
| Instruction fetch | Fetch | ICache (mem/) | RequestPort::sendTimingReq() via IcachePort |
| Branch prediction | Fetch | BPredUnit (pred/) | predict() on each fetched branch |
| Load/Store access | LSQ | DCache (mem/) | RequestPort::sendTimingReq() via DcachePort |
| TLB translation | Fetch/LSQ | MMU (arch/) | MMU::translateTiming() for page walks |
| Instruction decode | Fetch | Decoder (arch/) | InstDecoder::decode() creates StaticInst |
| Interrupt handling | Commit | Interrupts (arch/) | Check pending interrupts at commit, redirect fetch |
| Functional checker | Commit | CheckerCPU (checker/) | Verify committed results against functional execution |
| Statistics | All stages | Stats (base/) | statistics::Scalar, statistics::Vector counters |
Deep-Dive Topics
Register Renaming
How RenameMap and FreeList eliminate WAR/WAW hazards by mapping architectural registers to a larger physical register file. The rename history buffer enables rollback on squash.
Instruction Queue & Issue Logic
The InstructionQueue dependency graph tracks which instructions are waiting on which physical registers. When a writeback arrives, the IQ wakes all dependents, enabling back-to-back scheduling.
Load/Store Queue & Memory Ordering
The LSQ/LSQUnit manages speculative loads, store-to-load forwarding, and memory ordering violations. The MemDepUnit with StoreSet predictor reduces unnecessary serialization.
Squash & Recovery
When a misprediction is detected at Commit, a squash signal propagates backward through all stages. Each stage removes younger instructions, the RenameMap is restored from the history buffer, and Fetch restarts at the correct PC.
SMT Scheduling Policies
O3 supports multiple SMT policies for each pipeline stage: RoundRobin, IQCount (prioritize thread with fewer IQ entries), Oldest (prioritize oldest instruction), and Branch (prioritize thread with fewer unresolved branches).
TimeBuffer Communication
TimeBuffer<T> in cpu/timebuf.hh models pipeline register latency. Each buffer has configurable forward/backward delay slots, allowing stages to communicate with realistic timing.
Directory Map
o3/ source tree
o3/
├── cpu.hh / cpu.cc ← o3::CPU: top-level OOO CPU class
├── fetch.hh / fetch.cc ← Fetch stage: ICache access, branch prediction
├── decode.hh / decode.cc ← Decode stage: instruction validation
├── rename.hh / rename.cc ← Rename stage: register renaming
├── iew.hh / iew.cc ← IEW stage: issue, execute, writeback
├── commit.hh / commit.cc ← Commit stage: in-order retirement
├── rob.hh / rob.cc ← Reorder Buffer
├── inst_queue.hh / inst_queue.cc ← Instruction Queue with dependency graph
├── lsq.hh / lsq.cc ← Load/Store Queue top-level
├── lsq_unit.hh / lsq_unit.cc ← Per-thread Load/Store Queue
├── dyn_inst.hh / dyn_inst.cc ← Dynamic instruction representation
├── dyn_inst_ptr.hh ← Smart pointer for DynInst
├── rename_map.hh / rename_map.cc ← Architectural → Physical register map
├── free_list.hh / free_list.cc ← Physical register free pool
├── scoreboard.hh / scoreboard.cc ← Physical register readiness tracker
├── fu_pool.hh / fu_pool.cc ← Functional Unit pool management
├── mem_dep_unit.hh / .cc ← Memory dependency predictor
├── store_set.hh / store_set.cc ← Store set algorithm
├── ftq.hh / ftq.cc ← Fetch Target Queue
├── bac.hh / bac.cc ← Branch Address Calculator
├── comm.hh ← Inter-stage communication structs
├── dep_graph.hh ← Dependency graph for IQ
├── limits.hh ← Compile-time width/size limits
├── regfile.hh / regfile.cc ← O3-specific register file
├── thread_context.hh / .cc ← O3 thread context implementation
├── thread_state.hh / .cc ← Per-thread state
├── checker.hh / checker.cc ← Integration with CheckerCPU
├── probe/ ← O3-specific probe points
├── BaseO3CPU.py ← Python parameter definitions
├── O3CPU.py ← Python CPU class configuration
├── FUPool.py ← Functional unit pool config
└── SConscript ← Build definitions
Configuration Parameters
| Parameter | Default | Description |
|---|---|---|
fetchWidth | 8 | Max instructions fetched per cycle |
decodeWidth | 8 | Max instructions decoded per cycle |
renameWidth | 8 | Max instructions renamed per cycle |
dispatchWidth | 8 | Max instructions dispatched to IQ/LSQ per cycle |
issueWidth | 8 | Max instructions issued to FUs per cycle |
wbWidth | 8 | Max instructions written back per cycle |
commitWidth | 8 | Max instructions committed per cycle |
numROBEntries | 192 | Reorder Buffer capacity |
numIQEntries | 64 | Instruction Queue capacity |
LQEntries | 32 | Load Queue entries |
SQEntries | 32 | Store Queue entries |
numPhysIntRegs | 256 | Physical integer registers |
numPhysFloatRegs | 256 | Physical floating-point registers |
fetchBufferSize | 64 | Fetch buffer size in bytes |
numThreads | 1 | Number of SMT threads |
Code Example: O3 Fetch Stage Tick
// Simplified view of Fetch::tick() from o3/fetch.cc
void Fetch::tick()
{
// Select thread to fetch from (SMT policy)
ThreadID tid = getFetchingThread();
if (tid == InvalidThreadID) return;
// Consult branch predictor for fetch direction
const auto &pred = branchPred->predict(fetchPC[tid]);
// Send ICache request
icachePort.sendTimingReq(createFetchReq(fetchPC[tid]));
// On cache response: decode instructions, create DynInsts
for (int i = 0; i < fetchWidth; ++i) {
auto inst = buildInst(tid, fetchPC[tid]);
fetchQueue[tid].push(inst);
if (inst->isControl()) {
// Update fetch PC based on prediction
fetchPC[tid] = pred.target;
break;
}
advancePC(fetchPC[tid]);
}
}
Code Example: Register Rename
// Simplified view of Rename::rename() from o3/rename.cc
void Rename::rename(DynInstPtr &inst)
{
// For each source operand: look up current mapping
for (auto &src : inst->srcRegs()) {
PhysRegIdPtr phys = renameMap->lookup(src.archReg());
inst->renameSrcReg(src.index(), phys);
}
// For each destination: allocate new physical register
for (auto &dst : inst->destRegs()) {
PhysRegIdPtr newReg = freeList->getReg(dst.classValue());
PhysRegIdPtr oldReg = renameMap->lookup(dst.archReg());
renameMap->setEntry(dst.archReg(), newReg);
inst->renameDestReg(dst.index(), newReg, oldReg);
// Record history for squash recovery
renameHistory.push({dst.archReg(), newReg, oldReg});
}
}