Chapter 9: SystemC CCI

CCI Callbacks, Originators, and Tools

Pre-write and post-write callbacks, originator tracking, validation, introspection tools, and safe parameter observers.

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.

CCI Callbacks, Originators, and Tools

Callbacks allow a SystemC model to react when a CCI parameter changes. Originators tell the model who requested the access.

Together, they elevate CCI from a simple global variable table into a robust API capable of supporting complex Electronic Design Automation (EDA) tool flows, secure auditing, and trace generation.

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 Role of Originators

The IEEE 1666.1 standard requires every parameter modification to carry a cci::cci_originator.

  • If a parameter is changed directly by a module in the SystemC hierarchy, the originator is automatically determined to be the current sc_core::sc_object.
  • If an external tool (e.g., a Python script, a GUI, or a CLI parser) modifies a parameter via a handle, the tool can explicitly create a named originator (e.g., cci_originator("Debug_GUI")) and pass it to the setter function.

This allows models to log exactly who changed a configuration, or even reject writes from unprivileged originators in secure simulations.

Under the Hood: The cci_originator Constructor

In the C++ reference implementation, when you omit the cci_originator argument in set_value() or get_value(), the library dynamically constructs a default originator on the stack.

Inside the default constructor of cci_originator, it probes the SystemC kernel directly:

  1. It queries sc_core::sc_get_current_process_handle(). If the caller is running inside an SC_THREAD or SC_METHOD, it captures the exact hierarchical name of the active process.
  2. If the simulation is not running (e.g., during elaboration or sc_main), it falls back to inspecting the current active module scope via sc_core::sc_get_curr_simcontext()->hierarchy_curr().
  3. It stores a constant reference to this string or object pointer internally.

Because it is constructed extremely frequently (on every parameter access), cci_originator is optimized as a lightweight proxy class. It avoids std::string heap allocations internally, storing only pointers to existing string literals or sc_object instances already maintained by the SystemC kernel.

Complete Example: Tool Tracking via Originators and Callbacks

The following complete, compilable example demonstrates how to create custom originators, pass them through parameter handles, and use an untyped callback to generate an audit log of all configuration changes in the system.

#include <systemc>
#include <cci_configuration>
#include <iostream>
 
// 1. A global audit logger using an untyped callback
void audit_log_callback(const cci::cci_param_write_event<void>& ev) {
    std::cout << "[AUDIT] Parameter '" << ev.param_handle.name() 
              << "' changed:\n"
              << "        Old Value : " << ev.old_value.to_json() << "\n"
              << "        New Value : " << ev.new_value.to_json() << "\n"
              << "        Changed by: " << ev.originator.name() << "\n";
}
 
class NetworkInterface : public sc_core::sc_module {
public:
    cci::cci_param<std::string> mac_address;
 
    SC_HAS_PROCESS(NetworkInterface);
    NetworkInterface(sc_core::sc_module_name name)
        : sc_core::sc_module(name)
        , mac_address("mac_address", "00:00:00:00:00:00", "Device MAC")
    {}
 
    // A method simulating internal hardware behavior changing the parameter
    void reset_mac_hardware() {
        std::cout << "\n--- Triggering internal hardware reset ---\n";
        // When set_value is called internally, the originator defaults to this sc_module.
        mac_address.set_value("FF:FF:FF:FF:FF:FF");
    }
};
 
int sc_main(int argc, char* argv[]) {
    // Register broker
    cci::cci_register_broker(new cci_utils::consuming_broker("Global_Broker"));
    cci::cci_broker_handle broker = cci::cci_get_broker();
 
    // Instantiate module
    NetworkInterface eth0("eth0");
 
    // 2. Attach the audit logger to the parameter
    cci::cci_param_untyped_handle h_mac = broker.get_param_handle("eth0.mac_address");
    h_mac.register_post_write_callback(&audit_log_callback);
 
    // 3. Simulating an external CLI tool changing the value
    std::cout << "\n--- Simulating External CLI Tool ---\n";
    {
        // Explicitly create an originator representing the tool
        cci::cci_originator cli_originator("Command_Line_Parser");
        
        // Pass the originator into the handle's set function
        h_mac.set_cci_value(cci::cci_value("0A:1B:2C:3D:4E:5F"), cli_originator);
    }
 
    // 4. Simulating a GUI tool changing the value
    std::cout << "\n--- Simulating External GUI Configurator ---\n";
    {
        // Explicitly create a different originator
        cci::cci_originator gui_originator("Eclipse_Debug_GUI");
        
        h_mac.set_cci_value(cci::cci_value("11:22:33:44:55:66"), gui_originator);
    }
 
    // 5. Simulating the hardware modifying itself
    eth0.reset_mac_hardware();
 
    return 0;
}

Why Originators Matter for Tooling

In a 100-million-gate SoC simulation, a parameter like top.cpu0.cache.disable_prefetch might be modified by:

  1. The firmware writing to a memory-mapped register (Originator: top.cpu0.iss).
  2. A TCL script executing during elaboration (Originator: TCL_Script_Engine).
  3. A backend coverage tool enabling trace (Originator: Coverage_Tool).

When debugging why the simulation performance suddenly dropped, the audit log powered by originators is the only way to prove definitively who disabled the prefetcher.

Introspection Tools

Because all parameters are registered centrally, you can build powerful introspection tools. A standard configuration dump tool will iterate over broker.get_param_handles(), querying each handle for:

  • name()
  • get_cci_value().to_json()
  • get_description()
  • is_locked()
  • get_value_origin().name()

This single block of introspection code can dynamically generate HTML documentation or a comprehensive JSON dump for any standard-compliant VP, without requiring custom APIs from IP vendors.

Comments and Corrections