LRM Bridge: Events, Time, and Scheduler APIs
sc_event, event lists, sc_time, time resolution, sc_start, sc_pause, sc_stop, simulation status, and pending activity.
How to Read This Lesson
This lesson is an LRM bridge. We translate standard language into the questions you actually ask while debugging and reviewing models.
Events and time form the primary vocabulary for inter-process communication in the SystemC discrete-event scheduler. Here we explore the IEEE 1666 rules and examine the Accellera kernel C++ source code to see how they are executed under the hood.
Source and LRM Trail
This chapter is the LRM bridge. The primary reference is Docs/LRMs/SystemC_LRM_1666-2023.pdf; the secondary reference is .codex-src/systemc. Read the LRM first for the rule, then read the source to understand why the rule produces the behavior you see in a debugger.
sc_event
An sc_core::sc_event is a fundamental synchronization object. Processes can wait on it, and other processes or channels can notify it. Unlike software message queues, events do not queue or hold data. If a process is not actively waiting on an event when it is notified, that notification is permanently missed.
Under the Hood (Accellera Kernel):
When a process calls wait(sc_event), the scheduler maps the sc_process_b handle to the sc_event's internal dependency list. The LRM defines three notification types, and the sc_event::notify() method routes them through sc_simcontext:
- Immediate (
notify()): The event triggers in the current evaluation phase. The kernel instantly takes all suspended processes waiting on this event and pushes them onto thesc_simcontext::m_runnablequeue. - Delta (
notify(SC_ZERO_TIME)): The event triggers in the very next delta cycle. The event is pushed intosc_simcontext::m_delta_events. - Timed (
notify(10, SC_NS)): The event triggers at a future simulation time. The event and its activation time are packaged and pushed into thesc_simcontext::m_timed_eventspriority queue.
Event Lists (AND/OR semantics)
The IEEE 1666 standard provides event-list objects to allow processes to wake up on multiple, complex conditions.
wait(a | b): Wakes up if eventaOR eventbtriggers.wait(a & b): Suspends until BOTH eventaAND eventbhave triggered.
Under the Hood:
These operators dynamically construct objects of type sc_event_or_list and sc_event_and_list. For an AND list, the kernel tracks an internal counter of how many events have fired. When an event fires, it decrements the counter. The process is only moved back to m_runnable when the counter reaches zero.
Time and Simulation Control API
sc_core::sc_time represents absolute simulation time, distinct from physical wall-clock time.
Under the Hood: sc_time does not use floating-point math internally for simulation time because of precision loss. It stores time as an unsigned 64-bit integer (sc_dt::uint64 representing a multiple of the global time resolution). You must define the time resolution (e.g., sc_set_time_resolution(1, SC_PS)) before the first sc_time object is constructed, otherwise the kernel will throw a fatal error.
The kernel can be controlled dynamically using:
sc_start(sc_time): Runs the simulation until the specified time elapses. It loopscrunch()andnext_time()insidesc_simcontext.sc_pause(): Pauses the simulation and returns control tosc_main(). It flagsm_simulation_status = SC_PAUSEDcausingsc_start's internal loop to break early.sc_stop(): Permanently halts the simulation. Once stopped (SC_STOPPED), you cannot callsc_startagain.
End-to-End Scheduler API Example
This fully compliant sc_main example demonstrates event lists, immediate vs. timed notification, time resolution, and stepping the simulation via sc_start().
#include <systemc>
SC_MODULE(SchedulerAPI_Demo) {
sc_core::sc_event ev_a;
sc_core::sc_event ev_b;
SC_CTOR(SchedulerAPI_Demo) {
SC_THREAD(consumer_thread);
SC_THREAD(producer_thread);
}
void consumer_thread() {
while(true) {
std::cout << "@" << sc_core::sc_time_stamp()
<< " delta=" << sc_core::sc_delta_count()
<< " [Consumer] Waiting for Event A AND Event B..." << std::endl;
// AND event list: Will wake up only after BOTH have fired.
wait(ev_a & ev_b);
std::cout << "@" << sc_core::sc_time_stamp()
<< " delta=" << sc_core::sc_delta_count()
<< " [Consumer] Woke up! Both events received." << std::endl;
}
}
void producer_thread() {
wait(10, sc_core::SC_NS);
std::cout << "@" << sc_core::sc_time_stamp() << " [Producer] Notifying Event A (Immediate)" << std::endl;
ev_a.notify(); // Immediate notification
wait(5, sc_core::SC_NS);
std::cout << "@" << sc_core::sc_time_stamp() << " [Producer] Notifying Event B (Delta)" << std::endl;
ev_b.notify(sc_core::SC_ZERO_TIME); // Delta notification
wait(20, sc_core::SC_NS);
std::cout << "@" << sc_core::sc_time_stamp() << " [Producer] Pausing Simulation." << std::endl;
sc_core::sc_pause();
}
};
int sc_main(int argc, char* argv[]) {
// Must be called before any time objects are constructed
sc_core::sc_set_time_resolution(1, sc_core::SC_PS);
SchedulerAPI_Demo demo("demo");
std::cout << "--- Starting Simulation for 100 ns ---" << std::endl;
// We can step the simulation in chunks
sc_core::sc_start(20, sc_core::SC_NS);
std::cout << "--- Back in sc_main. Checking status ---" << std::endl;
if (sc_core::sc_get_status() == sc_core::SC_PAUSED) {
std::cout << "Simulation was paused. Resuming..." << std::endl;
sc_core::sc_start(); // Continue indefinitely until sc_stop() or exhaustion
}
return 0;
}Debugging Time Bugs
When diagnosing simulation hangs or zero-time loops, always print both the absolute time and the delta cycle count:
std::cout << sc_core::sc_time_stamp()
<< " delta=" << sc_core::sc_delta_count() << std::endl;If sc_time_stamp() is constant but sc_delta_count() increments infinitely, you have an un-clocked combinatorial feedback loop (an infinite zero-time scheduling loop). Under the hood, sc_simcontext::crunch() is looping endlessly over m_delta_events without ever returning to next_time(), and you must insert a timed wait() to break it.
Comments and Corrections