Chapter 11: Advanced Core Semantics

Understanding Delta Cycles

Why Delta Cycles exist, how they solve non-determinism, and how to debug zero-delay loops.

Understanding Delta Cycles

A Delta Cycle is the cornerstone of SystemC's ability to model parallel hardware using a sequential software language (C++).

The Non-Determinism Problem

Imagine two logic gates connected in series, executing in C++. If they are modeled as simple sequential assignments, the order of execution matters.

If Process A runs first and writes to a signal, and Process B runs second and reads it, the result is deterministic. But if the OS scheduler happens to run Process B first, Process B reads the old value of the signal. Real hardware evaluates simultaneously. Software evaluates sequentially, creating a race condition.

The Delta Cycle Solution

SystemC solves this via the Evaluate-Update paradigm as mandated by the IEEE 1666 standard.

  1. Evaluate: Both Process A and Process B run in an arbitrary order. Process A calls Signal1.write(1). SystemC does not change the value of Signal1. Instead, it schedules the change in the update queue.
  2. Update: Once all runnable processes finish and suspend, the kernel applies the writes in the update queue. Signal1 officially becomes 1.
  3. Delta: Because Signal1 changed, Process B (which is sensitive to it) wakes up. Time has not advanced. We are at T=0 + 1 delta cycle. Process B evaluates again, this time seeing the deterministic, updated value.

Complete Delta Cycle Example

Below is a complete, fully compilable sc_main program that demonstrates how a race condition is avoided using delta cycles and sc_signal. It also demonstrates the use of SC_ZERO_TIME to explicitly yield a thread until the next delta cycle.

#include <systemc>
#include <iostream>
 
SC_MODULE(delta_demo) {
    sc_core::sc_signal<bool> sig_a;
    sc_core::sc_signal<bool> sig_b;
 
    SC_CTOR(delta_demo) : sig_a("sig_a"), sig_b("sig_b") {
        SC_THREAD(driver_thread);
        
        SC_METHOD(combinational_method);
        sensitive << sig_a;
        dont_initialize();
    }
 
    void driver_thread() {
        // Time = 0 s, Delta = 0
        std::cout << "Delta 0: Driver writing true to sig_a" << std::endl;
        sig_a.write(true);
        
        // At this point, sig_a has NOT updated yet. 
        // We yield for one delta cycle to let the Update Phase happen.
        sc_core::wait(sc_core::SC_ZERO_TIME);
        
        // Time = 0 s, Delta = 1
        std::cout << "Delta 1: Driver woke up from zero-time wait." << std::endl;
        
        // Wait for actual simulation time to advance
        sc_core::wait(10, sc_core::SC_NS);
        
        std::cout << "@" << sc_core::sc_time_stamp() << ": Simulation complete." << std::endl;
    }
 
    void combinational_method() {
        // This is triggered whenever sig_a actually updates.
        // It happens during Delta 1.
        std::cout << "Delta 1: Combinational method triggered! sig_a = " 
                  << sig_a.read() << std::endl;
                  
        // Write to another signal, triggering a second update phase (Delta 2)
        sig_b.write(!sig_a.read());
    }
};
 
int sc_main(int argc, char* argv[]) {
    delta_demo demo("demo");
    sc_core::sc_start();
    return 0;
}

The Delta Delay wait(SC_ZERO_TIME)

As shown in the example, you sometimes need to explicitly force a thread process to yield until the next delta cycle without advancing physical time. You can do this by waiting on sc_core::SC_ZERO_TIME.

[!WARNING] Infinite Delta Loops: If Process A writes to a signal that wakes Process B, and Process B writes to a signal that wakes Process A, they will loop forever in zero time. The simulation will freeze without advancing the clock, resulting in a maximum delta cycle limit exception from the kernel. Always ensure combinational loops are broken by a clock edge, delay, or conditional logic that stops the oscillation!

Comments and Corrections