SystemC AMS (Analog/Mixed-Signal)
An introduction to the SystemC AMS standard, Timed Data Flow (TDF), and modeling continuous-time signals.
How to Read This Lesson
AMS becomes easier once you separate continuous-time intent from discrete-event synchronization. Watch where the analog cluster meets the SystemC kernel.
SystemC AMS (Analog/Mixed-Signal)
Standard SystemC is a discrete-event simulator. It evaluates state changes at specific, discrete points in time.
However, modern SoCs interact heavily with continuous-time physical systems (RF transceivers, sensors, power grids). To model these systems efficiently without resorting to cycle-heavy, numerically intense SPICE-level simulations, Accellera standardized the SystemC AMS 2.0 Language Reference Manual.
Source and LRM Trail
For AMS, use Docs/LRMs/SystemC_AMS_2_0_LRM.pdf as the standard reference. The implementation/source trail is the AMS proof-of-concept code and examples where available, plus the SystemC DE boundary in .codex-src/systemc/src/sysc/kernel. Pay special attention to TDF rate, delay, timestep, converter ports, and solver synchronization.
The Three Models of Computation in AMS
SystemC AMS introduces three mathematically distinct Models of Computation (MoC) that interoperate with the discrete SystemC kernel:
- Timed Data Flow (TDF): The most common MoC. It uses a static schedule and fixed time steps. Best for signal processing, communication algorithms, and behavioral analog modeling.
- Linear Signal Flow (LSF): Used to model continuous-time behavior using primitive blocks like integrators, differentiators, and adders.
- Electrical Linear Networks (ELN): Models electrical circuits using passive components (resistors, capacitors, inductors) and sources. Solved via nodal analysis matrices.
This guide focuses on TDF, as it bridges the gap between digital discrete control and continuous analog waveforms efficiently.
Writing a TDF Module & Accellera Kernel Details
In AMS TDF, you inherit from sca_tdf::sca_module and define a fixed-time calculation in the processing() callback.
Unlike discrete SystemC, TDF modules do not wait on events (no sc_event). They are statically scheduled by the AMS solver to execute at exact, regular intervals dictated by set_timestep().
Under the Hood (Accellera systemc-ams repository):
sca_tdf::sca_module registers itself with an internal synchronization layer (sca_core::sca_implementation::sca_sync_obj and sca_solver_base). During elaboration, the AMS kernel identifies all connected TDF modules and forms a "cluster." It performs a static scheduling algorithm to determine the exact order in which processing() must be called to satisfy data dependencies without deadlocks.
When sc_start() is called, the AMS solver actually registers a standard SC_METHOD within the standard SystemC sc_simcontext. This hidden method computes the next time the TDF cluster needs to run and issues a sc_core::next_trigger(sc_time) to sleep the continuous-time solver until the discrete-event time matches the next TDF time step.
Complete AMS Interoperability Example
Here is a complete sc_main example models an Analog-to-Digital Converter (ADC). It reads an analog sine wave generated in TDF, converts it to a discrete digital signal, and triggers a standard SystemC thread when the voltage crosses a threshold.
#include <systemc>
#include <systemc-ams.h>
// 1. TDF Sine Wave Generator (Purely Analog)
SCA_TDF_MODULE(SineGenerator) {
sca_tdf::sca_out<double> analog_out;
double amplitude;
double frequency;
SCA_CTOR(SineGenerator) : amplitude(3.3), frequency(1000.0) {}
// Defines the strict execution interval for the solver
void set_attributes() {
set_timestep(10, sc_core::SC_US);
}
void processing() {
double current_time = get_time().to_seconds();
double value = amplitude * std::sin(2.0 * M_PI * frequency * current_time);
analog_out.write(value);
}
};
// 2. TDF ADC (Mixed-Signal: Analog In, Digital Out)
SCA_TDF_MODULE(ADC_Model) {
sca_tdf::sca_in<double> analog_in;
// sca_de denotes Discrete-Event (standard SystemC) output
sca_tdf::sca_de::sca_out<bool> threshold_alert;
SCA_CTOR(ADC_Model) {}
void set_attributes() {
set_timestep(10, sc_core::SC_US);
}
void processing() {
double voltage = analog_in.read();
// If voltage exceeds 3.0V, trigger the digital alert
if (voltage > 3.0) {
threshold_alert.write(true); // Schedules a SystemC delta update!
} else {
threshold_alert.write(false);
}
}
};
// 3. Standard SystemC Digital Monitor
SC_MODULE(DigitalMonitor) {
sc_core::sc_in<bool> alert_in;
SC_CTOR(DigitalMonitor) {
SC_THREAD(monitor_thread);
sensitive << alert_in.pos(); // Wake up exactly when the AMS model crosses 3.0V
}
void monitor_thread() {
while(true) {
wait();
std::cout << "@" << sc_core::sc_time_stamp()
<< " [Digital Domain] WARNING: Voltage crossed 3.0V threshold!" << std::endl;
}
}
};
int sc_main(int argc, char* argv[]) {
// Standard SystemC signal bridging the two domains
sc_core::sc_signal<bool> alert_sig("alert_sig");
// AMS continuous-time signal (using sca_tdf::sca_signal)
sca_tdf::sca_signal<double> analog_wire("analog_wire");
// Instantiate modules
SineGenerator sine("sine");
sine.analog_out(analog_wire);
ADC_Model adc("adc");
adc.analog_in(analog_wire);
adc.threshold_alert(alert_sig);
DigitalMonitor monitor("monitor");
monitor.alert_in(alert_sig);
std::cout << "Starting Mixed-Signal Simulation..." << std::endl;
// Simulate for 2 milliseconds
sc_core::sc_start(2, sc_core::SC_MS);
return 0;
}Bridging the Gap: Converter Ports
The magic in the example above lies in sca_tdf::sca_de::sca_out<bool>.
To cross domains, SystemC AMS provides specialized converter ports:
sca_tdf::sca_de::sca_in<T>: Reads a standard SystemCsc_signal<T>from within the TDFprocessing()method.sca_tdf::sca_de::sca_out<T>: Writes to a standard SystemCsc_signal<T>.- Under the Hood: When
sca_tdf::sca_de::sca_out::write(val)is called inside theprocessing()callback, the AMS solver delegates the call to the boundsc_signal<T>::write(val). Because this standard SystemCwrite()operates normally, it triggers anrequest_update()in the SystemC discrete-event kernel. In the next discrete-event Update Phase, standard SystemC processes sensitive to that signal are made runnable.
- Under the Hood: When
Summary
SystemC AMS allows you to model RF, power, and analog subsystems within the same executable as your digital RTL and TLM firmware. By leveraging TDF and understanding how the AMS cluster scheduling integrates natively with the sc_simcontext time queue, you achieve extremely fast simulation speeds while maintaining enough accuracy for architectural exploration, power modeling, and mixed-signal verification.
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