CCI Variant Types (cci_value)
Exploring the cci_value class for dynamically-typed configuration, JSON serialization, and user-defined converters.
CCI Variant Types (cci_value)
To create a truly interoperable configuration API, SystemC CCI needs to pass configuration values between arbitrary models, external tools, and text-based parsers without knowing the specific C++ types at compile-time.
To achieve this, the IEEE 1666.1 standard relies heavily on the cci::cci_value class—a variant type capable of holding arbitrarily complex, dynamically typed configuration data.
The cci_value_category
A cci_value object always has a category (cci::cci_value_category) defining the type of data it currently holds. The base categories act as building blocks:
cci::CCI_NULL_VALUE- Uninitialized or explicitly null.cci::CCI_BOOL_VALUE- Boolean.cci::CCI_INTEGRAL_VALUE- Integers (up to 64-bit).cci::CCI_REAL_VALUE- Floating-point numbers.cci::CCI_STRING_VALUE- Text strings.cci::CCI_LIST_VALUE- A heterogeneous array ofcci_valueobjects.cci::CCI_DICT_VALUE(Map) - A dictionary of string keys mapping tocci_valueobjects.
User-Defined Types
CCI seamlessly handles basic C++ types. But what if you have a custom struct representing a coordinate, a MAC address, or an exact packet header that you want to configure as a single parameter?
To support this, you must define a template specialization of cci::cci_value_converter<T>. This converter explicitly tells the CCI library how to pack your C++ struct into a variant cci_value (usually a map/dictionary) and how to unpack it back out. Once defined, your custom type can be serialized to JSON and managed by brokers exactly like native integers or strings.
Complete Example: Values, JSON, and Converters
The following end-to-end example demonstrates how to:
- Define a custom configuration struct.
- Provide a
cci_value_converterto pack and unpack it. - Instantiate a CCI parameter using this custom type.
- Interact with
cci_valuedictionaries and JSON serialization.
#include <systemc>
#include <cci_configuration>
#include <iostream>
#include <string>
// 1. A custom user-defined type
struct NetworkConfig {
std::string ip_address;
int port;
};
// 2. Specialize the converter in the cci namespace
namespace cci {
template<>
struct cci_value_converter<NetworkConfig> {
typedef NetworkConfig type;
// Pack the struct into a cci_value map
static bool pack(cci_value::reference dst, const type& src) {
dst.set_map()
.push_entry("ip", cci_value(src.ip_address))
.push_entry("port", cci_value(src.port));
return true;
}
// Unpack a cci_value map back into the struct
static bool unpack(type& dst, cci_value::const_reference src) {
if (!src.is_map()) return false;
cci_value::const_map_reference m = src.get_map();
if (!m.has_entry("ip") || !m.has_entry("port")) return false;
// Note: CCI does NOT implicitly convert strings to ints.
// We must explicitly extract the right types.
if (!m.at("ip").is_string() || !m.at("port").is_int()) return false;
dst.ip_address = m.at("ip").get_string();
dst.port = m.at("port").get_int();
return true;
}
};
}
// 3. A module using the custom parameter
class Router : public sc_core::sc_module {
public:
cci::cci_param<NetworkConfig> net_config;
SC_HAS_PROCESS(Router);
Router(sc_core::sc_module_name name)
: sc_core::sc_module(name)
, net_config("net_config", {"192.168.1.1", 8080})
{
SC_METHOD(print_status);
}
void print_status() {
NetworkConfig cfg = net_config.get_value();
std::cout << "[Router] Started on IP: " << cfg.ip_address
<< " Port: " << cfg.port << "\n";
}
};
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();
// 4. Working with cci_value natively
// We can manually construct a cci_value dictionary (map) to use as a preset
cci::cci_value preset_val;
cci::cci_value::map_reference vmap = preset_val.set_map();
vmap.push_entry("ip", cci::cci_value("10.0.0.1"));
vmap.push_entry("port", cci::cci_value(9000));
// Set the preset
broker.set_preset_cci_value("router.net_config", preset_val);
// Instantiate module
Router router("router");
// 5. JSON Serialization
// Because we provided the converter, the CCI parameter can automatically
// export its custom type as a JSON string!
cci::cci_value current_val = router.net_config.get_cci_value();
std::string json_str = current_val.to_json();
std::cout << "--- JSON Serialization ---\n";
std::cout << "Router Configuration as JSON: " << json_str << "\n\n";
// 6. JSON Deserialization
// We can also parse JSON directly back into a cci_value, and apply it.
std::string new_json = "{\"ip\": \"127.0.0.1\", \"port\": 443}";
cci::cci_value parsed_val = cci::cci_value::from_json(new_json);
std::cout << "--- Applying new config via JSON ---\n";
router.net_config.set_cci_value(parsed_val);
// Start simulation to see the updated output
sc_core::sc_start(1, sc_core::SC_NS);
return 0;
}Key Takeaways from the LRM
- Strict Type Checking: If you call
.get_int()on acci_valueholding a string (e.g.,"123"), the system will throw an exception. The standard explicitly prohibits implicit type coercion. Always check with.is_int(),.is_string(), etc., before extracting. - Heterogeneous Lists: A
cci_value_listacts conceptually like anstd::vector<cci_value>. A single list can legally contain an integer in index 0, a string in index 1, and a nested map in index 2. - Seamless Tooling: By defining a
cci_value_converter, custom C++ objects instantly gain JSON serialization capabilities and can be interacted with via external tools completely natively.
Comments and Corrections