Chapter 9: SystemC CCI

CCI Parameters and Mutability

Understanding cci_param, default values, mutability locks, and tracking parameter originators.

CCI Parameters and Mutability

In the CCI standard, a parameter is a named instance of a specific compile-time type that registers itself with a broker. Parameters are the primary interface for module configuration, effectively replacing traditional C++ constructor arguments, struct-based config objects, or macro definitions.

The highest-level interface for a parameter is cci::cci_param_if, but in practice, you will instantiate concrete parameters using cci::cci_param_typed<T> (or its convenient alias cci::cci_param<T>).

Parameter Instantiation

A parameter requires a name and a default value. It can optionally take a description and a scoping rule (cci::cci_name_type). When instantiated, the parameter immediately queries its managing broker. If a preset value was registered in the broker for this parameter's exact hierarchical name, the parameter initializes itself with the preset value. Otherwise, it falls back to the default value provided in the constructor.

Mutability and Locking

According to IEEE 1666.1, not all configuration parameters should be modifiable during simulation. For instance, hardware structure (like cache sizes or bus widths) is typically fixed at elaboration. Modifying these values dynamically during the simulation phase could lead to undefined behavior in your SystemC logic (e.g., trying to resize a dynamically allocated array after simulation has started).

CCI provides two primary mechanisms to protect parameters from illegal modification: Constant Mutability and Runtime Locking.

1. Constant Mutability (CCI_IMMUTABLE_PARAM)

You can declare a parameter as immutable at compile-time by passing cci::CCI_IMMUTABLE_PARAM as a template argument. An immutable parameter takes its initial value (either from a preset or its default) and permanently rejects any subsequent modification attempts.

Any call to set_value() on an immutable parameter after its initial construction will result in an exception (specifically, a cci_set_param_failure error or similar diagnostic depending on the broker).

2. Runtime Locking

Alternatively, you can leave the parameter mutable but dynamically lock it at runtime using a password pointer. This is ideal for read-only status parameters published by a module: the module holds the "key" to unlock and update the parameter, while external observers can only read it.

If an external tool tries to set_value() on a locked parameter without providing the exact password pointer used to lock it, the write is rejected.

Value Origin Tracking (cci_originator)

In large virtual platforms, it can be difficult to track who modified a parameter. Was it set by a top-level script? A backdoor debug tool? Or another SystemC module?

CCI solves this through the cci_originator. Whenever a parameter is created or its value is updated, an originator is recorded.

  • If a parameter is updated from within the SystemC module hierarchy, the originator is automatically the sc_core::sc_object from which the call originated.
  • If updated from an external tool, the originator is usually a string name (e.g., "Debug_GUI").

You can query the origin of a parameter's current value via get_value_origin(). You can also determine if the current value is the default or if it was overridden using is_preset_value() and is_default_value().

Complete Example: Mutability and Originators

This complete, compilable example demonstrates immutable parameters, runtime locking, and originator tracking.

#include <systemc>
#include <cci_configuration>
#include <iostream>
 
class CacheConfig : public sc_core::sc_module {
public:
    // 1. Immutable Parameter: Set at creation, cannot be changed later.
    cci::cci_param<int, cci::CCI_IMMUTABLE_PARAM> cache_size;
 
    // 2. Mutable Parameter with runtime locking: used for status reporting.
    cci::cci_param<std::string> status_msg;
 
    SC_HAS_PROCESS(CacheConfig);
    CacheConfig(sc_core::sc_module_name name)
        : sc_core::sc_module(name)
        , cache_size("cache_size", 256, "Size of the cache in KB")
        , status_msg("status_msg", "Initializing", "Current state of the cache")
    {
        // Lock the status parameter using 'this' as the password pointer
        status_msg.lock(this);
 
        SC_THREAD(cache_behavior);
    }
 
    void cache_behavior() {
        wait(10, sc_core::SC_NS);
        
        // Update the status by unlocking, setting the value, and relocking
        status_msg.unlock(this);
        status_msg.set_value("Active");
        status_msg.lock(this);
 
        wait(10, sc_core::SC_NS);
        
        status_msg.unlock(this);
        status_msg.set_value("Idle");
        status_msg.lock(this);
    }
};
 
int sc_main(int argc, char* argv[]) {
    // Register the global broker
    cci::cci_register_broker(new cci_utils::consuming_broker("Global_Broker"));
    cci::cci_broker_handle broker = cci::cci_get_broker();
 
    // Set a preset value for the immutable parameter before instantiation
    broker.set_preset_cci_value("cache.cache_size", cci::cci_value(1024));
 
    // Instantiate the module
    CacheConfig cache("cache");
 
    // Inspect parameter origin and mutability
    std::cout << "--- Pre-Simulation Inspection ---\n";
    
    // Check if cache_size took the preset or default
    if (cache.cache_size.is_preset_value()) {
        std::cout << cache.cache_size.name() << " is using a preset value: " 
                  << cache.cache_size.get_value() << "\n";
    }
 
    // Attempting to modify an immutable parameter will throw an exception
    try {
        cache.cache_size.set_value(2048);
    } catch (const std::exception& e) {
        std::cout << "Expected Exception caught: Cannot modify immutable parameter.\n"
                  << "  -> " << e.what() << "\n";
    }
 
    // Attempting to modify a locked parameter with the wrong password will fail
    try {
        // We try to use nullptr as the password, but it was locked with 'this' (cache ptr)
        cache.status_msg.set_value("Hacked", nullptr);
    } catch (const std::exception& e) {
        std::cout << "Expected Exception caught: Cannot modify locked parameter without correct password.\n"
                  << "  -> " << e.what() << "\n";
    }
 
    std::cout << "\n--- Starting Simulation ---\n";
    
    // Trace the origin and value over time
    for (int i = 0; i < 3; ++i) {
        cci::cci_originator orig = cache.status_msg.get_value_origin();
        std::cout << "Time " << sc_core::sc_time_stamp() << ": " 
                  << cache.status_msg.name() << " = " << cache.status_msg.get_value() 
                  << " (Set by originator: " << orig.name() << ")\n";
                  
        sc_core::sc_start(10, sc_core::SC_NS);
    }
 
    return 0;
}

Understanding Metadata

Beyond core configuration values, parameters can also carry arbitrary metadata. This is a map of string keys to cci::cci_value types (a variant type capable of holding strings, ints, lists, etc.) that provide additional context for UI tools, such as acceptable ranges, display units, or documentation links.

// Example of attaching metadata to a parameter
cache_size.add_metadata("unit", cci::cci_value("KB"));
cache_size.add_metadata("max_limit", cci::cci_value(8192));

In the next tutorial, we will explore the cci_value class in depth to understand how CCI handles variant, dynamically-typed configuration values.

Comments and Corrections