Chapter 2: Core Modeling

Reset and Clocked Processes

How to model reset, clocked behavior, dont_initialize(), async_reset_signal_is(), and reset_signal_is().

How to Read This Lesson

Keep one question in mind: when does this code run as ordinary C++, and when is the simulation kernel in charge? That split explains most beginner bugs.

Reset modeling looks simple until the model mixes methods, threads, clocks, and initialization. Be explicit about what kind of reset you are modeling. According to the IEEE 1666 LRM Section 5.2.2, resets can be synchronous or asynchronous, and the behavior applies differently depending on the process type.

Here is a complete compilable example demonstrating both method-based synchronous resets and thread-based asynchronous resets.

#include <systemc>
 
using namespace sc_core;
 
SC_MODULE(ResetDemo) {
  sc_in<bool> clk{"clk"};
  sc_in<bool> rst_sync{"rst_sync"};
  sc_in<bool> rst_async{"rst_async"};
  
  sc_out<int> count_method{"count_method"};
  sc_out<int> count_thread{"count_thread"};
 
  SC_CTOR(ResetDemo) {
    // 1. Method-Based Clocked Logic
    SC_METHOD(tick_method);
    sensitive << clk.pos();
    dont_initialize();
 
    // 2. Thread-Based Clocked Logic with explicit reset routing
    SC_THREAD(run_thread);
    sensitive << clk.pos();
    async_reset_signal_is(rst_async, true);
  }
 
  // Synchronous Reset modeled inside the body
  void tick_method() {
    if (rst_sync.read()) {
      count_method.write(0);
      return;
    }
    count_method.write(count_method.read() + 1);
  }
 
  // Asynchronous Reset handled by the kernel
  void run_thread() {
    // This code block acts as the reset initialization
    count_thread.write(0);
    wait();
 
    while (true) {
      // Normal clocked behavior
      count_thread.write(count_thread.read() + 1);
      wait();
    }
  }
};
 
int sc_main(int argc, char* argv[]) {
  sc_clock clk("clk", 10, SC_NS);
  sc_signal<bool> rst_sync("rst_sync");
  sc_signal<bool> rst_async("rst_async");
  sc_signal<int> count_m("count_m"), count_t("count_t");
 
  ResetDemo demo("demo");
  demo.clk(clk);
  demo.rst_sync(rst_sync);
  demo.rst_async(rst_async);
  demo.count_method(count_m);
  demo.count_thread(count_t);
 
  // Assert both resets
  rst_sync.write(true);
  rst_async.write(true);
  sc_start(15, SC_NS);
 
  // De-assert and run
  rst_sync.write(false);
  rst_async.write(false);
  sc_start(50, SC_NS);
  
  // Assert async reset mid-flight
  rst_async.write(true);
  sc_start(20, SC_NS);
 
  return 0;
}

Source and LRM Trail

Read this topic against Docs/LRMs/SystemC_LRM_1666-2023.pdf for process, event, time, reset, and report semantics. In source, follow .codex-src/systemc/src/sysc/kernel/sc_simcontext.cpp, sc_process.*, sc_event.*, sc_wait.*, sc_reset.*, and .codex-src/systemc/src/sysc/utils/sc_report_handler.cpp.

Method-Based Clocked Logic

A common method process reacts to a clock edge. dont_initialize() matters because SystemC normally initializes method processes once before simulation time advances. For clocked logic, that time-zero call is often not desired.

For a method process, synchronous reset is usually modeled explicitly in the body, checking the reset signal state before updating logic.

Thread-Based Clocked Logic

Threads can express sequential control more naturally. The exact reset behavior depends on the process kind and reset declaration. When using async_reset_signal_is(rst, true) or reset_signal_is(rst, true), the simulation kernel tracks the reset signal. If the signal goes active, the kernel throws a specific C++ exception inside the process to immediately unwind the stack and jump back to the beginning of the run method!

Avoid Hidden State Surprises

Local C++ variables inside a thread preserve state across waits:

void run() {
  unsigned local_count = 0;
  while (true) {
    out.write(local_count++);
    wait();
  }
}

If the reset restarts the process via async_reset_signal_is, local variables inside the while loop will be destroyed, and the process restarts from the top of the function where local_count = 0 is executed again.

Modeling Advice

Keep reset code boring:

  • Put all reset assignments in one obvious branch.
  • Decide whether reset affects only architectural state or also modeling statistics.
  • Test reset assertion, deassertion, and reset during activity (mid-flight).

Under the Hood: sc_reset and sc_unwind_exception

When you define a reset using reset_signal_is(rst, active_high), you are attaching a reset policy to the sc_process_b object. How does the thread actually reset? During the evaluation phase, if the kernel detects that the reset signal has matched its active state, it throws a C++ exception of type sc_unwind_exception inside the running thread context (sysc/kernel/sc_process.cpp). This exception aggressively unwinds the C++ stack of the coroutine. In your thread code, the wait() call throws this exception. SystemC catches it at the root of the thread wrapper, resets the local variables, and restarts the thread function from the beginning.

Deep Dive: Accellera Source for sc_signal and update()

The sc_signal<T> channel perfectly illustrates the Evaluate-Update paradigm of SystemC. In the Accellera source (src/sysc/communication/sc_signal.cpp), sc_signal inherits from sc_prim_channel.

The write() Implementation

When you call write(const T&), the signal does not immediately change its value. Instead, it stores the requested value in m_new_val and registers itself with the kernel:

template<class T>
inline void sc_signal<T>::write(const T& value_) {
    if( !(m_new_val == value_) ) {
        m_new_val = value_;
        this->request_update(); // Inherited from sc_prim_channel
    }
}

The request_update() call appends the channel to sc_simcontext::m_update_list.

The update() Phase

After the Evaluate phase finishes (all ready processes have run), the kernel iterates over m_update_list and calls the update() virtual function on each primitive channel. For sc_signal, this looks like:

template<class T>
inline void sc_signal<T>::update() {
    if( !(m_new_val == m_cur_val) ) {
        m_cur_val = m_new_val;
        m_value_changed_event.notify(SC_ZERO_TIME); // Notify processes sensitive to value_changed_event()
    }
}

This guarantees that all concurrent processes see the same old value until the delta cycle advances, perfectly mimicking hardware register delays.

Comments and Corrections