CCI Variant Types (cci_value)
Exploring the cci_value class for dynamically-typed configuration, JSON serialization, and user-defined converters.
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.
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.
Standard and source context
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 cleanly 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.
Under the Hood: The cci_value AST and JSON Engine
In the Accellera reference implementation, cci_value is effectively an Abstract Syntax Tree (AST) node for configuration data. Internally, a cci_value object contains a tagged union (or pointer-to-implementation structure) that stores the actual payload alongside its cci_value_category enum. For primitive types like integers and booleans, the value is stored inline. For complex types like lists and maps, it stores pointers to heap-allocated std::vector<cci_value> or std::map<std::string, cci_value>.
When you call cci_value::to_json() or cci_value::from_json(), the Accellera reference implementation historically embeds a lightweight, high-performance JSON library (like RapidJSON) under the hood. The to_json method recursively traverses the cci_value AST, stringifying the tags according to strict JSON syntax.
The cci_value_converter<T> works via compile-time template instantiation. When you call param.set_value(my_struct), the C++ compiler resolves to your explicit pack() template specialization, actively marshalling the struct's fields into the dynamically allocated cci_value dictionary AST before passing it to the broker. This isolates the expensive string manipulation/JSON operations exclusively to external tooling, while internal parameter accesses compile down to native C++ assignments.
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.
Can you answer these clearly?
Keep moving when you can answer each question without looking back at the lesson.
Comments and Corrections