Electrical Linear Networks (ELN)
Modeling conservative continuous-time electrical circuits using the Electrical Linear Networks (ELN) model of computation.
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.
Electrical Linear Networks (ELN)
The Electrical Linear Networks (ELN) model of computation brings traditional schematic-based circuit modeling into SystemC AMS. While LSF relies on abstract mathematical data flows, ELN is specifically designed for conservative continuous-time systems. This means the AMS solver automatically enforces Kirchhoff's Voltage and Current Laws (KVL/KCL) across the network.
If you are accustomed to SPICE netlists, ELN will look very familiar. You build your model by defining electrical nodes (nets) and connecting physical components (like resistors, capacitors, and voltage sources) between them.
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.
Nodes, Terminals, and Primitives
The core difference between ELN and TDF/LSF is how components interact. In TDF, data flows directionally from an output port to an input port. In ELN, energy is exchanged conservatively across bidirectional terminals.
1. Nodes (sca_eln::sca_node)
A node represents a physical electrical connection point (a wire or net). The instantaneous voltage at a node is calculated dynamically by the ELN solver relative to a reference ground.
sca_eln::sca_node: A standard electrical net.sca_eln::sca_node_ref: The electrical ground (defined as exactly 0 Volts). Every ELN cluster must have at least one path to a reference node.
2. Terminals (sca_eln::sca_terminal)
ELN components use terminals rather than directional ports. Terminals bind to nodes. By convention, current flows through the terminal into the component, and the voltage drops across the component's positive (p) and negative (n) terminals.
3. Electrical Primitives
The sca_eln namespace provides standard passive electrical components. All of them are instantiated as standard members in an sc_module:
sca_eln::sca_r: Resistor.sca_eln::sca_c: Capacitor.sca_eln::sca_l: Inductor.sca_eln::sca_vsource/sca_eln::sca_isource: Independent voltage/current sources.
Converting to/from ELN
You cannot connect a discrete TDF signal directly to a resistor. Instead, you use TDF-to-ELN converter primitives:
sca_eln::sca_tdf::sca_vsource: Acts as an ideal voltage source in the ELN domain, where the instantaneous voltage is driven by the sampled TDF input signal.sca_eln::sca_tdf::sca_vsink: Acts as an ideal voltmeter that reads the voltage drop across two ELN nodes and outputs it as a discrete-time TDF signal.
Complete Example: An RLC Low-Pass Filter
The following complete, compilable example demonstrates how to construct a classic Resistor-Inductor-Capacitor (RLC) filter. We will drive the filter with a TDF step-signal and read the continuous-time voltage response of the capacitor back into the TDF domain.
#include <systemc>
#include <systemc-ams.h>
// 1. TDF Source: Generates a Step input at 1.0 seconds
SCA_TDF_MODULE(StepSource) {
sca_tdf::sca_out<double> out;
SCA_CTOR(StepSource) {}
void set_attributes() {
set_timestep(1.0, sc_core::SC_MS); // 1 ms discrete timestep
}
void processing() {
double t = get_time().to_seconds();
double val = (t >= 1.0) ? 5.0 : 0.0; // 0V to 5V Step
out.write(val);
}
};
// 2. The ELN RLC Circuit
SC_MODULE(RLC_Circuit) {
// Interface to the TDF world
sca_tdf::sca_in<double> in;
sca_tdf::sca_out<double> out;
// Electrical Nodes
sca_eln::sca_node n_src; // Node between Source and Resistor
sca_eln::sca_node n_rl; // Node between Resistor and Inductor
sca_eln::sca_node n_lc; // Node between Inductor and Capacitor
sca_eln::sca_node_ref gnd; // The electrical ground (0V)
// Converter Primitives
sca_eln::sca_tdf::sca_vsource tdf_v_src;
sca_eln::sca_tdf::sca_vsink tdf_v_snk;
// Electrical Primitives
sca_eln::sca_r resistor;
sca_eln::sca_l inductor;
sca_eln::sca_c capacitor;
SC_CTOR(RLC_Circuit)
: tdf_v_src("tdf_v_src", 1.0) // Scale = 1.0
, tdf_v_snk("tdf_v_snk", 1.0)
, resistor("resistor", 10.0) // R = 10 Ohms
, inductor("inductor", 0.1) // L = 100 mH
, capacitor("capacitor", 0.001) // C = 1000 uF
{
// 1. Drive the circuit using the TDF input
tdf_v_src.inp(in); // Bind to TDF discrete port
tdf_v_src.p(n_src); // Positive terminal to Source node
tdf_v_src.n(gnd); // Negative terminal to Ground
// 2. Resistor in series
resistor.p(n_src);
resistor.n(n_rl);
// 3. Inductor in series
inductor.p(n_rl);
inductor.n(n_lc);
// 4. Capacitor to ground
capacitor.p(n_lc);
capacitor.n(gnd);
// 5. Read the voltage across the capacitor (n_lc to ground)
tdf_v_snk.p(n_lc);
tdf_v_snk.n(gnd);
tdf_v_snk.outp(out); // Bind to TDF discrete port
}
};
int sc_main(int argc, char* argv[]) {
// Signals
sca_tdf::sca_signal<double> sig_step("sig_step");
sca_tdf::sca_signal<double> sig_response("sig_response");
// Instantiate Modules
StepSource src("src");
src.out(sig_step);
RLC_Circuit rlc("rlc");
rlc.in(sig_step);
rlc.out(sig_response);
// Setup Tracing
sca_util::sca_trace_file* tf = sca_util::sca_create_vcd_trace_file("eln_rlc_wave");
sca_util::sca_trace(tf, sig_step, "Input_Step_Voltage");
sca_util::sca_trace(tf, sig_response, "Capacitor_Voltage_Response");
// You can also trace internal ELN nodes directly!
sca_util::sca_trace(tf, rlc.n_rl, "Node_RL_Voltage");
// Start Simulation
sc_core::sc_start(3.0, sc_core::SC_SEC);
sca_util::sca_close_vcd_trace_file(tf);
return 0;
}How the ELN Solver Works
During the elaboration phase, the SystemC AMS kernel identifies all connected ELN primitives (the converters, resistor, inductor, and capacitor) and groups them into an ELN cluster. It automatically formulates a continuous-time matrix of differential equations based on KVL and KCL.
During the simulation, whenever the discrete TDF solver injects a new voltage value via tdf_v_src, the ELN Linear Solver mathematically integrates the continuous-time response of the RLC circuit across that timestep, tracking the energy stored in the inductor and capacitor. It then provides the precise resulting voltage at node n_lc back to the discrete TDF domain via tdf_v_snk. This complex analog numerical integration happens entirely under the hood.
Under the Hood: Modified Nodal Analysis (MNA) Matrices
In C++, an sca_eln::sca_node contains no value payload. It merely contains a unique integer ID assigned during elaboration. To calculate the voltages across the network, the Accellera AMS ELN solver utilizes Modified Nodal Analysis (MNA).
Under the hood, all sca_eln components inherit from an internal base class that provides a virtual matrix_stamp() method. The solver allocates global Admittance ($\mathbf$), Capacitance ($\mathbf$), and Excitation ($\mathbf$) matrices based on the total number of node IDs.
When sca_eln::sca_r (Resistor) executes its matrix_stamp() during elaboration, it reads its positive node ID ($i$) and negative node ID ($j$), and computes its conductance ($G = 1/R$). It then directly adds $+G$ to the matrix entries $\mathbf$ and $\mathbf$, and $-G$ to entries $\mathbf$ and $\mathbf$.
Similarly, sca_eln::sca_c stamps the $\mathbf$ differential matrix. The solver then computes the continuous-time transient response by numerically inverting these matrices ($\mathbf(t) = \mathbf^ \cdot \mathbf$) at each solver step. Therefore, when you write sca_eln::sca_r in your code, you are not writing a behavioral model; you are instantiating a declarative rule that configures the C++ linear algebra engine.
Comments and Corrections