The Execution Phases
A deep dive into the SystemC execution model: Elaboration, Initialization, Evaluation, Update, and advancing time.
How to Read This Lesson
These core semantics are where experienced SystemC engineers earn their calm. We will name the scheduler rule, then show how the source enforces it.
The Execution Phases
The SystemC Language Reference Manual (IEEE 1666) defines a strict execution model that governs how your C++ code behaves like parallel hardware. Understanding these phases is the key to mastering SystemC.
Under the Hood: C++ Implementation in Accellera SystemC
To understand how a sequential C++ program simulates parallel hardware, look at the core of the Accellera systemc repository (src/sysc/kernel/sc_simcontext.cpp):
sc_simcontext: This is the heart of the SystemC kernel. It is a global singleton object that manages the simulation time, the hierarchy tree (sc_object_manager), and the event queues.- The Runnable Queue (
sc_runnable): When an event triggers, thesc_simcontextpushes the sensitivesc_process_b(the base class for threads and methods) onto thesc_runnablequeue. The evaluation phase is literally awhile (!runnable->is_empty())loop that pops processes and executes them. - Context Switching: How does
sc_core::wait()work? Accellera SystemC uses user-level coroutines (historically QuickThreads, now often ucontext or POSIX threads depending on the OS). When a thread callswait(), the C++ execution context (registers, stack pointer) is saved, and control yields back to thesc_simcontextevaluation loop. - The Update Queue: Primitive channels (like
sc_signal) inherit fromsc_prim_channel. When you write a new value, the channel callsrequest_update(). This simply pushes a pointer to the channel onto them_update_listarray insidesc_simcontext. During the update phase, the kernel loops over this array and calls the virtualupdate()method on each channel.
Standard and source context
1. Elaboration Phase
Before the simulation clock even starts ticking, SystemC builds the hierarchy.
- Module Instantiation: The constructors (
SC_CTORor custom constructors) of allsc_modulederived classes are executed. - Port Binding: Ports (
sc_in,sc_out,sc_port) are bound to channels or interfaces. - Process Registration:
SC_METHOD,SC_THREAD, andSC_CTHREADare registered with the kernel.
[!WARNING] You cannot bind ports after the elaboration phase has completed. Once
sc_start()is called, the hierarchy is locked.
2. Initialization Phase
When sc_start() is invoked, the kernel enters initialization:
- Every registered
SC_METHODandSC_THREADis executed exactly once, unlessdont_initialize()was explicitly called on it during elaboration. - The processes run until they yield (e.g., hit a
wait()) or return.
3. The Evaluation Phase
This is the core of the simulation loop.
- The scheduler selects a runnable process from the runnable queue.
- The process executes. If it writes to a signal (
sc_signal::write), the new value is not immediately visible. Instead, the signal is added to the update queue. - The evaluation phase continues until the runnable queue is entirely empty.
4. The Update Phase
Once all processes have suspended, the kernel processes the update queue.
- All primitive channels (like
sc_signal) apply their pending writes. The "next" value becomes the "current" value. - If the value changed, any process sensitive to that channel's events is pushed back onto the runnable queue.
5. Delta Cycles
If the update phase caused new processes to become runnable, the kernel returns to the Evaluation Phase without advancing the simulation time. This loop (Evaluate -> Update -> Evaluate) is called a Delta Cycle. It allows zero-delay combinational logic to settle deterministically.
6. Advancing Time
If the runnable queue and update queue are both empty, the kernel looks at the event queue for pending timed events (e.g., wait(10, SC_NS)).
- The simulation time is advanced to the timestamp of the earliest pending event.
- The processes waiting on that event are moved to the runnable queue.
- The cycle repeats.
Complete Execution Example
Below is a complete, compilable sc_main example that demonstrates the elaboration, initialization, evaluation, and update phases in action.
#include <systemc>
#include <iostream>
SC_MODULE(phase_demo) {
sc_core::sc_signal<int> sig;
SC_CTOR(phase_demo) : sig("sig") {
std::cout << "1. Elaboration Phase: Constructor running." << std::endl;
SC_THREAD(stimulus_thread);
// We do NOT use dont_initialize() here, so it runs during initialization.
SC_METHOD(monitor_method);
sensitive << sig;
dont_initialize(); // Only run when 'sig' actually changes
}
void stimulus_thread() {
std::cout << "2. Initialization Phase: Thread running for the first time." << std::endl;
// Wait for some time to pass
sc_core::wait(10, sc_core::SC_NS);
std::cout << "3. Evaluation Phase (@" << sc_core::sc_time_stamp()
<< "): Thread writing to signal." << std::endl;
// This schedules an update, but does not change the value immediately
sig.write(42);
std::cout << " Immediate read after write (old value): " << sig.read() << std::endl;
// Yield to allow the update phase and subsequent delta cycle to run
sc_core::wait(sc_core::SC_ZERO_TIME);
std::cout << "6. Evaluation Phase (Next Delta): Value is now updated to: "
<< sig.read() << std::endl;
}
void monitor_method() {
std::cout << "5. Evaluation Phase (Delta Cycle): Monitor triggered! Signal value is: "
<< sig.read() << std::endl;
}
};
int sc_main(int argc, char* argv[]) {
std::cout << "Starting Elaboration..." << std::endl;
phase_demo demo("demo");
std::cout << "Starting Simulation..." << std::endl;
sc_core::sc_start();
return 0;
}Running this program clearly illustrates how the SystemC kernel defers signal updates until the evaluation phase completes, ensuring deterministic hardware-like behavior in a sequential software environment.
Can you answer these clearly?
Keep moving when you can answer each question without looking back at the lesson.
Comments and Corrections