AMS Modeling Style for Virtual Platforms
How to combine SystemC AMS with a VP without turning a software platform model into a slow analog simulation.
How to Read This Lesson
AMS Modeling Style for Virtual Platforms
SystemC AMS can enrich a Virtual Platform (VP), but it should not accidentally change the purpose of the VP. Virtual platforms are primarily designed for fast software execution, whereas analog simulations often require fine-grained time steps that slow down simulation significantly.
Standard and source context
When AMS Belongs in a VP
According to the SystemC AMS standard (IEEE 1666.1), you should use AMS when software-visible behavior depends on analog or signal-processing effects:
- ADC sample streams
- Sensor thresholds
- PLL lock approximation
- Power or thermal trends
- Filters
- Motor-control feedback
Do not use AMS merely to make the model look more advanced.
Good VP Boundary
A practical mixed VP usually has:
- A TLM register block for software programming.
- An AMS cluster (TDF or LSF) for signal behavior.
- Converter ports (e.g.,
sca_tdf::sca_in,sca_tdf::sca_outconnected tosc_core::sc_signal) for control and status between the AMS and discrete-event worlds. - Interrupts or status registers for software-visible results.
The software should still see registers, memory, and interrupts, modeled via standard TLM-2.0 or Simple Bus abstraction.
Complete AMS to SystemC Boundary Example
Below is a fully compilable sc_main program demonstrating how to interface an AMS TDF (Timed Data Flow) model representing an ADC with a standard SystemC discrete-event module representing a simple Virtual Platform peripheral register block.
#include <systemc>
#include <systemc-ams>
// 1. AMS TDF Module (The Analog / Continuous Part)
SCA_TDF_MODULE(adc_sensor) {
// Converter port: Continuous output to Discrete Event (DE) domain
sca_tdf::sca_out<double> analog_out;
// Internal state
double current_val;
SCA_CTOR(adc_sensor) : analog_out("analog_out"), current_val(0.0) {}
void set_attributes() override {
// Set a coarse timestep so we don't slow down the VP unnecessarily
set_timestep(1.0, sc_core::SC_MS);
}
void processing() override {
// Simulate a slowly changing analog value (e.g., a temperature sensor)
current_val += 0.5;
if (current_val > 100.0) {
current_val = 0.0;
}
// Write out the analog value to the converter port
analog_out.write(current_val);
}
};
// 2. VP Peripheral (The Discrete Event / Software Visible Part)
SC_MODULE(vp_adc_peripheral) {
// Input from the AMS domain
sc_core::sc_in<double> analog_in;
// Interrupt output to the CPU
sc_core::sc_out<bool> irq_out;
// A simple threshold register programmable by software
double threshold_reg;
SC_CTOR(vp_adc_peripheral) : analog_in("analog_in"), irq_out("irq_out"), threshold_reg(50.0) {
SC_METHOD(monitor_threshold);
sensitive << analog_in; // Trigger whenever the AMS converter writes a new value
dont_initialize();
}
void monitor_threshold() {
double current = analog_in.read();
// If the analog value crosses the software-programmed threshold, trigger an interrupt
if (current >= threshold_reg) {
irq_out.write(true);
std::cout << "@ " << sc_core::sc_time_stamp()
<< " VP ADC: Threshold crossed! Value: " << current
<< ", raising IRQ." << std::endl;
} else {
irq_out.write(false);
}
}
};
// 3. Top-Level Integration
int sc_main(int argc, char* argv[]) {
// Signals
sc_core::sc_signal<double> analog_sig("analog_sig");
sc_core::sc_signal<bool> irq_sig("irq_sig");
// Instantiation
adc_sensor ams_block("ams_block");
vp_adc_peripheral vp_block("vp_block");
// Binding
ams_block.analog_out(analog_sig);
vp_block.analog_in(analog_sig);
vp_block.irq_out(irq_sig);
// Run simulation
std::cout << "Starting mixed AMS/VP simulation..." << std::endl;
sc_core::sc_start(200.0, sc_core::SC_MS);
std::cout << "Simulation finished." << std::endl;
return 0;
}Performance Rule
Keep AMS timesteps as coarse as the use case allows. A VP that needs to boot firmware should not spend most of its time solving unnecessary high-resolution analog detail. In the example above, a 1.0 ms timestep was used to minimize context switches between the AMS solver and the SystemC kernel.
Under the Hood: Dynamic TDF (request_next_activation)
In standard TDF, the internal SC_THREAD loops rigidly at cluster_period intervals. Even if the analog signal isn't changing or the software isn't reading the ADC, the SystemC scheduler is continually interrupted to execute the solver matrix.
To optimize VPs, the Accellera implementation of SystemC AMS 2.0 introduced Dynamic TDF. Inside your processing() callback, you can explicitly call the C++ method request_next_activation(sc_event).
When you make this call, you are signaling the AMS cluster thread to abandon its fixed static schedule for this module. The cluster thread internally yields and does not automatically wake up on the next timestep. Instead, it waits indefinitely until the standard sc_core::sc_event is fired.
For example, if a TLM-2.0 b_transport read arrives from the digital CPU, the TLM socket can fire an sc_event. The TDF module wakes up, computes a single analog sample, provides the result back to the TLM thread, and goes back to sleep. This "demand-driven" analog evaluation means the AMS solver consumes zero CPU cycles while the software VP is executing unrelated code, drastically accelerating boot times.
Documentation Rule
For each AMS block in a VP, document:
- Model of computation (e.g., TDF, LSF, ELN)
- Timestep and rates
- Delays
- Boundary ports (Converter type)
- Numerical approximations
- Software-visible effects (IRQs, specific register behaviors)
This lets users understand what is accurate, approximate, and intentionally ignored.
Can you answer these clearly?
Keep moving when you can answer each question without looking back at the lesson.
Comments and Corrections