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.
- Evaluate: Both Process A and Process B run in an arbitrary order. Process A calls
Signal1.write(1). SystemC does not change the value ofSignal1. Instead, it schedules the change in the update queue. - Update: Once all runnable processes finish and suspend, the kernel applies the writes in the update queue.
Signal1officially becomes1. - Delta: Because
Signal1changed, Process B (which is sensitive to it) wakes up. Time has not advanced. We are atT=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