simple
The Simple CPU models are gem5’s fastest and simplest processor implementations, executing one instruction at a time without pipelining. AtomicSimpleCPU uses instantaneous memory accesses for maximum simulation speed, while TimingSimpleCPU models realistic Cache Hierarchy | gem5 Wiki" href="../../mem_doc/cache/index.html">cache port timing. Together, they are the workhorses for functional validation, fast-forward phases, and memory system research.
⚙ One-at-a-Time 🔨 Atomic Memory 🚀 Timing Memory 🎯 Fastest Simulation
📚 What is this module?
The simple/ directory provides CPU models that execute instructions one at a time in a simple fetch–execute loop with no pipeline stages. These are not designed to model real processor microarchitecture but instead to provide functional correctness at maximum simulation speed. They are typically used for fast-forwarding to a region of interest (then switching to O3/Minor), for warming up caches, or for studies where only the memory system matters and CPU pipeline detail is irrelevant.
💡 Real-world analogy: Calculator vs Computer
If the O3 CPU is a modern multi-lane superscalar processor and Minor is a simple pipelined processor, then the Simple CPUs are like a basic calculator. You enter one calculation at a time, get the result, then enter the next one. AtomicSimpleCPU is like a calculator with instant memory — press the button and the answer appears immediately (no cache delay). TimingSimpleCPU is like a calculator connected to a filing cabinet — it still does one calculation at a time, but it has to wait for the filing cabinet (cache/memory) to deliver the data before proceeding. This makes Timing useful for studying how memory latency affects program execution.
Class Hierarchy
All simple CPU variants inherit from BaseSimpleCPU, which provides the common fetch–execute loop infrastructure. Each variant overrides the memory access methods to implement different timing models.
flowchart TD
BCPU["BaseCPU\ncpu/base.hh"]
BSC["BaseSimpleCPU\nsimple/base.hh"]
ASC["AtomicSimpleCPU\nsimple/atomic.hh"]
TSC["TimingSimpleCPU\nsimple/timing.hh"]
NCSC["NonCachingSimpleCPU\nsimple/noncaching.hh"]
SEC["SimpleExecContext\nsimple/exec_context.hh"]
BCPU --> BSC
BSC --> ASC
BSC --> TSC
ASC --> NCSC
BSC -.->|"creates"| SEC
CPU Variants
AtomicSimpleCPU
AtomicSimpleCPU (simple/atomic.hh) is the fastest CPU model in gem5. It uses atomic memory accesses — each load or store completes instantaneously in a single function call, returning the access latency as a value that the CPU uses to advance the simulation clock. No cache timing ports are used; instead, the CPU calls sendAtomic() which bypasses the normal cache request/response protocol.
This makes AtomicSimpleCPU ideal for:
- Fast-forwarding billions of instructions to reach a region of interest
- Functional validation of ISA implementations
- Boot-up phases in full-system simulation before switching to a detailed model
- SimPoint checkpoint generation
TimingSimpleCPU
TimingSimpleCPU (simple/timing.hh) uses timing memory accesses through standard cache request/response ports. Each memory operation sends a timing request to the L1 cache and must wait for the response callback before the instruction can complete. This models realistic cache hit/miss latencies while keeping the CPU model itself minimal.
Use TimingSimpleCPU when you need:
- Memory system studies where pipeline detail is irrelevant
- A simple CPU to drive cache/DRAM timing simulations
- Baseline performance numbers with realistic memory timing
NonCachingSimpleCPU
NonCachingSimpleCPU (simple/noncaching.hh) extends AtomicSimpleCPU by connecting directly to the memory bus without any caches. Every memory access goes straight to main memory. This is used for minimal configurations and testing.
Execution Loop
Both Atomic and Timing share the same basic execution loop defined in BaseSimpleCPU:
flowchart TD
START["tick()"] --> PREFETCH["preExecute()\nFetch instruction at PC"]
PREFETCH --> DECODE["Decode: create StaticInst"]
DECODE --> EXEC["Execute: call StaticInst::execute()\nvia SimpleExecContext"]
EXEC --> MEM{"Memory\ninstruction?"}
MEM -->|"Yes, Atomic"| AMEM["sendAtomic()\nInstant response + latency"]
MEM -->|"Yes, Timing"| TMEM["sendTimingReq()\nWait for callback"]
MEM -->|"No"| POST["postExecute()\nUpdate PC, stats"]
AMEM --> POST
TMEM --> WAIT["Suspend until\nrecvTimingResp()"]
WAIT --> POST
POST --> ADVANCE["advancePC()\nSchedule next tick()"]
ADVANCE --> START
Key Classes
| Class | Header | Responsibility |
|---|---|---|
BaseSimpleCPU | simple/base.hh | Base class with common fetch–execute loop, branch prediction, tracing, and thread management. Not instantiated directly. |
AtomicSimpleCPU | simple/atomic.hh | Atomic memory model. Overrides readMem() and writeMem() with sendAtomic() calls. Advances clock by access latency. |
TimingSimpleCPU | simple/timing.hh | Timing memory model. Uses sendTimingReq() and recvTimingResp() callbacks. Suspends CPU until memory response. |
NonCachingSimpleCPU | simple/noncaching.hh | AtomicSimpleCPU variant that bypasses caches, connecting directly to memory bus. |
SimpleExecContext | simple/exec_context.hh | ExecContext implementation for Simple CPUs. Provides register read/write and memory access methods to StaticInst::execute(). |
SimpleThread | cpu/simple_thread.hh | Per-thread architectural state: register file, PC, and miscellaneous registers. Used by all Simple (and Minor) CPU models. |
BPredUnit | cpu/pred/bpred_unit.hh | Optional branch predictor. BaseSimpleCPU supports an attached predictor for branch prediction accuracy studies. |
Data Flow: Atomic vs Timing
sequenceDiagram
participant CPU as SimpleExecContext
participant BSC as BaseSimpleCPU
participant MEM as Memory System
rect rgb(30, 40, 55)
Note over CPU,MEM: AtomicSimpleCPU path
BSC->>BSC: preExecute() - fetch & decode
BSC->>CPU: StaticInst::execute(xc)
CPU->>MEM: sendAtomic(pkt)
MEM-->>CPU: latency = 3 cycles (instant)
CPU-->>BSC: execution complete
BSC->>BSC: advancePC, schedule tick(+latency)
end
rect rgb(30, 50, 40)
Note over CPU,MEM: TimingSimpleCPU path
BSC->>BSC: preExecute() - fetch & decode
BSC->>CPU: StaticInst::execute(xc)
CPU->>MEM: sendTimingReq(pkt)
Note over BSC: CPU suspended, no tick scheduled
MEM-->>CPU: recvTimingResp(pkt) after 3-200 cycles
CPU-->>BSC: completeDataAccess()
BSC->>BSC: postExecute, advancePC, schedule tick(+1)
end
Cross-Module Interactions
| Interaction | Simple Component | External Module | Mechanism |
|---|---|---|---|
| Atomic memory access | AtomicSimpleCPU | Cache/Memory (mem/) | sendAtomic() — synchronous, returns latency |
| Timing memory access | TimingSimpleCPU | Cache (mem/) | sendTimingReq() / recvTimingResp() callback |
| Instruction decode | BaseSimpleCPU | Decoder (arch/) | InstDecoder::decode() → StaticInst |
| TLB translation | BaseSimpleCPU | MMU (arch/) | MMU::translateAtomic() or translateTiming() |
| Branch prediction | BaseSimpleCPU | BPredUnit (pred/) | Optional predictor for accuracy stats |
| Execution tracing | BaseSimpleCPU | ExeTracer (cpu/) | InstRecord for instruction-level trace output |
| System call emulation | SimpleExecContext | Process (sim/) | SE-mode syscall handler invoked on trap instructions |
| CPU switching | AtomicSimpleCPU | BaseCPU | switchOut() / takeOverFrom() for CPU model switching |
Variant Comparison
| Feature | AtomicSimpleCPU | TimingSimpleCPU | NonCachingSimpleCPU |
|---|---|---|---|
| Memory model | Atomic (instant) | Timing (async) | Atomic (no cache) |
| Cache interaction | sendAtomic — bypasses timing | sendTimingReq — full cache timing | Direct to memory bus |
| Simulation speed | Fastest (~10 MIPS) | ~3× slower than Atomic | Similar to Atomic |
| Memory timing accuracy | None (uses access latency estimate) | Cycle-accurate cache behavior | None |
| Typical use | Fast-forward, checkpoint gen, warmup | Memory system studies | Minimal test configs |
| Supports CPU switching | Yes (common fast-forward target) | Yes | Yes |
Directory Map
simple/ source tree
simple/
├── base.hh / base.cc ← BaseSimpleCPU: common fetch-execute loop
├── atomic.hh / atomic.cc ← AtomicSimpleCPU: instant memory model
├── timing.hh / timing.cc ← TimingSimpleCPU: cache-timed memory
├── noncaching.hh / noncaching.cc ← NonCachingSimpleCPU: no cache, direct memory
├── exec_context.hh ← SimpleExecContext: register/memory access
├── probes/ ← Profiling probes
│ ├── simpoint.hh ← SimPoint basic block vector profiling
│ └── looppoint_analysis.hh ← LoopPoint region detection
├── BaseSimpleCPU.py ← Python parameter base class
├── AtomicSimpleCPU.py ← Python Atomic config
├── BaseAtomicSimpleCPU.py ← Python Atomic base params
├── TimingSimpleCPU.py ← Python Timing config
├── BaseTimingSimpleCPU.py ← Python Timing base params
├── NonCachingSimpleCPU.py ← Python NonCaching config
├── BaseNonCachingSimpleCPU.py ← Python NonCaching base params
└── SConscript ← Build definitions
Configuration Parameters
| Parameter | Class | Default | Description |
|---|---|---|---|
clock | All | “1GHz” | CPU clock frequency |
system | All | (required) | Parent System object |
branchPred | BaseSimpleCPU | NULL | Optional branch predictor for accuracy studies |
numThreads | All | 1 | Number of threads (typically 1 for Simple) |
checker | BaseSimpleCPU | NULL | Optional functional checker CPU |
simulate_data_stalls | AtomicSimpleCPU | false | Add data access latency to tick advancement |
simulate_inst_stalls | AtomicSimpleCPU | false | Add instruction fetch latency to tick advancement |
Code Example: AtomicSimpleCPU Tick
// Simplified view of AtomicSimpleCPU::tick() from simple/atomic.cc
void AtomicSimpleCPU::tick()
{
SimpleExecContext &t_info = *threadInfo[curThread];
SimpleThread *thread = t_info.thread;
// Fetch: read instruction bytes at current PC
Tick icache_latency = fetchInstMem();
// Decode: create StaticInst from fetched bytes
preExecute();
// Execute: call StaticInst::execute()
Fault fault = curStaticInst->execute(&t_info, traceData);
// Memory ops use sendAtomic() — completes instantly
// returning access latency for clock advancement
postExecute();
// Advance simulation clock by the greater of
// instruction latency and memory latency
Tick stall_ticks = 0;
if (simulate_data_stalls)
stall_ticks = t_info.dcache_latency;
advanceInst(stall_ticks + icache_latency);
}
Code Example: TimingSimpleCPU Memory Response
// Simplified view of TimingSimpleCPU::completeDataAccess()
bool TimingSimpleCPU::DcachePort::recvTimingResp(PacketPtr pkt)
{
// Called by cache when load/store response arrives
cpu->completeDataAccess(pkt);
return true;
}
void TimingSimpleCPU::completeDataAccess(PacketPtr pkt)
{
SimpleExecContext &t_info = *threadInfo[curThread];
// Complete the memory operation
t_info.thread->setMem(pkt);
// Resume execution: finish instruction, advance PC
postExecute();
advanceInst(Tick(1));
// Schedule next tick — CPU was suspended until now
schedule(tickEvent, curTick() + cyclesToTicks(Cycles(1)));
}
CPU Model Switching
A common gem5 pattern is to use AtomicSimpleCPU for fast-forward, then switch to O3CPU for detailed simulation:
Typical workflow: Boot the OS with AtomicSimpleCPU (fast), take a checkpoint, then restore with O3CPU for detailed analysis. TheswitchOut()andtakeOverFrom()methods in BaseCPU transfer thread state between models seamlessly.