Chapter 7: SystemC 1666-2023 LRM

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:

  1. 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 the sc_simcontext::m_runnable queue.
  2. Delta (notify(SC_ZERO_TIME)): The event triggers in the very next delta cycle. The event is pushed into sc_simcontext::m_delta_events.
  3. Timed (notify(10, SC_NS)): The event triggers at a future simulation time. The event and its activation time are packaged and pushed into the sc_simcontext::m_timed_events priority 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 event a OR event b triggers.
  • wait(a & b): Suspends until BOTH event a AND event b have 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 loops crunch() and next_time() inside sc_simcontext.
  • sc_pause(): Pauses the simulation and returns control to sc_main(). It flags m_simulation_status = SC_PAUSED causing sc_start's internal loop to break early.
  • sc_stop(): Permanently halts the simulation. Once stopped (SC_STOPPED), you cannot call sc_start again.

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