---
title: 'AS CPU: Gem5 Atomic CPU Model'
url: https://www.emergentmind.com/topics/atomicsimplecpu-as-cpu
type: topic
---

# AS CPU: Gem5 Atomic CPU Model

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 [2508.18043].

## 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 [2508.18043].

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 [2508.18043].

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:
$$T_{total} = T_{CPU} + T_{Ruby} + T_{other}$$

$$p_{stage} = 100 \times (T_{stage} / T_{total})$$

It also notes the conventional throughput relation
$$CPI = cycles / instructions$$
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 [2508.18043].

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)` [2508.18043].

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` [2508.18043].

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 [2508.18043].

| 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 [2508.18043].

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.

Source: https://www.emergentmind.com/topics/atomicsimplecpu-as-cpu