A CHI-protocol cache-coherent SoC simulator built with simcpp20 coroutines.
The simulator is loosely based on SystemC TLM-2.0 conventions — modules communicate via ports with on-receive callbacks, payloads carry protocol-specific extensions, and a discrete-event scheduler advances simulation time. The substantive departure is using C++20 coroutines rather than SystemC processes, which allows each transaction to be modeled as a top-to-bottom protocol flow rather than a distributed state machine.
- An initiator that issues ReadShared and WriteUniqueFull transactions on a cycle-driven schedule.
- A home agent with a metadata-only cache, handling reads (forward to target on miss, direct response on hit) and writes (absorb locally, install line on data arrival). Each transaction runs as its own coroutine; address serialization is enforced by a TransactionQueue.
- A target that services read requests with a configurable latency.
- An interconnect that routes flits between modules by target id.
CHI transactions terminate correctly: reads via CompAck, writes via combined CompDBIDResp + NCBWrData.
The simulator uses a coroutine-per-transaction model in the home agent. Each incoming REQ spawns a coroutine that runs the full protocol flow top-to-bottom — cache lookup, optional refill, response, completion wait — with co_await at each protocol pause. Address-serialization is enforced by a TransactionQueue: transactions acquire a slot at coroutine entry, and same-address transactions queue behind the active head.
This makes the protocol logic read as the protocol it models, rather than being distributed across callbacks and state-machine maps.
The simulator emits structured events to trace.jsonl (one JSON event per line, crash-safe) and a human-readable trace.txt. The JSONL output is convertible to Chrome Trace format for visualization in Perfetto:
python3 scripts/jsonl_to_perfetto.py trace.jsonl trace.jsonOpen trace.json in https://ui.perfetto.dev. Each transaction appears as a track; per-flit events appear as rows within the track.
Modules expose tunable parameters via a knob system. Each module registers its knobs at construction; values can be set from the command line or a JSON/text config file:
./coroutine_sim --ha0.cache_hit_latency_cycles 5 --ha0.tq_capacity 16
./coroutine_sim --config myconfig.txt
./coroutine_sim --json myconfig.json
./coroutine_sim --help # lists all registered knobsinclude/
csim/ reusable simulation framework
core/ module, system, port, payload, sim_types
utilities/ work_queue, delay_channel, scheduled_event, cache
tracing/ tracer
config/ knob system, ConfigManager
models/ CHI simulation components (HA, RN, SN, interconnect)
protocols/ CHI enum and field definitions
src/
csim/ framework implementations
models/ model implementations
main.cpp top-level simulation setup
test/ unit tests (googletest)
scripts/ utilities (Perfetto trace conversion)
Requires a C++20 compiler and CMake 3.14+.
mkdir build && cd build
cmake ..
cmake --build .
./src/coroutine_simDependencies (simcpp20, magic_enum, nlohmann/json, googletest) are fetched automatically via CMake FetchContent.
The project follows the rules in .clang-format. CI verifies formatting using clang-format 18; using a matching version locally is recommended to avoid spurious diffs.
To check formatting locally:
find include src test -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format --dry-run --Werror {} +To apply formatting in place:
find include src test -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format -i {} +Functional for ReadShared and WriteUniqueFull on a single initiator, with TransactionQueue-based address hazard handling and per-transaction coroutine isolation.
Planned: multiple initiators, ReadUnique / MakeUnique opcodes, snoop modeling (with structured per-peer expectations), DMT (Direct Memory Transfer) variants, eviction modeling, and statistics infrastructure.