Chapter 9: SystemC CCI

Custom User-Defined Types (UDT) in CCI

How to safely wrap your custom hardware C++ types into cci_value and register them globally using SystemC CCI.

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.

Custom User-Defined Types in CCI

While SystemC Configuration Control Interface (CCI) provides native serialization for built-in C++ and SystemC types (like int, std::string, sc_time), you will often need to expose complex struct-based configurations to the broker. This requires bridging your User-Defined Type (UDT) with cci_value.

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 cci_value Bridge

To use a custom struct with a cci_param, you must define how that struct converts to and from a cci_value. The Accellera CCI implementation uses a template traits mechanism (cci_param_traits). By implementing the cci_value_converter template specialization, the CCI parameter registry will dynamically synthesize conversion calls when parsing broker data.

#include <systemc>
#include <cci_configuration>
#include <string>
 
// 1. Define your custom hardware configuration struct
struct DMAConfig {
    unsigned int base_address;
    bool enable_scatter_gather;
    std::string channel_mode;
    
    // Equality operator required by CCI for tracking parameter value changes
    bool operator==(const DMAConfig& o) const {
        return base_address == o.base_address && 
               enable_scatter_gather == o.enable_scatter_gather &&
               channel_mode == o.channel_mode;
    }
};
 
// 2. Specialize cci_value_converter inside the cci namespace
namespace cci {
    template<>
    struct cci_value_converter<DMAConfig> {
        
        // Pack: UDT -> cci_value
        // cci_value is internally an AST. We tell it to become a map (JSON object)
        static bool pack(cci_value::reference dst, const DMAConfig& src) {
            dst.set_map()
               .push_back("base_address", cci_value(src.base_address))
               .push_back("enable_scatter_gather", cci_value(src.enable_scatter_gather))
               .push_back("channel_mode", cci_value(src.channel_mode));
            return true;
        }
 
        // Unpack: cci_value -> UDT
        // Safely extract from the AST back into C++ memory
        static bool unpack(DMAConfig& dst, cci_value::const_reference src) {
            if (!src.is_map()) return false;
            
            if (src.has_entry("base_address")) 
                dst.base_address = src.get_entry("base_address").get_int();
            if (src.has_entry("enable_scatter_gather"))
                dst.enable_scatter_gather = src.get_entry("enable_scatter_gather").get_bool();
            if (src.has_entry("channel_mode"))
                dst.channel_mode = src.get_entry("channel_mode").get_string();
                
            return true;
        }
    };
} // namespace cci
 
// 3. Use it in a module
SC_MODULE(DMACtrl) {
    cci::cci_param<DMAConfig> cfg;
 
    SC_CTOR(DMACtrl) 
      : cfg("cfg", DMAConfig{0x0000, false, "SINGLE"}) 
    {
        SC_METHOD(print_cfg);
    }
    
    void print_cfg() {
        DMAConfig val = cfg.get_value();
        std::cout << "DMA initialized at 0x" << std::hex << val.base_address << std::endl;
    }
};
 
int sc_main(int argc, char* argv[]) {
    cci::cci_register_broker(new cci_utils::broker("global_broker"));
    DMACtrl dma("dma");
    
    // Override from global broker using JSON
    cci::cci_get_broker().set_preset_cci_value("dma.cfg", 
        cci::cci_value::from_json("{\"base_address\": 4096, \"enable_scatter_gather\": true, \"channel_mode\": \"BURST\"}"));
        
    sc_core::sc_start();
    return 0;
}

Behind the Scenes: The RapidJSON AST

When cci_value::from_json() is called, the Accellera CCI proof-of-concept uses an embedded version of the RapidJSON library to parse the string.

It constructs a cci_value, which acts as a variant tree (an Abstract Syntax Tree of configuration). The cci_broker_if (the global configuration registry) stores this raw AST. The broker has zero knowledge of your DMAConfig struct.

Later, when your module instantiates cci::cci_param<DMAConfig> cfg, the parameter constructor queries the broker for the path "dma.cfg". The broker returns a reference to the cci_value AST node. The template parameter triggers the compiler to locate cci_value_converter<DMAConfig>::unpack(). This static function bridges the untyped JSON tree into your strongly typed C++ memory space, allowing the simulation to proceed securely with zero dynamic type-checking overhead during execution.

Comments and Corrections