Chapter 9: SystemC CCI

CCI Bridge: Guidelines and Integration

Practical CCI modeler guidelines for parameter ownership, descriptions, callbacks, custom value types, and platform integration.

CCI Bridge: Guidelines and Integration

The IEEE 1666.1 SystemC CCI standard is highly flexible, but its effectiveness depends heavily on models following consistent engineering guidelines. When developing Virtual Platforms or intellectual property (IP) blocks using CCI, adhering to standardized practices ensures that your models can be easily integrated, queried, and managed by top-level configuration tools.

1. Keep Parameters Owned by the Hierarchy

Parameters should not be floating in global scope. They must be declared as part of the sc_core::sc_module that owns the corresponding behavior. This ensures that parameter hierarchical names (e.g., top.memory.size) logically match the model structure.

Avoid creating a giant, monolithic "configuration struct" passed down from the top level, as this defeats the distributed, decentralized nature of CCI parameters.

2. Always Provide Descriptions

Descriptions are not optional in serious, industrial platforms. Tools can extract parameter descriptions via cci_param::get_description() to generate user-facing configuration GUIs, auto-generate markdown documentation, or display help in a command-line interface. Future maintainers need to know the semantic intent of a parameter.

3. Prefer Typed Access Internally

While CCI brokers deal heavily in variant types (cci::cci_value), IP models should use strongly typed access internally for safety and performance:

// Preferred within the module:
uint64_t bytes = memory_size.get_value();

Untyped access (set_cci_value(), get_cci_value()) should be strictly reserved for generic tools, JSON parsers, and infrastructure components that do not know the schema at compile time.

4. Validate Early

Validate configuration values as early as possible—typically in end_of_elaboration() or start_of_simulation()—before the simulation behavior begins depending on them. Examples of critical validations include:

  • Memory size must be a power of two.
  • Timing latency must be strictly greater than zero.
  • Address ranges must not overlap with another target.
  • A boolean feature flag combination must be legally supported by the hardware model.

5. Be Conservative with Custom Value Types

CCI can support user-defined value types, but using them requires registering custom type traits and serialization functions. If a configuration can be expressed using a simple map, list, integer, string, or boolean, strongly prefer the common built-in types. This maximizes compatibility with external tools (like JSON bridges) that only understand standard primitives.

Complete Integration Pattern Example

A platform-level configuration flow in a real Virtual Platform often looks like this:

  1. Register the global broker.
  2. Load configuration file or command-line values (simulated here via presets).
  3. Construct the module hierarchy (parameters consume the presets).
  4. Validate the parameter set (e.g., check for unconsumed presets or invalid values).
  5. Start the simulation.

The following complete, end-to-end sc_main demonstrates this exact integration pattern:

#include <systemc>
#include <cci_configuration>
#include <iostream>
#include <vector>
 
// 1. Parameter Ownership: Parameters belong to the module
class MemoryModel : public sc_core::sc_module {
public:
    // 2. Descriptions: Always provided
    cci::cci_param<uint64_t> size_bytes;
    cci::cci_param<uint32_t> latency_ns;
 
    SC_HAS_PROCESS(MemoryModel);
    MemoryModel(sc_core::sc_module_name name)
        : sc_core::sc_module(name)
        , size_bytes("size_bytes", 1024 * 1024, "Memory size in bytes (must be power of two)")
        , latency_ns("latency_ns", 10, "Access latency in nanoseconds (must be > 0)")
    {
    }
 
    // 4. Validate Early: Check parameters before simulation runs
    void end_of_elaboration() override {
        uint64_t size = size_bytes.get_value(); // 3. Typed access
        uint32_t latency = latency_ns.get_value();
 
        if (latency == 0) {
            SC_REPORT_FATAL("MemoryModel", "Latency must be strictly greater than 0.");
        }
        
        // Simple power-of-two check
        if (size == 0 || (size & (size - 1)) != 0) {
            SC_REPORT_FATAL("MemoryModel", "Memory size must be a power of two.");
        }
 
        std::cout << name() << " initialized successfully with size " 
                  << size << " bytes and latency " << latency << " ns.\n";
    }
};
 
class PlatformTop : public sc_core::sc_module {
public:
    MemoryModel mem;
 
    PlatformTop(sc_core::sc_module_name name)
        : sc_core::sc_module(name)
        , mem("mem") // Instantiates "top.mem.size_bytes" etc.
    {}
};
 
int sc_main(int argc, char* argv[]) {
    // Step A: Register broker
    cci::cci_register_broker(new cci_utils::consuming_broker("Platform_Broker"));
    cci::cci_broker_handle broker = cci::cci_get_broker();
 
    // Step B: Load configuration (e.g., from command line or JSON)
    // We simulate an external tool setting presets here.
    broker.set_preset_cci_value("top.mem.size_bytes", cci::cci_value(2048));
    broker.set_preset_cci_value("top.mem.latency_ns", cci::cci_value(5));
    
    // We deliberately create a typo to demonstrate validation
    broker.set_preset_cci_value("top.mem.latancy_ns", cci::cci_value(20));
 
    // Step C: Construct modules
    PlatformTop top("top");
 
    // Step D: Validate parameter set at the platform level (check unconsumed presets)
    auto unconsumed = broker.get_unconsumed_preset_values();
    if (!unconsumed.empty()) {
        std::cout << "\n--- WARNING: Configuration Typos Detected ---\n";
        for (const auto& preset : unconsumed) {
            std::cout << "Unconsumed preset: " << preset.first << "\n";
        }
        std::cout << "---------------------------------------------\n\n";
    }
 
    // Step E: Start simulation (triggers end_of_elaboration validation internally)
    sc_core::sc_start();
 
    return 0;
}

Following these guidelines ensures that your models remain highly reusable, safely configurable, and easily integrated into larger SystemC Virtual Platforms leveraging the IEEE 1666.1 standard.

Comments and Corrections