Browse by type
Ramulator 2.1 is a modern, modular, and extensible cycle-level DRAM simulator. It is the successor of Ramulator 1.0 [Kim+, CAL'16] and a major overhaul of Ramulator 2.0 [Luo+, CAL'23]. The goal of Ramulator 2.1 is to enable rapid and agile implementation and evaluation of design changes in the memory controller and DRAM to meet the increasing research effort in improving the performance, security, and reliability of memory systems. Ramulator 2.1 features a clean and modular C++ codebase with automatically generated Python wrappers that enables easy and scriptable configurations and extensions. Users can focus just on the C++ code that implements modeling and simulation logic without having to worry about manually maintaining boilerplate code.
Ramulator 2.1 can either be used as a standalone simulator that takes memory traces, or be easily integrated into other simulators as a DRAM and memory controller simulation library. We currently provide gem5 wrappers that works as a drop-in component for both SE and FS mode (tested with gem5 v25.1, FS mode tested with a post-boot checkpoint).
This Github repository contains the public version of Ramulator 2.1. From time to time, we will synchronize improvements of the code framework, additional functionalities, bug fixes, etc. from our internal version. Ramulator 2.1 welcomes your contribution as well as new ideas and implementations in the memory system.
Currently, Ramulator 2.1 provides the DRAM device and memory controller models for the following standards: - DDR3, DDR4, DDR5 - GDDR6 - LPDDR5 (with WCK2CK sync and expiry tracking and tAAD deadline aware scheduling for separate ACT1 and ACT2) - HBM1, HBM2, HBM3, HBM4 (Row and column command dual-issue and pseudochannels)
What has changed from Ramulator 2.0: - Aggregated bug fixes - More comprehensive support for newer DRAM & controller features - More comprehensive sets of test and validation workflows - Significantly improved the ease of use, configuration, and extension - Overall code quality improvements
src/
C++ code for main simulator implementation.python/
Python wrappers for easy and scriptable configuration of Ramulator.examples/
Ready-to-run example configurations and traces.tests/
Tests and validation workflows.resources/gem5_wrappers/
Reference wrapper code for gem5 integration.visualizer/
Browser-based trace visualizer (Nuxt/WebGL). See Section 10.We highly recommend to use our container (Dockerfile available at .devcontainer/Dockerfile) to avoid any dependency issues. The repository is also configured to be able to be one-click opened as a Dev Container with all the dependencies already installed. The easiest way to start using and developing Ramulator 2.1 is to directly create a Codespace on the GitHub page of the Ramulator 2.1 repository. If you are using Visual Studio Code, it should automatically prompt you to reopen the repository in a Dev Container after you clone and open the repo for the first time.
If you want to set up the container locally, you can do the following steps:
docker compose up -d --build
docker compose exec ramulator2 bash
Doing so creates a container with all the dependencies, mounts the Ramulator 2.1 repository at /workspace, and automatically activates ramulator2-venv in the container bash.
If you need to configure your own environment, please refer to Section 2.3 for detailed instructions.
From the repository root:
mkdir -p build
cd build
cmake ..
make -j
cd ..
This default build does three useful things:
libramulator.so in the repository rootpython/ramulator/Then run Ramulator 2.1 in standalone mode with an example configuration:
python3 examples/example_config.py
You should see some example statistics being printed. You can head to Section 3 directly for detailed explanations and instructions on how to use and configure Ramulator 2.1 if you do not need to build Ramulator 2.1 in your custom environment.
Required:
g++-12 or clang++-15Auto-fetched by CMake (no manual install):
yaml-cpp — YAML configuration parsingfmt — C++ print formattingnanobind — Python-C++ bindings (only when RAMULATOR_PYTHON_BINDINGS=ON)argparse — command-line argument parsing (vendored in ext/)Python dev dependencies (install with pip install -r requirements-dev.txt):
pytest — test frameworkmatplotlib — latency-throughput plottingruff — Python linter and formattersetuptools >= 64 is required as the build backend and is pulled in automatically by pip install -e ..
Optional: clang-format.
To build Ramulator 2.1 in standalone mode:
mkdir -p build
cd build
cmake ..
make -j
cd ..
If you only want the pure C++ library without Python bindings for your own simulator:
mkdir -p build
cd build
cmake .. -DRAMULATOR_PYTHON_BINDINGS=OFF
make -j
cd ..
After building, install the Python package in editable mode so that python -m ramulator and import ramulator work from any directory:
pip install -e .
Then run examples directly:
python3 examples/example_config.py
If you prefer not to install the python package, you can also set PYTHONPATH=python as a one-off alternative:
PYTHONPATH=python python3 examples/example_config.py
examples/example_config.py looks like the following:
"""Example Ramulator2 configuration and simulation script"""
import ramulator
# Configure the simulation frontend that sends memory requests
frontend = ramulator.frontend.SimpleO3(
clock_ratio=8,
traces=["./examples/traces/example_inst.trace"],
num_expected_insts=500000,
translation=ramulator.translation.NoTranslation(max_addr=2147483648),
)
# Create DRAM configuration
ddr4 = ramulator.dram.DDR4(org_preset="DDR4_8Gb_x8", timing_preset="DDR4_2400R", rank=1)
# Instantiate the memory controller with the DRAM configuration
ctrl = ramulator.controller.GenericDDR(
dram=ddr4,
scheduler=ramulator.scheduler.FRFCFS(),
refresh_manager=ramulator.refresh_manager.AllBank(),
row_policy=ramulator.row_policy.Open(),
addr_mapper=ramulator.addr_mapper.RoBaRaCoCh(),
)
# Create a memory system with the controller
mem = ramulator.memory_system.GenericDRAM(
clock_ratio=3,
controllers=[ctrl],
channel_mapper=ramulator.channel_mapper.CacheLineInterleave(),
)
# Run the simulation
sim = ramulator.Simulation(frontend, mem)
sim.run()
# sim.stats returns a nested Python dict of all simulation statistics
stats = sim.stats
# Guard here for `ramulator export`, which captures the configuration without
# running the simulation.
if stats:
# Controller stats are under memory_system → controller
ctrl_stats = stats["memory_system"]["controller"]
print(f"Controller cycles: {ctrl_stats['cycles']}")
print(f"Avg read latency: {ctrl_stats['avg_read_latency']:.1f} cycles")
print(f"Read requests: {ctrl_stats['num_read_reqs']}")
print(f"Write requests: {ctrl_stats['num_write_reqs']}")
print(f"Row hits: {ctrl_stats['row_hits']}")
print(f"Row misses: {ctrl_stats['row_misses']}")
print(f"Row conflicts: {ctrl_stats['row_conflicts']}")
There are two top-level components in Ramulator 2.1:
frontend
generates memory traffic. In this example it is a simple out-of-order core model driven by a memory instruction trace.memory_system
encapsulates one or more memory controllers (channel). Each controller owns a DRAM device model.Any Ramulator 2.1 simulation must contain these two components. They are used to create the main simulation object (ramulator.Simulation(frontend, mem)) used as the entry point of the simulation.
frontend = ramulator.frontend.SimpleO3(
clock_ratio=8,
traces=["./examples/traces/example_inst.trace"],
num_expected_insts=500000,
translation=ramulator.translation.NoTranslation(max_addr=2147483648),
)
The frontend generates memory requests and sends them to the memory system. In this example, SimpleO3 models a simple Out-of-Order processor with an LLC. It reads one or more instruction trace files, each corresponds to a processor core. The simulation will run until the number of retired instructions has reached 500000. The configured fontend will not apply address translation to the memory addresses in the trace (i.e., ramulator.translation.NoTranslation). clock_ratio=8 means that for every x memory ticks (i.e., memory system side clock_ratio=x), the frontend will be ticked 8 times.
ddr4 = ramulator.dram.DDR4(
org_preset="DDR4_8Gb_x8",
timing_preset="DDR4_2400R",
rank=2,
)
This includes:
rank=2. You can append as many overrides as you want.If you want to understand what this object turns into at runtime, section 9.3 walks through the full DRAM device model and the hierarchical state machine behind it.
ctrl = ramulator.controller.GenericDDR(
dram=ddr4,
scheduler=ramulator.scheduler.FRFCFS(),
refresh_manager=ramulator.refresh_manager.AllBank(),
row_policy=ramulator.row_policy.Open(),
addr_mapper=ramulator.addr_mapper.RoBaRaCoCh(),
)
This configures a GenericDDR memory controller for our just configured ddr4 DRAM. It has an FRFCFS (First-Ready First-Come-First-Served) scheduler, an all-bank refresh, an Open row policy, and a RoBaRaCoCh address mapper.
mem = ramulator.memory_system.GenericDRAM(
clock_ratio=3,
controllers=[ctrl],
channel_mapper=ramulator.channel_mapper.CacheLineInterleave(),
)
GenericDRAM is a thin top-level wrapper around one or more controllers. It contains a clock_ratio that sets the memory-side tick rate, a list of controllers (controllers=[...]), and a channel mapper (channel_mapper=...) that decides which memory requests goes to which controller. clock_ratio=3 means that for every y frontend ticks (i.e., front side clock_ratio=y), the memory system will be ticked 3 times. Currently, GenericDRAM requires all its memory controllers to have the same frequency.
The example prints a few key numbers from sim.stats after the simulation finishes:
Controller cycles: 81302
Avg read latency: 45.2 cycles
Read requests: 6
Write requests: 0
Row hits: 3
Row misses: 3
Row conflicts: 0
The exact numbers depend on the workload and configuration.
sim.stats is a nested Python dict with two top-level keys: "frontend" and "memory_system". The most useful counters live under stats["memory_system"]["controller"]:
| Stat | Meaning |
|---|---|
cycles |
Controller cycles |
avg_read_latency |
Average read latency in controller cycles |
num_read_reqs |
Read requests accepted |
num_write_reqs |
Write requests accepted |
row_hits |
Total row hits |
row_misses |
Total row misses |
row_conflicts |
Total row conflicts |
read_queue_len_avg |
Average read queue occupancy |
write_queue_len_avg |
Average write queue occupancy |
total_num_read_requests |
Total reads accepted by the memory system (one level up, under stats["memory_system"]) |
total_num_write_requests |
Total writes accepted by the memory system |
With a single channel, stats["memory_system"]["controller"] is a dict. With multiple channels it becomes a list of dicts (one per channel), each with an "id" field (e.g., "Channel 0").
The Ramulator Python package exposes the major components as a set of namespaces:
ramulator.frontendramulator.dramramulator.controllerramulator.schedulerramulator.refresh_managerramulator.row_policyramulator.addr_mapperramulator.channel_mapperramulator.translationramulator.controller_pluginramulator.memory_systemYou can swap DDR4 for another standard by replacing the DRAM object and, when needed, the controller class.
Examples:
dram = ramulator.dram.DDR5(org_preset="DDR5_8Gb_x8", timing_preset="DDR5_4800AN")
ctrl = ramulator.controller.GenericDDR(dram=dram, ...)
dram = ramulator.dram.LPDDR5(org_preset="LPDDR5_8Gb_x16", timing_preset="LPDDR5_5500")
ctrl = ramulator.controller.LPDDR5(dram=dram,...)
```python dram = ramulator.dram.HBM2(org_p
$ claude mcp add ramulator2 \
-- python -m otcore.mcp_server <graph>