Chapter 8: SystemC AMS

AMS Converter Ports and Domain Boundaries

How SystemC discrete-event models connect to AMS TDF models through converter ports and disciplined boundary design.

AMS Converter Ports and Domain Boundaries

Mixed-signal modeling is mostly about managing boundaries. A digital controller, a TLM register block, or a standard SystemC SC_THREAD inevitably needs to interact with an AMS dataflow model.

If you connect them casually, the model becomes confusing and simulation performance degrades. A clean boundary explicitly defines:

  • How often analog values are sampled.
  • How continuous/sampled analog crossings become digital discrete events.
  • Which domain owns the timing.

The Converter Ports

To cross between the SystemC Discrete-Event (DE) kernel and the AMS TDF solver, the LRM mandates the use of converter ports:

  • sca_tdf::sca_de::sca_in<T>: TDF reads a DE sc_signal.
  • sca_tdf::sca_de::sca_out<T>: TDF writes to a DE sc_signal.

[!WARNING] A TDF output port driving a discrete-event signal can produce an event storm. If a TDF module running at a 1 ns timestep continuously writes slightly fluctuating analog values to a DE signal, the SystemC kernel will wake up millions of times per second, destroying your simulation performance. You should always use a comparator or threshold detector in the AMS domain to reduce the event frequency before crossing the boundary into DE.

Complete Example: Smart Temperature Sensor

The following complete sc_main example perfectly illustrates mixed-signal boundaries. It models a temperature sensor. The sensor's raw physics (a TDF waveform) is processed by a TDF Threshold Comparator. Only when the temperature crosses a dangerous threshold does the TDF module output a boolean event into the SystemC Digital domain, triggering an interrupt.

#include <systemc>
#include <systemc-ams.h>
 
// 1. TDF Domain: Analog Temperature Physics
SCA_TDF_MODULE(AnalogTempPhysics) {
    sca_tdf::sca_out<double> temp_out;
 
    SCA_CTOR(AnalogTempPhysics) {}
 
    void set_attributes() {
        set_timestep(10.0, sc_core::SC_MS); // Sample physics every 10 ms
    }
 
    void processing() {
        // Simulate a slow temperature rise (e.g., an engine heating up)
        double t = get_time().to_seconds();
        double temperature = 20.0 + (t * 5.0); // Starts at 20C, rises 5C per second
        temp_out.write(temperature);
    }
};
 
// 2. TDF Domain: Analog Threshold Comparator
// This module prevents "event storms" by only outputting boolean state changes.
SCA_TDF_MODULE(ThresholdComparator) {
    sca_tdf::sca_in<double> temp_in;
    
    // Converter Port: TDF driving standard SystemC DE
    sca_tdf::sca_de::sca_out<bool> alarm_out; 
 
    double threshold;
 
    SCA_CTOR(ThresholdComparator) : threshold(75.0) {}
 
    void set_attributes() {
        // Inherits timestep from AnalogTempPhysics (10 ms)
    }
 
    void processing() {
        double current_temp = temp_in.read();
        
        // Write the boolean evaluation. 
        // Note: Writing the exact same boolean value repeatedly to a SystemC sc_signal
        // does NOT trigger value_changed_event() continuously, saving performance.
        alarm_out.write(current_temp >= threshold);
    }
};
 
// 3. Digital Domain: SystemC Interrupt Controller
SC_MODULE(InterruptController) {
    // Standard SystemC DE port
    sc_core::sc_in<bool> hw_alarm;
 
    SC_CTOR(InterruptController) {
        SC_THREAD(monitor_interrupts);
        // Only wake up when the boolean alarm state changes
        sensitive << hw_alarm.value_changed_event();
        dont_initialize();
    }
 
    void monitor_interrupts() {
        while(true) {
            if (hw_alarm.read() == true) {
                std::cout << "@ " << sc_core::sc_time_stamp() 
                          << " [DIGITAL_CTRL]: HARDWARE ALARM TRIGGERED! Shutting down system.\n";
            } else {
                std::cout << "@ " << sc_core::sc_time_stamp() 
                          << " [DIGITAL_CTRL]: Alarm cleared. System nominal.\n";
            }
            wait();
        }
    }
};
 
int sc_main(int argc, char* argv[]) {
    // Mixed-Signal Boundary Signals
    sca_tdf::sca_signal<double> sig_temp("sig_temp"); // TDF-to-TDF
    sc_core::sc_signal<bool> sig_alarm("sig_alarm");  // TDF-to-DE
 
    // Instantiate Modules
    AnalogTempPhysics physics("physics");
    physics.temp_out(sig_temp);
 
    ThresholdComparator comparator("comparator");
    comparator.temp_in(sig_temp);
    comparator.alarm_out(sig_alarm);
 
    InterruptController ctrl("ctrl");
    ctrl.hw_alarm(sig_alarm);
 
    // Setup Tracing
    sca_util::sca_trace_file* tf = sca_util::sca_create_vcd_trace_file("boundary_wave");
    sca_util::sca_trace(tf, sig_temp, "Analog_Temperature");
    sca_util::sca_trace(tf, sig_alarm, "Digital_Alarm_Signal");
 
    // Start Simulation
    std::cout << "Starting Mixed-Signal Simulation...\n";
    sc_core::sc_start(15.0, sc_core::SC_SEC); // Simulate 15 seconds of physics
    
    sca_util::sca_close_vcd_trace_file(tf);
    return 0;
}

Best Practices for Modeler

  1. Minimize Crossings: Put analog-like signal processing (filters, integrations) entirely inside AMS clusters. Put digital control (state machines, registers) entirely inside SystemC modules.
  2. Compress Data: Use comparators or decimation filters inside the TDF domain to reduce the rate of data crossing into the DE domain.
  3. Document Timing Ownership: Clearly document which module acts as the timing master. In the example above, the TDF AnalogTempPhysics module sets the timestep, dictating exactly when the ThresholdComparator runs and when the DE boundary is evaluated.

Comments and Corrections