Chapter 9: SystemC CCI

CCI Brokers and Architecture

Deep dive into SystemC CCI Brokers, handling parameter registration, global vs local brokers, and preset values.

CCI Brokers and Architecture

The SystemC Configuration, Control and Inspection (CCI) standard (IEEE 1666.1) introduces a powerful, standardized mechanism for configuring models. At the heart of this architecture lies the relationship between Parameters and Brokers.

If you think of a parameter as a typed variable bound to a hierarchical name, the broker is the centralized registry that manages its existence, access control, and initial configuration. It is responsible for bridging the gap between the internal SystemC module hierarchy and external tools or top-level configuration scripts.

What is a Broker?

A broker (implementing cci::cci_broker_if) is an object that aggregates parameters. It provides container behaviors such as finding parameters by name, enumerating all known parameters, and managing preset values.

Rather than interacting with the raw broker interface, applications and models use a cci::cci_broker_handle. This handle acts as a proxy, safely routing requests to the underlying broker while injecting cci::cci_originator information that represents the caller.

Global vs Local Brokers

CCI defines a hierarchy of brokers that typically mirrors the sc_core::sc_module hierarchy. The LRM defines three primary layers of broker interaction:

  1. Global Broker: The ultimate fallback. There is exactly one global broker in the simulation, and it sits at the top level, above any sc_module hierarchy. It must be registered before any parameters or local brokers are created. If you fail to register a global broker before instantiating a parameter, the CCI library will enforce a default behavior, but explicit registration is strongly recommended.
  2. Local Brokers: Modules can define their own "local" brokers to hide or manage parameters private to a sub-assembly. By registering a broker to a specific module, all parameters instantiated within that module (and its children) will default to communicating with the local broker.
  3. Automatic Broker Resolution: When a parameter is created inside an sc_module, it registers with the "automatic" broker. The CCI runtime finds this broker by walking up the sc_core::sc_object hierarchy until a local broker is found. If none is found, the global broker takes responsibility.

Initializing Parameters with Presets

One of the most important jobs of a broker is to manage preset values. A preset allows an external tool, testbench, or top-level script to define the value of a parameter before the parameter even exists.

When a parameter is finally constructed by its owning module, it queries its broker. If the broker has a preset value for that parameter's hierarchical name, the parameter adopts the preset value instead of its hardcoded constructor default.

Unconsumed Presets

A common configuration error is a typo in the hierarchical name of a preset value. Because presets are stored in the broker until claimed by a newly constructed parameter, a preset with a typo will simply sit in the broker, unconsumed, while the target parameter silently falls back to its default value.

Brokers provide a way to detect this via get_unconsumed_preset_values(). This is typically checked at the end_of_elaboration() or start_of_simulation() phase to catch typos before runtime.

Complete Example: Brokers and Presets

The following complete, compilable example demonstrates how to set up the global broker, apply preset values, instantiate a module with parameters, and check for unconsumed presets (e.g., due to typos).

#include <systemc>
#include <cci_configuration>
#include <iostream>
 
// A simple module that defines CCI parameters
class MySubsystem : public sc_core::sc_module {
public:
    // CCI Parameters
    cci::cci_param<int> buffer_size;
    cci::cci_param<bool> enable_logging;
 
    SC_HAS_PROCESS(MySubsystem);
    MySubsystem(sc_core::sc_module_name name)
        : sc_core::sc_module(name)
        // Initialize parameters with default values
        , buffer_size("buffer_size", 256, "Size of the internal buffer")
        , enable_logging("enable_logging", false, "Enable verbose logging") 
    {
        SC_METHOD(print_config);
    }
 
    void print_config() {
        std::cout << "[MySubsystem] " << name() << " Configuration:\n"
                  << "  buffer_size = " << buffer_size.get_value() << "\n"
                  << "  enable_logging = " << (enable_logging.get_value() ? "true" : "false") 
                  << std::endl;
    }
};
 
int sc_main(int argc, char* argv[]) {
    // 1. Register the global broker before any modules or parameters are created.
    // cci_utils::consuming_broker is the standard implementation provided by the library.
    cci::cci_register_broker(new cci_utils::consuming_broker("Global_Broker"));
    
    // 2. Obtain a handle to the global broker
    cci::cci_broker_handle global_broker = cci::cci_get_broker();
 
    // 3. Set preset values for parameters that do not exist yet.
    // The hierarchical path includes the module instance name ("subsystem").
    global_broker.set_preset_cci_value("subsystem.buffer_size", cci::cci_value(1024));
    
    // We intentionally introduce a typo here to demonstrate unconsumed presets:
    global_broker.set_preset_cci_value("subsystem.enable_loging", cci::cci_value(true)); // Typo!
 
    // 4. Instantiate the module. 
    // It will consume the "subsystem.buffer_size" preset automatically.
    MySubsystem subsystem("subsystem"); 
 
    // 5. Check for unconsumed presets before starting simulation.
    // This is a crucial step for catching typos in configuration files or scripts.
    std::vector<std::pair<std::string, cci::cci_value>> unconsumed = 
        global_broker.get_unconsumed_preset_values();
        
    for (const auto& preset : unconsumed) {
        SC_REPORT_WARNING("CCI", 
            ("Unconsumed preset value detected for parameter path: " + preset.first).c_str());
    }
 
    // 6. Start simulation
    sc_core::sc_start(1, sc_core::SC_NS);
 
    return 0;
}

Explanation of the Flow

  1. cci_register_broker: Installs a consuming_broker as the global fallback.
  2. set_preset_cci_value: The broker records that if a parameter named "subsystem.buffer_size" is ever created, it should take the value 1024 instead of its hardcoded default.
  3. Instantiation: MySubsystem is instantiated. When buffer_size is constructed, it queries the broker, finds the preset, and sets its initial value to 1024.
  4. Typo Detection: The preset "subsystem.enable_loging" contains a typo. The parameter enable_logging does not match, so it uses its default (false). The typo is caught by calling get_unconsumed_preset_values() and explicitly issuing an SC_REPORT_WARNING.

In the next tutorial, we will look closer at the cci_param class itself and how parameters manage mutability and originators.

Comments and Corrections