CCI Mutability, Locking, and Lifecycle
Elaboration-only parameters, simulation-time updates, locks, callbacks, destruction, and safe configuration windows.
CCI Mutability, Locking, and Lifecycle
Not every parameter should be changeable at any time during simulation. The IEEE 1666.1 CCI standard gives you a specific vocabulary and API to enforce mutability rules, protecting model invariants from unsafe runtime modifications.
Mutability Categories
A parameter can be intended for elaboration-time configuration (structural) or simulation-time control (dynamic).
Structural Parameters (Elaboration-Time only):
- RAM size, address widths, or array dimensions.
- If these change after the
sc_core::sc_start()phase begins, memory reallocation might corrupt pointers, or TLM sockets might become invalid.
Dynamic Parameters (Simulation-Time):
- Trace enables, log verbosity levels.
- Throttling delays or error-injection flags.
Enforcing Immutability and Locks
If you do not have a robust mechanism to handle a parameter changing dynamically, you must lock it.
CCI_IMMUTABLE_PARAM: Passed as a template argument during instantiation, making the parameter permanently unchangeable after its initial constructor/preset evaluation..lock(void* pwd): Dynamically locks the parameter at runtime. The parameter cannot be modified unless the caller provides the exact same password pointer (pwd) to.unlock(void* pwd)..is_locked(): Allows tools to query if a parameter is currently accepting changes.
Complete Example: Lifecycle and Safe Configuration Windows
A robust Virtual Platform often implements a "Safe Configuration Window." During this window (typically end_of_elaboration), the top-level module verifies configurations and locks down all structural parameters.
#include <systemc>
#include <cci_configuration>
#include <iostream>
class MemoryController : public sc_core::sc_module {
public:
// Structural parameter: must be locked before simulation starts.
cci::cci_param<int> memory_size;
// Dynamic parameter: can be changed during simulation.
cci::cci_param<bool> enable_debug;
SC_HAS_PROCESS(MemoryController);
MemoryController(sc_core::sc_module_name name)
: sc_core::sc_module(name)
, memory_size("memory_size", 1024)
, enable_debug("enable_debug", false)
{
SC_THREAD(run_controller);
}
void run_controller() {
while(true) {
wait(10, sc_core::SC_NS);
if (enable_debug.get_value()) {
std::cout << "[MemCtrl] Debug tick at " << sc_core::sc_time_stamp() << "\n";
}
}
}
};
class PlatformTop : public sc_core::sc_module {
public:
MemoryController mem_ctrl;
PlatformTop(sc_core::sc_module_name name) : sc_core::sc_module(name), mem_ctrl("mem_ctrl") {}
// Safe Configuration Window: Lock structural parameters before simulation
void end_of_elaboration() override {
std::cout << "--- End of Elaboration: Locking Structural Parameters ---\n";
// We lock 'memory_size' using 'this' as the password pointer.
mem_ctrl.memory_size.lock(this);
if (mem_ctrl.memory_size.is_locked()) {
std::cout << "Success: " << mem_ctrl.memory_size.name() << " is locked.\n";
}
}
};
int sc_main(int argc, char* argv[]) {
// 1. Setup Broker
cci::cci_register_broker(new cci_utils::consuming_broker("Global_Broker"));
cci::cci_broker_handle broker = cci::cci_get_broker();
// 2. Set Presets
broker.set_preset_cci_value("top.mem_ctrl.memory_size", cci::cci_value(2048));
// 3. Instantiate Platform
PlatformTop top("top");
// 4. Start simulation (Triggers end_of_elaboration, locking memory_size)
sc_core::sc_start(15, sc_core::SC_NS);
std::cout << "\n--- Attempting Runtime Configuration ---\n";
// 5. Modifying an unlocked dynamic parameter (Succeeds)
std::cout << "Enabling debug...\n";
top.mem_ctrl.enable_debug.set_value(true);
// 6. Attempting to modify a locked structural parameter (Fails)
try {
std::cout << "Attempting to change memory_size...\n";
// This will throw cci_set_param_failure because it is locked!
top.mem_ctrl.memory_size.set_value(4096);
} catch (const std::exception& e) {
std::cout << "Caught Expected Exception: " << e.what() << "\n";
}
// 7. Modifying a locked parameter with the CORRECT password (Succeeds)
// (Usually this is only done internally by the owner, but shown here for completeness)
top.mem_ctrl.memory_size.unlock(&top);
top.mem_ctrl.memory_size.set_value(4096);
top.mem_ctrl.memory_size.lock(&top);
std::cout << "Successfully updated memory_size using correct password.\n\n";
sc_core::sc_start(20, sc_core::SC_NS);
return 0;
}Destruction Lifecycle
What happens to a parameter when its owning sc_module is dynamically destroyed?
The CCI standard dictates that when a cci_param is destroyed:
- It unregisters itself from its managing broker.
- It immediately invalidates all outstanding
cci_param_handleproxies. - It safely invalidates all registered callbacks.
This lifecycle management guarantees that a long-running external tool (like a GUI) will not crash via segmentation fault if it attempts to query a handle for a hardware block that was hot-unplugged and deleted from the simulation.
Comments and Corrections