Chapter 2: Core Modeling

Processes, Events, and Time

SC_METHOD, SC_THREAD, sensitivity, wait(), sc_event, and the meaning of delta cycles.

Listen to this lessonAudiobook mode

How to Read This Lesson

Processes are the executable behavior inside a SystemC model. Modules provide structure. Channels provide communication. Processes provide activity.

SystemC has two process styles you will use constantly:

  • SC_METHOD: runs to completion and cannot call wait().
  • SC_THREAD: can suspend with wait() and resume later.

Standard and source context

SC_METHOD

Use SC_METHOD for combinational behavior or small reactions to events:

#include <systemc>
using namespace sc_core;
 
SC_MODULE(AndGate) {
  sc_in<bool> a{"a"};
  sc_in<bool> b{"b"};
  sc_out<bool> y{"y"};
 
  void comb() {
    y.write(a.read() && b.read());
  }
 
  SC_CTOR(AndGate) {
    SC_METHOD(comb);
    sensitive << a << b;
  }
};
 
int sc_main(int, char*[]) {
  sc_signal<bool> sig_a, sig_b, sig_y;
  AndGate and_gate("and_gate");
  and_gate.a(sig_a);
  and_gate.b(sig_b);
  and_gate.y(sig_y);
 
  sc_start(1, SC_NS);
  return 0;
}

The method runs when an event in its sensitivity list occurs. It should finish quickly because it cannot yield.

SC_THREAD

Use SC_THREAD when behavior has an internal timeline:

#include <systemc>
using namespace sc_core;
 
SC_MODULE(Timer) {
  sc_event done;
 
  SC_CTOR(Timer) {
    SC_THREAD(run);
  }
 
  void run() {
    wait(100, SC_NS);
    done.notify();
    std::cout << "Timer done at " << sc_time_stamp() << std::endl;
  }
};
 
int sc_main(int, char*[]) {
  Timer timer("timer");
  sc_start(200, SC_NS);
  return 0;
}

The call to wait() suspends the thread process. The simulation kernel saves enough process state to resume it when the wait condition is satisfied.

Events Do Not Store History

An sc_event is not a queue of messages. It is a notification mechanism. If nobody is waiting when an immediate event is notified, the event is missed.

That makes this pattern important:

#include <systemc>
using namespace sc_core;
 
SC_MODULE(Consumer) {
  sc_event producer_done;
 
  SC_CTOR(Consumer) {
    SC_THREAD(consumer_thread);
  }
 
  void consumer_thread() {
    while (true) {
      wait(producer_done);
      consume_result();
    }
  }
 
  void consume_result() {
    std::cout << "Result consumed at " << sc_time_stamp() << std::endl;
  }
};
 
int sc_main(int, char*[]) {
  Consumer cons("cons");
  cons.producer_done.notify(10, SC_NS);
  sc_start(20, SC_NS);
  return 0;
}

The process arms the wait first, then reacts.

Delta Cycles

A delta cycle is a zero-time scheduling step. It lets the kernel settle chains of events without advancing simulation time. Signal writes use this idea: a process writes a new value, the channel schedules an update, and dependent processes wake in a later delta cycle.

Delta cycles are why SystemC can avoid many order-dependent bugs. Processes can run in a deterministic simulation order while still modeling hardware-like simultaneous updates.

Practical Debug Rule

When behavior looks one step late, ask which phase you are observing:

  • Did a method write a signal but the update has not happened yet?
  • Did an event notify in the same delta or the next delta?
  • Is a thread waiting on a value change or on a timed delay?

Most SystemC timing surprises become ordinary once you separate time advancement from delta-cycle settling.

Under the Hood: sc_process, sc_event, and QuickThreads

When you register a process using SC_METHOD or SC_THREAD, SystemC allocates a process object inheriting from sc_process_b (defined in sysc/kernel/sc_process.h).

  • sc_method_process: A simple C++ function pointer. The scheduler calls it, and it must run to completion.
  • sc_thread_process: Requires its own execution stack to support wait(). Under the hood, the Accellera kernel uses a coroutine library. On Linux/Windows, it typically uses QuickThreads (src/sysc/qt/) or POSIX fibers. When wait() is called, the coroutine context is saved, and execution yields back to the SystemC scheduler. When an sc_event::notify() is called, the kernel pushes the event into sc_simcontext::m_event_list. At the end of the delta cycle, the scheduler wakes up all processes statically or dynamically sensitive to that event by moving them into the m_runnable list.
Lesson self-check

Can you answer these clearly?

Keep moving when you can answer each question without looking back at the lesson.

Comments and Corrections