LRM Bridge: Elaboration and Simulation Semantics
The SystemC 1666-2023 model of construction, elaboration, initialization, evaluation, update, delta notification, and timed notification.
The IEEE 1666 LRM strictly separates a model's life cycle into elaboration and simulation. This distinction is the key to understanding many API rules and avoiding illegal dynamic structural changes.
Elaboration
Elaboration is where the structural topology becomes known. Modules are constructed, child objects are named, ports and exports are bound, process macros register behavior, and time resolution is set.
You are executing standard C++ constructors, but the SystemC kernel is simultaneously observing and registering the hierarchy. Binding belongs entirely in this phase. If you attempt to treat topology as something that can be freely rewired after simulation starts, you are violating the LRM.
Simulation and the Discrete-Event Scheduler
Simulation begins when control enters the scheduler, normally through sc_core::sc_start(). The LRM describes scheduling through explicit, strictly ordered phases:
- Initialization: Processes are made runnable.
- Evaluation: Runnable processes execute until they yield (
wait()) or finish. - Update: Channels commit pending values (e.g.,
sc_signalwrites). - Delta Notification: Events with zero-time delay are triggered, which may make new processes runnable in the current time. If so, loop back to Evaluation.
- Timed Notification: When no more delta-cycle work remains, the scheduler advances the simulation time to the next scheduled event.
End-to-End LRM Example
This complete example demonstrates elaboration, initialization, delta cycles, and timed notification, strictly following IEEE 1666 semantics.
#include <systemc>
// A module demonstrating initialization and evaluation phases
SC_MODULE(SemanticsDemo) {
sc_core::sc_signal<bool> ready{"ready"};
sc_core::sc_event timed_event;
SC_CTOR(SemanticsDemo) {
// Registered during elaboration
SC_METHOD(initialization_method);
// By default, methods are placed in the runnable queue during initialization.
// We do not call dont_initialize() here.
SC_THREAD(simulation_thread);
// Wait for the ready signal to go true
sensitive << ready.pos();
}
// This method runs automatically at time 0 (Initialization Phase)
void initialization_method() {
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Init/Eval Phase] Method executing." << std::endl;
// Write a value. This schedules an Update request, it does NOT change the value instantly.
ready.write(true);
// Schedule a timed event for 10 ns in the future
timed_event.notify(10, sc_core::SC_NS);
// Prevent it from re-running endlessly
next_trigger(timed_event);
}
void simulation_thread() {
while(true) {
// Wakes up when ready becomes true (which happens in the Delta Update phase)
wait();
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Eval Phase] Thread woke up from ready.pos() delta event." << std::endl;
// Suspend until the timed event fires
wait(timed_event);
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Timed Notification] Thread woke up from timed_event." << std::endl;
}
}
};
int sc_main(int argc, char* argv[]) {
// Elaboration Phase
SemanticsDemo demo("demo");
std::cout << "--- Starting Simulation ---" << std::endl;
// Simulation Phase begins
sc_core::sc_start(20, sc_core::SC_NS);
std::cout << "--- Simulation Finished ---" << std::endl;
return 0;
}Initialization Rules
Before time advances, some processes are placed in the runnable queue. That is why SC_METHOD processes often run at time zero.
For clocked behaviors, you almost always want to prevent this initial time-zero execution:
SC_METHOD(tick);
sensitive << clk.pos();
dont_initialize(); // Crucial to prevent time-zero glitchingWithout dont_initialize(), your "clocked" behavior will execute before the very first clock edge actually occurs.
Delta Cycles vs Timed Notification
Delta cycles let the model settle without advancing physical simulation time. A delta cycle can propagate events from one process to another while sc_time_stamp() remains strictly unchanged. This is not an implementation trick; it is a fundamental pillar of hardware description languages to model concurrency on parallel wires.
Timed Notification occurs only when the delta-event queue is empty. The scheduler jumps time forward to the nearest pending timed event.
Practical Questions to Ask
When debugging LRM-compliant models:
- Is this code executing during a constructor (elaboration) or during
sc_start(simulation)? - Did the process unintentionally initialize at time zero because
dont_initialize()was forgotten? - Is the read value pending an update, or has the update phase already committed it?
- Did the event notify immediately (same evaluation phase), in a delta (next evaluation loop at same time), or at a future time?
Comments and Corrections