Configuration, Control & Inspection (CCI)
Mastering the IEEE 1666.1 CCI Standard for standardizing parameter configuration in complex SystemC SoCs.
How to Read This Lesson
CCI is about making configuration explicit and inspectable. Read every parameter as part of the platform contract, not just a convenient variable.
Configuration, Control & Inspection (CCI)
As SystemC models grow from simple IP blocks into massive, multi-core System-on-Chip (SoC) virtual platforms, configuring them becomes a monumental task.
Historically, designers used C++ constructor arguments, #define macros, or custom text-file parsers to configure things like memory sizes, clock speeds, or debug verbosity. This fragmented ecosystem meant IP from Company A couldn't be configured by the same tools as IP from Company B.
To solve this, Accellera standardizes configuration via the IEEE 1666.1 Configuration, Control, and Inspection (CCI) standard.
Source and LRM Trail
For CCI, start with Docs/LRMs/SystemC_CCI_1_0_LRM.pdf. Then inspect .codex-src/cci/configuration/src/cci for cci_param, cci_broker_if, cci_value, originators, callbacks, and the consuming broker. The practical question is always: who owns this value, when may it change, and how can tools inspect it?
The Problem CCI Solves & The Accellera Architecture
Imagine a virtual platform integrating an ARM processor and a custom memory controller. How does the user configure the memory size without modifying the underlying C++ IP source code?
CCI provides a unified API to:
- Define configurable parameters within modules using
cci_param. - Set initial values via a central Broker before elaboration.
- Lock parameters to prevent accidental modification at runtime.
- Track modifications via callbacks.
Under the Hood (Accellera cci repository):
The architecture centers around the cci_broker_if abstract interface and its default implementation, cci_core_broker.
When cci_get_broker() is called, it queries the cci_broker_manager singleton, which walks the sc_core::sc_object hierarchy tree to find the nearest broker. If none is explicitly set for a sub-hierarchy, the global fallback broker is used.
Furthermore, how does CCI store values of varying types dynamically? Through cci_value. In the source code, cci_value is essentially a type-safe JSON Abstract Syntax Tree (AST). It uses a union-like structure (or std::variant equivalents in newer standards) combined with string mappings to support booleans, numbers, strings, lists, and dictionaries. This allows external JSON configuration files to seamlessly map into typed cci_param<T> instances.
Complete CCI Setup Example
The following end-to-end example demonstrates how to set up a Global Broker, configure preset values, and instantiate an IP block that reads those parameters.
#include <systemc>
#include <cci_configuration>
// A typical IP block using CCI parameters
SC_MODULE(MemoryController) {
// cci_param wraps the standard types for broker integration
cci::cci_param<int> mem_size;
cci::cci_param<bool> debug_mode;
SC_CTOR(MemoryController)
// Parameters are named and provided with default values and descriptions
: mem_size("mem_size", 1024, "Size of the memory in bytes")
, debug_mode("debug_mode", false, "Enable debug tracing")
{
SC_METHOD(do_memory_op);
// Example: Lock the parameter so it cannot be changed dynamically during simulation
mem_size.lock();
}
void do_memory_op() {
if (debug_mode.get_value()) {
std::cout << "@" << sc_core::sc_time_stamp()
<< " [MemoryController] Operating on memory of size: "
<< mem_size.get_value() << " bytes" << std::endl;
}
}
};
// Top-level module encapsulating the IP
SC_MODULE(TopModule) {
MemoryController mem_ctrl;
SC_CTOR(TopModule) : mem_ctrl("mem_ctrl") {}
};
int sc_main(int argc, char* argv[]) {
// 1. Get the global broker instance
cci::cci_broker_handle broker = cci::cci_get_broker();
// 2. Set parameters BEFORE the modules are instantiated.
// The hierarchical name must match exactly: "top.mem_ctrl.mem_size"
broker.set_initial_preset_value("top.mem_ctrl.mem_size", cci::cci_value(4096));
broker.set_initial_preset_value("top.mem_ctrl.debug_mode", cci::cci_value(true));
// 3. Instantiate the top module.
// The MemoryController inside will now initialize its cci_params using the broker's preset values!
TopModule top("top");
std::cout << "Starting simulation with configured CCI parameters..." << std::endl;
// Start simulation
sc_core::sc_start(10, sc_core::SC_NS);
return 0;
}Advanced LRM Details: Callbacks and Locking
Locking Parameters
According to IEEE 1666.1, a parameter can be locked using lock(). Once locked, any subsequent attempt to call set_value() on that parameter will throw a CCI error. This is crucial for parameters that define the physical topology of the hardware (e.g., bus widths, memory sizes) which physically cannot change after elaboration. In the implementation, locking simply sets an internal boolean flag m_is_locked in the cci_param_untyped base class, which is strictly verified before invoking the write operation.
Parameter Callbacks
CCI allows you to register callbacks for parameter lifecycle events (creation, destruction, value changes). This is heavily utilized by GUI tools to dynamically update property views. In the source code, cci_param maintains an internal vector of cci_callback_untyped_handle. When set_value() executes, it iterates over these handles, dynamically invoking the registered function pointers.
#include <systemc>
#include <cci_configuration>
void on_debug_changed(const cci::cci_param_write_event<bool>& ev) {
std::cout << ">>> CCI Event: debug_mode changed from "
<< ev.old_value << " to " << ev.new_value << std::endl;
}
SC_MODULE(CallbackDemo) {
cci::cci_param<bool> debug_mode;
SC_CTOR(CallbackDemo) : debug_mode("debug_mode", false) {
// Registering a post-write callback
debug_mode.register_write_callback(&on_debug_changed);
SC_THREAD(modifier_thread);
}
void modifier_thread() {
wait(10, sc_core::SC_NS);
debug_mode.set_value(true); // Triggers the callback
}
};
int sc_main(int argc, char* argv[]) {
CallbackDemo demo("demo");
sc_core::sc_start(20, sc_core::SC_NS);
return 0;
}By conforming to IEEE 1666.1, your models ensure interoperability across all standard SystemC virtual platforms, eliminating proprietary setup files.
Comments and Corrections