Chapter 9: SystemC CCI

Configuration, Control & Inspection (CCI)

Mastering the IEEE 1666.1 CCI Standard for standardizing parameter configuration in complex SystemC SoCs.

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.

The Problem CCI Solves

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:

  1. Define configurable parameters within modules using cci_param.
  2. Set initial values via a central Broker before elaboration.
  3. Lock parameters to prevent accidental modification at runtime.
  4. Track modifications via callbacks.

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.

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.

#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