AS CPU: Gem5 Atomic CPU Model
- AtomicSimpleCPU is an in-order, single-threaded CPU model that uses synchronous, atomic memory accesses for quick, functionally correct execution.
- It operates via a fixed sequential tick() routine encompassing fetch, decode, execute, and post-execution stages, bypassing event scheduling.
- The model prioritizes speed and correctness over cycle accuracy, making it ideal for warmup, debugging, and first-pass performance assessments.
AtomicSimpleCPU (AS CPU) is one of gem5’s three major CPU models and is characterized as an in-order, single-threaded, sequential model that uses “atomic” memory accesses: requests run to completion within a single, synchronous function call chain, and the CPU proceeds immediately without simulating latency or queues. The model therefore provides functionally correct execution but is not cycle-accurate, and it is typically used for fast functional runs, warmup, debugging, and quick “first-pass” measurements when timing fidelity is unnecessary but correctness and basic interactions with the memory hierarchy, including coherence, are still required (Söderström et al., 25 Aug 2025).
1. Definition and placement within gem5
AS CPU is defined by the absence of temporal decoupling between the core and the memory system. TimingSimpleCPU (TS CPU) and O3CPU decouple the CPU from memory via timing ports and event scheduling, whereas AS does not. In AS, a request caller “owns” the packet; responders modify it and return inline. Because there are no timing events to carry between components, there is no Garnet traversal in AS (Söderström et al., 25 Aug 2025).
This execution model determines both the simulator semantics and the class of studies for which AS CPU is appropriate. The model is intended for scenarios in which fast, functionally correct execution is more important than timing fidelity. A common misconception is that “atomic” implies negligible simulator overhead. The anatomical analysis instead shows that atomicity means synchronous completion within a single call chain, not the absence of substantial work in address validation, controller logic, or memory access handling.
2. Sequential execution model and top-level time partition
The entry point of the model is AtomicSimpleCPU::tick(), which is called every simulated cycle and drives four sequential stages: fetch, preExec, execute, and postExec. At the simulator level, gem5’s top-level simulate() calls doSimLoop(), which repeatedly dequeues events from EventQueue::serviceOne(). AS CPU’s per-cycle work then occurs in AtomicSimpleCPU::tick() without waiting on memory responses (Söderström et al., 25 Aug 2025).
The reported time partition for AS CPU is strongly asymmetric. Instruction handling dominates overall execution time, with fetch consistently the largest share for nearly all workloads. preExec and postExec form the remaining instruction preparation and commit slices. Dynamic instruction execution (D-inst) is typically equal to or smaller than fetch; SPEC lbm is the notable outlier in which D-inst dominates. Static instruction work (S-inst) and miscellaneous CPU or gem5 infrastructure are minority shares.
The analysis summarizes total and per-stage costs using the following quantities:
It also notes the conventional throughput relation
and states that, because AS is non-pipelined and atomic, CPI is shaped by the aggregate cost per tick of fetch, decode, execute, and commit, together with the function-level time spent in Ruby versus CPU stages.
3. Per-instruction call chain
A single dynamic instruction traverses a fixed sequential path. In the fetch stage, the CPU emits an instruction-fetch request and returns with instruction bytes through the atomic path. The principal CPU-side functions are AtomicSimpleCPU::tick() and RequestPort::sendAtomic(). In Ruby, the corresponding call chain is RubyPort::MemResponsePort::recvAtomic(), Port::isPhysMemAddr(), AbstractController::mapAddrToMachine(), AbstractController::recvAtomic(), and then AbstractMemory access for address validation, operation-type checks, data copy, and the read path. With 3 GB memory, Port::isPhysMemAddr accounts for slightly more than half of Ruby-port time and recvAtomic is second; as memory increases, recvAtomic becomes the largest Ruby-port component and isPhysMemAddr shrinks proportionally (Söderström et al., 25 Aug 2025).
In preExec, instruction bytes are prepared and decoded into a decoded object. The key functions are moreBytes, decoder::process, decoder::decode, fetchRomMicroop, and updateNPC. The decoder is organized as a finite-state machine with states including Reset, Prefix, 1-byte opcode, 2-byte opcode, ModRM, SIB, displacement, immediate, proc-Opcode, getNextByte, and Done. Reset is the most expensive state across GAPBS, PARSEC, and SPEC because it accesses the internal DecodeCache as a page-chunk address map and performs bitfield work with heavy use of BitfieldType. ModRM and opcode states are secondary hotspots, whereas Immediate and SIB tend to be lighter.
In execute, the dominant x86 instruction classes are loads and stores across GAPBS, PARSEC, and SPEC. The key functions are readMemAtomic() and writeMemAtomic(), which dispatch through RequestPort::sendAtomic() for data access. For Ldbig and St, nearly all time is spent in Ruby’s atomic handling rather than in CPU-side instruction logic. TLB and MMU behavior follow the same pattern: X86::TLB::translateAtomic and Lookup are the fast path, while misses invoke TLB::Walker through startWalk and stepWalk, and the walker’s time is dominated by waiting on Ruby during page-table fetches.
In postExec, the CPU performs accounting and probe notification. The main functions are postExecute, countCommitInst, and probeInstCommit. Most post-execution time goes to commit-related work, especially countCommitInst and probeInstCommit, and core scaling has negligible impact because the commit path is sequential.
Typical source locations identified in the analysis include src/cpu/simple/atomic.cc or atomic.hh for AtomicSimpleCPU::tick(), readMemAtomic(), and writeMemAtomic(); src/mem/port.hh for RequestPort::sendAtomic(); src/mem/ruby/ports/MemResponsePort.hh/cc for recvAtomic(); src/mem/ruby/system/AbstractController.* for recvAtomic() and mapAddrToMachine(); src/mem/abstract_mem.* for memory access; src/arch/x86/mmu/tlb.hh/cc for translateAtomic() and walker functions; src/arch/x86/decoder.* for decode functions and FSM states; and src/cpu/base.* for commit accounting and probes.
4. Interaction with the Ruby memory subsystem
The atomic request path in AS CPU is:
CPU → RequestPort::sendAtomic() → RubyPort::MemResponsePort::recvAtomic() → Port::isPhysMemAddr() → AbstractController::mapAddrToMachine() → AbstractController::recvAtomic() → AbstractMemory access (Addr, OpType, Read/Write, Response) (Söderström et al., 25 Aug 2025).
Instruction fetch is especially important because the instruction bytes route through the I-side memory port into Ruby, where cache controllers validate and serve the line, still atomically in AS, and return data immediately through the call chain. This path explains why Ruby consistently accounts for the largest share of execution time in AS CPU. Even though there is no event scheduling and no Garnet traversal, each atomic request still traverses address validation and mapping, L1/L2 controller logic and coherence state checks in MESI_Two_Level, and data movement into the packet.
The most prominent Ruby-port bottlenecks are Port::isPhysMemAddr and MemResponsePort::recvAtomic. At 3 GB, isPhysMemAddr is slightly above 50% of Ruby-port time; with larger memories, recvAtomic surpasses isPhysMemAddr, and the overall Ruby-port share increases with memory size. In the memory access layer, AbstractMemory::Addr and OpType are prominent due to address and operation validation, while Read, Write, and Response account for the remainder.
The MMU/TLB path reinforces the same conclusion. During page walks, the walker is Ruby-bound rather than MMU-code-bound, and workloads such as GAPBS bfs_raw spend much of translation time in the walker waiting on Ruby. The paper’s educational framing therefore uses AS CPU to show that “atomic” does not imply “cheap”: the simulator can still spend a substantial fraction of runtime inside coherence and mapping machinery.
5. Profiling basis, workloads, and observed behavior
The empirical results were obtained with a lightweight profiler built on Linux’s perf_event interface through the perf_event_open syscall. Sampling was performed at a 1000 ms interval, collecting bottom-up callchains into nested JSON with counts. The study reports 458 one-hour runs in full-system mode for x86_64, producing callstack.json for each configuration. The host platform was Ubuntu 22.04.4, kernel 6.5.0-28-generic, Intel i9-12900K, and 128 GB RAM. The gem5 build used the fast binary with debug symbols for function names, and the memory system and ISA were Ruby MESI_Two_Level and x86_64 (Söderström et al., 25 Aug 2025).
The benchmark suites were GAPBS (bc_raw, bfs_raw, cc_raw, cc_sv_raw, pr_raw, pr_spmv_raw, tc_raw), PARSEC-3.0 (blackscholes, bodytrack, dedup, facesim, ferret, fluidanimate, freqmine, raytrace, streamcluster, swaptions, vips), and SPEC2017 (bwaves, cactuBSSN, exchange2, gcc, imagick, lbm, leela, mcf, nab, omnetpp, perlbench, x264, xalancbmk). Core configurations were 1, 4, and 16 for GAPBS and PARSEC, and 1 for SPEC. Memory configurations included 3 GB, 8 GB, and 16 GB, mixed across suites.
Across suites, loads and stores dominate execution time. In GAPBS, LdBig, LdstBig, and Ldfp dominate, with cc_raw showing relatively more St. In PARSEC, blackscholes and raytrace are homogeneous and Ldfp-heavy, while dedup is more diverse. SPEC exhibits a mix of Ld, LdstBig, Ldfp, and St, but remains load/store dominated. Increasing memory modestly reduces Reset and ModRM shares in the decoder, which the paper interprets as likely improved locality within the decoder’s emi and DecodeCache; by contrast, increasing core count has minor effect because the FSM path is inherently sequential.
6. Comparison with TimingSimpleCPU and O3CPU
AS CPU is best understood relative to gem5’s other two major CPU models. TS CPU and O3CPU both use timed interfaces and event scheduling, while AS CPU uses synchronous atomic completion. The consequence is not merely a difference in fidelity, but a difference in where simulator time is spent (Söderström et al., 25 Aug 2025).
| CPU model | Memory interaction | Dominant reported time tendencies |
|---|---|---|
| AS CPU | Atomic, inline, no Garnet traversal | Fetch largest; Ruby dominates, especially i-fetch |
| TS CPU | Timing ports, event scheduling, Garnet on misses | Ruby dominates; I-tick often larger than D-tick |
| O3CPU | Timing ports with out-of-order pipeline stages | IEW dominates; fetch second; Ruby fraction smaller |
For TS CPU, the top level is Ruby-dominated, and I-tick is often larger than D-tick; complete_i-fetch accounts for most i-tick time. The fetch path proceeds through BaseMMU::translateTiming, FetchTranslation::finish, RequestPort::sendTimingReq, MemResponsePort::recvTimingReq, the sequencer, and Ruby response handling through hit callbacks. On L1 misses, the network path includes Garnet router and network interface activity; on hits, L1 cache-controller finite-state-machine actions such as ifetch-hit, load-hit, and store-hit are prominent. Ruby hitback time is concentrated in send_I/D_back, PacketQueue::schedSendTiming, QueuedResponsePort::schedTimingResp, PacketQueue::schedSendEvent, and event-queue scheduling.
For O3CPU, the top-level profile is dominated by the IEW stage, with fetch second and a relatively smaller fraction in Ruby than in AS or TS. Fetch time is spent primarily in building dynamic instructions through buildInst and the dynamic-instruction constructor rather than in Ruby. Decoder and branch predictor costs remain significant, and pipelineIcacheAccesses calls the TLB, but fetch returns quickly after issuing I-cache requests and performs substantial work when the cache response arrives. In IEW execution, loads and stores dominate, with LSQ read or write and completeDataAccess or writeback paths as the key functions, and Ruby or Garnet engaged for memory operations.
The comparison shows that AS CPU’s bottlenecks are not simply a reduced version of TS or O3 bottlenecks. TS adds scheduling overhead and network traversal when timing is enabled, whereas O3 shifts a larger share of simulator time into dynamic-instruction construction and pipeline stages. AS remains dominated by fetch and Ruby-side atomic handling.
7. Optimization directions and analytical significance
The paper identifies several optimization directions grounded in the observed bottlenecks. For Ruby fast paths in atomic mode, it proposes a specialized, minimal-coherence path for atomic instruction-fetch requests that bypasses heavy mapping and coherence checks when safe. It also proposes reducing per-request overhead in recvAtomic by streamlining packet validation and memory mapping for common-case loads and stores, such as cache-line-aligned accesses that hit in private L1. These suggestions target the major AS hotspots at the Ruby port (Söderström et al., 25 Aug 2025).
For the decoder FSM, the analysis recommends expanding or optimizing DecodeCache indexing with larger or more cache-friendly structures and prefetching decode-page chunks to minimize Reset cost and bitfield manipulation. It also recommends inline or SIMD-friendly bitfield operations to reduce overhead in prefix, opcode, and ModRM handling. Since Reset is the single most expensive decode step, the expected effect is concentrated on the dominant CPU-side bottleneck.
For MMU and TLB behavior, the proposed remedies are larger TLBs and hugepages when OS and full-system configuration permit, together with larger page sizes in full-system images for AS runs. These changes aim to reduce walker activation and therefore Ruby-bound page-walk time. For the memory subsystem, the paper states that, if timing is not needed, Classic memory can be considered instead of Ruby for AS runs because the Classic path is simpler and can significantly reduce simulator overhead relative to Ruby’s coherence machinery. Within Ruby MESI_Two_Level, tuning L1 sizes, associativity, and line sizes is suggested to improve instruction-fetch hit rates and reduce deeper controller work even in atomic mode.
Further suggestions include batching small instruction fetches into cache-line prefetches within AS fetch, caching validated physical mappings for instruction fetch to avoid repeated Port::isPhysMemAddr and mapAddrToMachine work on the same page or line, and deferring or aggregating probeInstCommit notifications and countCommitInst updates to reduce post-execution overhead.
The broader significance of the analysis lies in its “anatomical view” of functions, FSMs, ports, and controllers. In AS CPU, that view makes explicit that much of the simulator’s time is spent in Ruby’s address validation and controller path despite the inline completion of requests. In TS CPU, it exposes a timed, event-driven fetch path dominated by scheduling and hit callbacks. In O3CPU, it shows that pipeline stages and dynamic-instruction construction occupy the bulk of runtime. This suggests that AS CPU is not merely a fast surrogate for more detailed models, but a distinct execution regime whose dominant costs must be analyzed at the level of concrete call chains and component interactions.