Chapter 8: SystemC AMS

Solver Synchronization and Execution Semantics

Learn how the SystemC AMS solvers synchronize the TDF, LSF, and ELN models of computation with each other and the discrete-event kernel.

Solver Synchronization and Execution Semantics

Because SystemC AMS allows you to mix and match Timed Data Flow (TDF), Linear Signal Flow (LSF), Electrical Linear Networks (ELN), and standard SystemC discrete-event (DE) models, it relies on a sophisticated synchronization layer to ensure time remains consistent across all domains.

In this tutorial, we will explore how these different solvers interact, how time steps are propagated, and how data moves securely between the analog and digital worlds.

TDF as the Master Scheduler

In SystemC AMS, the TDF solver acts as the primary time-keeper for the continuous-time domains.

When you build an LSF or ELN model, those models form a system of continuous-time equations. However, a computer cannot simulate continuous time infinitely; it must discretize it. The LSF and ELN solvers derive their calculation timesteps directly from the TDF cluster they are connected to.

If you connect an ELN circuit to a TDF module that runs with a 1.0 us timestep, the ELN solver will simulate that circuit in chunks of 1.0 us to provide a synchronized output back to the TDF module.

Synchronizing with the SystemC DE Kernel

Ultimately, your AMS system will likely need to communicate with a standard SystemC discrete-event digital component (like a processor, an interrupt controller, or a TLM bus).

Synchronization between the AMS solvers and the SystemC DE kernel is done exclusively through specialized converter ports:

  • sca_tdf::sca_de::sca_in<T>: Reads a standard sc_core::sc_signal<T> from the DE kernel into the TDF domain.
  • sca_tdf::sca_de::sca_out<T>: Writes a TDF sample to a standard sc_core::sc_signal<T> in the DE kernel.

The Synchronization Rules

According to the IEEE 1666.1 LRM:

  1. Reading from DE to TDF: When the TDF solver reads a value from a sca_de::sca_in port during a processing() callback, it reads the value that was present on the SystemC signal at the first delta cycle of the current SystemC simulation time. The value is assumed to remain constant for the duration of the TDF timestep.
  2. Writing from TDF to DE: When the TDF solver writes a sample to a sca_de::sca_out port, the value is written to the SystemC signal at the exact corresponding SystemC time, triggering standard SystemC update phases and events (like value_changed_event()).

Complete Example: DE and TDF Synchronization

This complete, compilable example demonstrates a TDF module generating a mathematical waveform, connected via a converter port to a purely digital SystemC module (a simple threshold monitor) that wakes up only when the analog value crosses a boundary.

#include <systemc>
#include <systemc-ams.h>
 
// 1. TDF Domain: Analog Waveform Generator
SCA_TDF_MODULE(AnalogSensor) {
    // A converter port: TDF driving a standard SystemC DE signal
    sca_tdf::sca_de::sca_out<double> analog_out;
 
    SCA_CTOR(AnalogSensor) {}
 
    void set_attributes() {
        set_timestep(1.0, sc_core::SC_MS); // 1 millisecond sampling
    }
 
    void processing() {
        // Generate a slow 1 Hz sine wave
        double t = get_time().to_seconds();
        double voltage = 5.0 * std::sin(2.0 * M_PI * 1.0 * t);
        
        // Write to the DE kernel. This schedules a SystemC event.
        analog_out.write(voltage);
    }
};
 
// 2. Digital Domain: Discrete-Event Monitor
SC_MODULE(DigitalMonitor) {
    sc_core::sc_in<double> analog_in;
 
    SC_CTOR(DigitalMonitor) {
        SC_THREAD(monitor_thread);
        // Wake up whenever the analog signal changes
        sensitive << analog_in.value_changed_event(); 
        dont_initialize();
    }
 
    void monitor_thread() {
        bool threshold_exceeded = false;
 
        while (true) {
            double current_val = analog_in.read();
            
            if (current_val > 4.5 && !threshold_exceeded) {
                threshold_exceeded = true;
                std::cout << "@ " << sc_core::sc_time_stamp() 
                          << " [DIGITAL ALARM]: Voltage exceeded 4.5V! (Value: " 
                          << current_val << "V)\n";
            } 
            else if (current_val < 4.0 && threshold_exceeded) {
                threshold_exceeded = false;
                std::cout << "@ " << sc_core::sc_time_stamp() 
                          << " [DIGITAL CLEAR]: Voltage dropped below 4.0V.\n";
            }
 
            wait(); // Wait for the next value_changed_event
        }
    }
};
 
int sc_main(int argc, char* argv[]) {
    // 3. The boundary signal: Standard SystemC sc_signal
    sc_core::sc_signal<double> sig_analog_voltage("sig_analog_voltage");
 
    // 4. Instantiate and bind
    AnalogSensor sensor("sensor");
    sensor.analog_out(sig_analog_voltage); // TDF writes to DE
 
    DigitalMonitor monitor("monitor");
    monitor.analog_in(sig_analog_voltage); // DE reads from DE
 
    // Setup Tracing to observe the synchronization
    sca_util::sca_trace_file* tf = sca_util::sca_create_vcd_trace_file("sync_wave");
    sca_util::sca_trace(tf, sig_analog_voltage, "Sensor_Voltage");
 
    // Run simulation for 2 seconds
    std::cout << "Starting mixed-signal simulation...\n";
    sc_core::sc_start(2.0, sc_core::SC_SEC);
    
    sca_util::sca_close_vcd_trace_file(tf);
    return 0;
}

Event-Driven TDF Activation (Dynamic TDF)

In Dynamic TDF (introduced in SystemC AMS 2.0), a TDF module can actually suspend its static schedule and wait for a discrete-event trigger.

By using request_next_activation(port.default_event()) inside the processing() callback, a TDF module can wake up reactively when a digital signal changes. This saves immense computation power when the analog domain is otherwise idle, rather than forcing the TDF solver to calculate empty samples on a fixed timestep.

Comments and Corrections