simple

simple — Atomic and Timing Simple CPUs

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.

3
CPU Variants
18
Source Files
4K
Lines of Code
Execution Model

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:

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:

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

ClassHeaderResponsibility
BaseSimpleCPUsimple/base.hhBase class with common fetch–execute loop, branch prediction, tracing, and thread management. Not instantiated directly.
AtomicSimpleCPUsimple/atomic.hhAtomic memory model. Overrides readMem() and writeMem() with sendAtomic() calls. Advances clock by access latency.
TimingSimpleCPUsimple/timing.hhTiming memory model. Uses sendTimingReq() and recvTimingResp() callbacks. Suspends CPU until memory response.
NonCachingSimpleCPUsimple/noncaching.hhAtomicSimpleCPU variant that bypasses caches, connecting directly to memory bus.
SimpleExecContextsimple/exec_context.hhExecContext implementation for Simple CPUs. Provides register read/write and memory access methods to StaticInst::execute().
SimpleThreadcpu/simple_thread.hhPer-thread architectural state: register file, PC, and miscellaneous registers. Used by all Simple (and Minor) CPU models.
BPredUnitcpu/pred/bpred_unit.hhOptional 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

InteractionSimple ComponentExternal ModuleMechanism
Atomic memory accessAtomicSimpleCPUCache/Memory (mem/)sendAtomic() — synchronous, returns latency
Timing memory accessTimingSimpleCPUCache (mem/)sendTimingReq() / recvTimingResp() callback
Instruction decodeBaseSimpleCPUDecoder (arch/)InstDecoder::decode() → StaticInst
TLB translationBaseSimpleCPUMMU (arch/)MMU::translateAtomic() or translateTiming()
Branch predictionBaseSimpleCPUBPredUnit (pred/)Optional predictor for accuracy stats
Execution tracingBaseSimpleCPUExeTracer (cpu/)InstRecord for instruction-level trace output
System call emulationSimpleExecContextProcess (sim/)SE-mode syscall handler invoked on trap instructions
CPU switchingAtomicSimpleCPUBaseCPUswitchOut() / takeOverFrom() for CPU model switching

Variant Comparison

FeatureAtomicSimpleCPUTimingSimpleCPUNonCachingSimpleCPU
Memory modelAtomic (instant)Timing (async)Atomic (no cache)
Cache interactionsendAtomic — bypasses timingsendTimingReq — full cache timingDirect to memory bus
Simulation speedFastest (~10 MIPS)~3× slower than AtomicSimilar to Atomic
Memory timing accuracyNone (uses access latency estimate)Cycle-accurate cache behaviorNone
Typical useFast-forward, checkpoint gen, warmupMemory system studiesMinimal test configs
Supports CPU switchingYes (common fast-forward target)YesYes

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

ParameterClassDefaultDescription
clockAll“1GHz”CPU clock frequency
systemAll(required)Parent System object
branchPredBaseSimpleCPUNULLOptional branch predictor for accuracy studies
numThreadsAll1Number of threads (typically 1 for Simple)
checkerBaseSimpleCPUNULLOptional functional checker CPU
simulate_data_stallsAtomicSimpleCPUfalseAdd data access latency to tick advancement
simulate_inst_stallsAtomicSimpleCPUfalseAdd 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. The switchOut() and takeOverFrom() methods in BaseCPU transfer thread state between models seamlessly.