Tools and libraries — what we use and why¶
Quick guide to every external piece of software the project relies on. Written for someone who knows architecture but hasn't worked deeply with the C++ ecosystem or the DBI/tracing world. Each section says: what it is, why it's the choice we made, what it would feel like to use it from scratch.
CMake (build system)¶
What it is¶
A meta-build tool: you write one CMakeLists.txt describing your
project ("here are the source files, here are the dependencies") and
CMake generates the actual build files for whatever build system your
machine has — make on Linux, ninja if you have it, MSVC .sln
files on Windows, Xcode projects on macOS. The same CMakeLists.txt
works everywhere; you just tell CMake which generator you want.
CMake is the de facto standard for any modern C++ project that needs to build on more than one platform. Alternatives like Bazel or Meson exist; CMake won by inertia and ecosystem.
Why we use it¶
- Cross-platform. Linux + macOS CI on every push, no hand-rolled Makefile-per-OS.
FetchContent. Lets us pull external libraries directly from their git repos at configure time, with a known-good commit pinned. We don't need to vendor source code or rely on the user having package managers likevcpkgorconanset up. Three external libraries (CLI11, nlohmann/json, Catch2) all come down this way. See cmake/Dependencies.cmake.- Targets. Each subsystem (
casim_cache,casim_predictor,casim_common, etc.) is a CMake target with its own include paths and dependencies. The top-levelsimbinary just lists the targets it links against — no manual archive-flag wrangling. - Presets. CMakePresets.json defines named
configurations (
default,release,asan,ci). One command:cmake --preset default && cmake --build --preset default. Nocd build && cmake -DCMAKE_BUILD_TYPE=... -G Ninja ...
One-line summary¶
"Tells your compiler what to compile, on whatever OS or compiler you happen to have."
CLI11 (command-line argument parsing)¶
What it is¶
A header-only C++ library for parsing command-line flags. You write:
CLI::App app{"sim - my simulator"};
std::filesystem::path config_path;
app.add_option("--config", config_path, "JSON config")->required();
CLI11_PARSE(app, argc, argv);
…and you get parsed flags, error messages on bad input, auto-generated
--help output. Validation (->check(CLI::PositiveNumber),
->check(CLI::ExistingFile)) is built in.
Why we use it¶
- No
argvparsing by hand. Project2's CLI usedgetoptwith a growingcasestatement; project1's accepted no flags at all. Both approaches break the moment you add the 30-flag config sweep we'd need. - Header-only. One
FetchContentline, no separate library to manage. - Auto-generated
--help. Falls out for free.
CLI11 is used in src/common/cli.cpp (the main sim CLI), tools/gen_trace/main.cpp, and tools/proj2_to_champsim/main.cpp.
One-line summary¶
"Handles
--flag valuestyle argument parsing without you having to write the parser."
nlohmann/json (JSON parsing)¶
What it is¶
The de facto JSON library for modern C++. You write nlohmann::json j
= /* parse from file */; and operate on the result with j["key"],
j.at("key").get<int>(), etc. It also supports custom types — define
to_json and from_json overloads for your struct and j.get<MyStruct>()
just works.
Why we use it¶
- JSON-driven configs. src/common/config.cpp
defines
from_jsonfor every config struct (SimConfig,CacheLevelConfig,PredictorConfig, etc.). Loading a config is literallyj.get<SimConfig>(). - Round-trippable. Same struct → JSON → struct gives identical output. Makes it trivial to dump the merged config (after CLI overrides) for the user to inspect.
- Header-only. Same
FetchContentstory as the others.
The only mild downside: nlohmann/json is slow compared to RapidJSON or simdjson. For config files that's irrelevant — we parse one tiny JSON file at startup.
One-line summary¶
"Parses JSON files into C++ structs."
Catch2 (unit testing framework)¶
What it is¶
A C++ testing framework. You write tests like:
TEST_CASE("SaturatingCounter walks the Smith state diagram", "[counter]") {
SaturatingCounter c(2, 1);
REQUIRE(c.is_weak());
c.update(true);
REQUIRE(c.is_taken());
}
…and Catch2 discovers them, runs them, prints colorized pass/fail with
expression rewriting (with expansion: 0 == 1 instead of just failed).
The SECTION { ... } mechanism lets you group related assertions in a
test case while resetting state between sections.
Why we use it¶
- Header-only and zero-config. One
#include, oneCatch2WithMainlink, you're done. - CTest integration.
catch_discover_tests(target)inCMakeLists.txtregisters every Catch2 test case as an individual CTest target.ctest --preset defaultruns them all and reports pass/fail counts. - Sections. Lets per-test-case setup happen once but assertions
branch into multiple scenarios — useful for the predictor tests
where we share a
cfgacross "all-taken", "alternating", "period-5" variants.
Alternative was Google Test (gtest); Catch2 wins on lower friction
for a single-engineer hobby project.
Tests live under tests/. At the end of Phase 3 there are 62 passing tests.
One-line summary¶
"Lets you write
REQUIRE(expression)insideTEST_CASEs and reports which ones fail."
ChampSim trace format¶
Already covered in detail in 02-phase1-traces.md. Key facts:
- Binary format — one fixed-size C struct per instruction record,
packed, little-endian, optionally
xz-compressed. - Fields:
ip,is_branch,branch_taken, fixed-size arrays of destination/source register IDs and memory addresses. - Two record-shape variants —
input_instr(the SPEC default, ~64 B) andcloudsuite_instr(for IPC-1 server traces, ~56 B). - De facto standard for trace-driven academic comparch research.
Repository: github.com/ChampSim/ChampSim. Methodology paper: arXiv:2210.14324.
Why we adopted it: free interoperability with the existing public trace corpora (DPC-3, CRC-2, IPC-1, CVP-1).
One-line summary¶
"The on-disk format every research-grade trace-driven simulator in this niche reads."
DynamoRIO (planned tracer, Path A)¶
What it is¶
A dynamic binary instrumentation (DBI) framework. Lets you run an unmodified compiled binary while intercepting every basic block of machine code at runtime. You can read, modify, or count every load, store, and branch as it happens — without touching source code, without recompiling, without a debugger.
DynamoRIO ships with a built-in trace collector called drmemtrace
that records exactly the (PC, opcode, mem_addr, branch_outcome) stream
that microarchitectural simulators need. Run the program once under
DynamoRIO, get a multi-GB trace file out, replay it through the
simulator forever after.
License: BSD. Cross-platform: Linux, macOS, Windows. Architectures: x86, x86-64, AArch64. Actively maintained at Google.
Repository: github.com/DynamoRIO/dynamorio. Trace format spec: drcachesim format.
How DBI works (intuition)¶
When you launch a program normally, the OS loads the binary's machine
code into memory and the CPU executes it directly. When you launch
the same program through drrun, DynamoRIO inserts itself between the
program and the CPU:
- DynamoRIO copies a basic block of the program's machine code into its own "code cache".
- It modifies the copy — for
drmemtrace, that means inserting a small bit of instrumentation that records every load, store, or branch. - The modified copy executes on the CPU. The program never knows.
- When control flow leaves the basic block, DynamoRIO catches it and instruments the next block.
The entire program runs through this filter. The cost is ~5-50× slower than native execution, but the recorded stream is faithful to a real binary on a real ISA.
Why we want it (and why it's deferred)¶
For Phase 5 (multi-core coherence) and Phase 6 (publishable results), we need real workload traces — SPEC, multithreaded micro-benchmarks, maybe a subset of a real application. Synthetic traces stop being convincing.
DynamoRIO drmemtrace is the modern, open-source way to get those
traces. The plan (described in
02-phase1-traces.md §"Path A") is:
drrun -t drcachesim -offline -- ./my_program args
tools/tracer/drmem2champsim out.champsimtrace drmemtrace.*.zip
A small post-processor (drmem2champsim, not yet written) converts
DynamoRIO's native trace into our canonical ChampSim binary format.
It's deferred because Phase 3 didn't need it: synthetic + project2-derived traces were enough for predictor cross-validation.
One-line summary¶
"Lets you record a (PC, mem-addr, branch-outcome) trace from any compiled binary without modifying it."
Intel Pin (compatibility tracer, Path B)¶
What it is¶
The other big DBI framework. Older than DynamoRIO, originally from Intel Research, still widely used in academic comparch work because it's been the default for so long.
License: proprietary EULA, free binary download. Cross-platform: Linux + Windows, primarily x86. Less actively developed than DynamoRIO.
ChampSim's official tracer is a Pintool — the file
tracer/champsim_tracer.cpp in the ChampSim repo
is what produced the original DPC-3 / CRC-2 trace corpora. If we want
to re-trace SPEC binaries we already have, the simplest path is to
run that exact Pintool, not to re-implement the tracing logic.
Why we plan to vendor it¶
For two reasons:
- Some platforms install Pin more cleanly than DynamoRIO. A teaching-machine where Pin is already set up is a better path than bringing up DynamoRIO from scratch.
- The DPC-3 / CRC-2 corpora were generated by this exact tool. Bit-for-bit reproducibility of public trace files means using the same tracer that produced them.
We won't reimplement the Pintool — we'll vendor (git-submodule or
pinned tarball) the upstream champsim_tracer.cpp under
tools/tracer/pin_champsim/ and add a thin CMake
glue to build it on Pin-friendly platforms.
Status: deferred alongside the DynamoRIO path.
One-line summary¶
"The legacy DBI framework used to generate the public ChampSim trace corpora. We'll vendor its tracer for compatibility."
Public trace corpora¶
These are the trace collections we plan to plug in once the
DynamoRIO/Pin paths are wired. We don't have to host any of them; they
live on third-party servers and we just pull representative ones into
traces/ for local runs.
| Corpus | What | Host |
|---|---|---|
| DPC-3 | SPEC CPU2006 + CPU2017 memory-intensive subset | dpc3.compas.cs.stonybrook.edu |
| CRC-2 | Same SPEC mix, used for the cache replacement championship | (linked from CRC-2 page) |
| IPC-1 | Server / front-end-heavy: Cassandra, Drupal, Kafka, MySQL | research.ece.ncsu.edu/ipc/rules-criteria |
| CVP-1 → ChampSim | Qualcomm's 135 small + 2013 large traces, ported by Feliu et al. | zenodo:14045186, Feliu IISWC 2023 |
If a reviewer ever asks "but how does your simulator do on real workloads?", these are the answer.
GitHub Actions (CI)¶
What it is¶
Free CI/CD service that runs jobs in a fresh VM every time you push.
Configuration lives in a YAML file under .github/workflows/.
Why we use it¶
.github/workflows/ci.yml is set up to
build the project and run the test suite on Linux and macOS for every
push. Catches "works on my machine" regressions — anything that
compiles only because my local toolchain has weird headers will fail
in CI immediately.
Free for public repos, generous quota for private. Costs nothing to keep running.
One-line summary¶
"A robot that builds and tests your project on every push so you notice breakage before merging."
Quick reference: which tool covers which need¶
| Need | Tool | Where |
|---|---|---|
| Compile across Linux/macOS | CMake + presets | CMakeLists.txt, CMakePresets.json |
Parse --flag arguments |
CLI11 | src/common/cli.cpp |
| Load JSON config files | nlohmann/json | src/common/config.cpp |
| Write unit tests | Catch2 (+ CTest discovery) | tests/ |
| On-disk trace format | ChampSim binary spec | docs/trace-format.md |
| Trace from a real binary (Linux/macOS, modern) | DynamoRIO drmemtrace + drmem2champsim |
(Phase 5; not yet written) |
| Trace from a real binary (Linux, legacy) | Intel Pin + ChampSim Pintool | (Phase 5; not yet vendored) |
| Synthesize a small trace for tests | tools/gen_trace |
tools/gen_trace/ |
| Convert project2 text trace → ChampSim | tools/proj2_to_champsim |
tools/proj2_to_champsim/ |
| Run tests on every push | GitHub Actions | .github/workflows/ci.yml |
If you're trying to answer "how do I do X with this codebase?", look up X in the left column.