Parameters to tweak for ZVS in LLC simulations QSPICE vs LTspice

Hi all,
I am simulating resonant switching converters, such as LLC, and frequently encounter issues with achieving proper ZVS (Zero Voltage Switching) waveforms in QSPICE, even though the same circuit works well in LTspice. My typical setup uses an ideal switch model (.model SW SW (Ron=10m Roff=100Meg Vt=0.5 Vh=-0.2)) in parallel with an ideal diode (.model D D (Ron=1m Roff=100Meg Vfwd=1)), along with a parallel capacitor, such as 130pF.

I suspect the ideal switch implementation in QSPICE might differ from LTspice, which could explain the waveform discrepancies. Additionally, I have observed that PWL-based solvers often produce textbook-like waveforms for such circuits.

Is there an option in QSPICE to use an ideal device model similar to those available in PWL-based solvers, or are there recommended settings to achieve ZVS waveforms comparable to LTspice?

Here is an example view of ZVS plots in both QSIPCE and LTSPICE:

BR, Thomas

Can you upload the netlist which you get your waveform in LTspice? Refer to this post.
Try to give us a circuit as simple as possible that you can encounter different between simulation platform.

Qspice Forum - New User to Basic User (File Upload) - QSPICE - Qorvo Tech Forum

Hi KSKelvin,

Here is the LTspice netlist.
For the QSPICE it would not be so easy to share because of some custom C-blocks.

* Generated by LTspice 26.0.0 for Windows.
V1 N003 0 {Vin}
C1 In+ N006 100µ
C2 N006 0 100µ
C3 N004 N001 {Cr} Rser=0.1m
L1 N004 N005 {Lr} Rser=1m
L2 N005 N008 {Lp} Rser=1m
L3 N002 N007 {Ls} Rser=1m
R1 Load+ 0 {Rload}
L4 N005 N008 {Lm}
C4 Load+ N007 100µ
V2 G1 S1 PULSE(0 15 0 0 0 {(d/fsw)-(td)} {1/fsw})
V3 G2 S2 PULSE(0 15 {(d/fsw)} 0 0 {(d/fsw)-(td)} {1/fsw})
V4 G3 S3 PULSE(0 15 {(d/fsw)} 0 0 {(d/fsw)-(td)} {1/fsw})
V5 G4 S4 PULSE(0 15 0 0 0 {(d/fsw)-(td)} {1/fsw})
V6 G5 S5 PULSE(0 {Vsc} 0 0 0 {(d/fsw)-(td)} {1/fsw})
V7 G6 S6 PULSE(0 {Vsc} {1/(2*fsw)} 0 0 {(d/fsw)-(td)} {1/fsw})
S1 N002 0 G6 S6 MYSW
S4 Load+ N002 G5 S5 MYSW
S5 In+ N001 G1 S1 MYSW
S6 N001 N006 G2 S2 MYSW
S7 N006 N008 G3 S3 MYSW
S8 N008 0 G4 S4 MYSW
D1 N002 Load+ D
D4 0 N002 D
D5 N001 In+ D
D6 N006 N001 D
D7 N008 N006 D
D8 0 N008 D
C5 N007 0 100µ
L5 N003 In+ 10µ
C6 In+ N001 {Cpr} Rser=0.01u
C7 N001 N006 {Cpr} Rser=0.01u
C8 N006 N008 {Cpr} Rser=0.01u
C9 N008 0 {Cpr} Rser=0.01u
C10 Load+ N002 {Cpr} Rser=0.01u
C11 N002 0 {Cpr} Rser=0.01u
.model D D
.lib C:\Users\xxxx\AppData\Local\LTspice\lib\cmp\standard.dio
.param Vin=400
.tran 5m
.model MYSW SW(Ron=0.05m Roff=10Meg Vt=5 Vh=0)
k L2 L3 1
.param fsw=1100000
.param d=0.5
.param Cr=4n
.param Lr=6.33u
.param Lm=33.55u
.param Lp=39.88u
.param Ls=0.75u
.param Cpr=128p
.param Rload=0.384
.param td=100n
.param Vsc=15
.backanno
.end

You didn’t specify the rise and fall time of the pulse, and LTspice automatically assigned a rise and fall time for you. I copied that value into the netlist.

Here are some options that can help eliminate trapezoidal ringing. Well… Qspice’s timestep is quite aggressive, and in my personal experience, circuits resembling ideal simulations of zero-current or zero-voltage switching often exhibit trapezoidal ringing.

In short, what you observed is trapezoidal ringing (you can search this keyword in forum for more information), and it is related to integration method and timestep profile.

Here is Qspice netlist modified from the netlist you provided.
ZVS-Qspice.cir (1.8 KB)

1 Like

Hi Kelvin,

Thank you very much for your time and effort in providing your answer.

The netlist I sent you is very “ideal” because it uses fixed timing with pulse sources. In my actual QSPICE simulation, I am using a closed-loop converter with cascaded voltage and current control implemented in C. This is where the issue is more apparent: when the switching frequency changes to maintain CC or CV at the output, the ZVS waveform turns into this trapezoidal, ringing shape.

Since you are very experienced with SPICE and have created an excellent library and very detailed documentation, I was wondering if you know of any better “ideal” MOSFET model than what is currently available in QSPICE. By the way, thank you for all the great work you do and for making your libraries freely available. It is really appreciated.

So far, I have tried using a behavioral resistor instead of the ideal switch, and I have also tested one of the micromodels suggested here: Macromodeling ideal switches for SPICE | IEEE Journals & Magazine | IEEE Xplore. Unfortunately, none of these approaches solve the problem, and in some cases they even slow down the simulation.

Bsw D S I = { V(D,S) * (
+  V(G)<=Voff ? 1/Roff :
+  ( V(G)>=Von ? 1/Ron :
+    (1/Roff + (1/Ron-1/Roff)*(V(G)-Voff)/(Von-Voff)) )
+)}

BR,
Thomas

This community allows me to explore various aspects of electronics and connect with different experts. I concur that when dealing with “ideal” (or near-ideal) models in SPICE, particularly for Zero Voltage Switching (ZVS) or Zero Current Switching (ZCS), trapezoidal ringing is a common occurrence. For instance, if you modify the rise time/fall time of a pulse source in your original LTspice netlist from 0 to a finite value (e.g., 10p, not allow LTspice to assign a larger rise/fall time), you will also observe trapezoidal ringing in LTspice. In contrast, piecewise linear simulators like PSIM can provide results akin to those in textbooks for ZVS/ZCS with ideal switches.

I recommend that if you can adjust your circuit to a degree where it can be shared with the community, we may collectively determine the best approach to address it. However, this remains a highly challenging area in SPICE, particularly in Qspice, where the timestep dynamically adjusts to accelerate simulations. There is a higher likelihood of introducing trapezoidal ringing, especially in ZVS/ZCS circuits.

Here is an example of addressing trapezoidal ringing in a ZVS circuit. My understanding is that trapezoidal ringing occurs when the inductor current becomes constant (di/dt = 0), but the timestep does not align precisely with that transition point. Consequently, the integration results oscillate back and forth in the subsequent timestep until di/dt not equal 0. Therefore, I came up with an idea that utilize a subcircuit to enforce a very small timestep at the moment when the inductor current begins to transition to 1A (constant).

One thing to keep in mind is that trapezoidal ringing can be real. It can indicate that you will experience oscillations if you construct this circuit. For instance, if you introduce a 1pF capacitor into this circuit, between Lr, you will observe the oscillation.

zvs-resonant-switch.qsch (8.6 KB)

Original with Trap Ringing

The experimental option - feather - Trap integration damping factor

Smaller maxstep, i.e smaller timestep

Gear integration - gear with damping in its nature

TTOL-I symbol I created - to reduce timestep to TTOL when current cross REF

1 Like

Hi Kelvin,

Thank you for your answer.

I have attached a simpler “basic circuit” with a simplified control scheme. I will only provide the simulation and .dll files for this example.

In this setup, ZVS is present, but there is still a noticeable ringing effect. The screenshot shows the simulation without control, running at a fixed frequency at resonance, which by definition should be in the ZVS region. Instructions on how to enable control and explore other cases are included as comments within the simulation file.

BR,
Thomas

LLC_ZVS_topic.qsch (39.3 KB)
modulator.dll (38.5 KB)
pid_discrete.dll (20.0 KB)

Add .option trtol=0.1… this will improve steady state waveform for I(S1). But I am not sure why there is spike before 0.5ms.

This option helps but also increases simulation time.
To avoid the spike before 0.5ms we we need some kind of soft-start. In closed loop this is handled. For fixed frequency you can try PWL 0 500K 0.1m 400K 0.2m 350K 0.3m 300K 0.4 270K 0.5 250K, instead of a fixed 250k FSW.
But even with trtol=0.1 I still see some oscillations.

Did you use Trunc() in your C code?

Yes, here is part of my code.

Time handling in modulator:

// =====================================================================
// MaxExtStepSize – keep timestep small w.r.t. fastest FSW and DT
// =====================================================================
extern "C" __declspec(dllexport)
double MaxExtStepSize(struct sMODULATOR &inst)
{
   double fsw1 = (inst.last_fsw1 > 0.0) ? inst.last_fsw1 : 0.0;
   double fsw2 = (inst.last_fsw2 > 0.0) ? inst.last_fsw2 : 0.0;
   double fmax = (fsw1 > fsw2) ? fsw1 : fsw2;

   if (fmax <= 0.0)
      fmax = 100e3;

   double T = 1.0 / fmax;
   double dt = (inst.last_dt >= 0.0) ? inst.last_dt : 0.0;

   // ~50 points per period, ~6 points inside DT
   double step_T  = T  / 50.0;
   double step_dt = dt > 0.0 ? dt / 6.0 : step_T;

   double limit = (step_T < step_dt) ? step_T : step_dt;
   if (limit <= 0.0) limit = 1e-12;

   return limit;
}

// =====================================================================
// Trunc: per-step refinement of timestep
// =====================================================================
extern "C" __declspec(dllexport)
void Trunc(struct sMODULATOR &inst, double t, union uData data[], double *timestep)
{
   (void)t;
   (void)data;
   if (!timestep) return;

   double limit = MaxExtStepSize(inst);
   if (*timestep > limit)
      *timestep = limit;
}




Time handling in PID controller:

//-----------------------------------------------------------------------------
// MaxExtStepSize: limit global max timestep to roughly the sampling period
//-----------------------------------------------------------------------------
extern "C" __declspec(dllexport) double MaxExtStepSize(struct sPID_DISCRETE &inst)
{
   // If Ts has been set, use it as a reasonable upper bound; otherwise no limit.
   if (inst.Ts > 0.0)
      return inst.Ts;
   return 1e308;   // "eternity" (no restriction)
}

//-----------------------------------------------------------------------------
// Trunc: locally refine timestep so that we hit the next PID sample instant
//-----------------------------------------------------------------------------
extern "C" __declspec(dllexport) void Trunc(struct sPID_DISCRETE &inst, double t, union uData data[], double *timestep)
{
   // Rebuild Fs/Ts from parameters (in case user changes Fs at runtime)
   double Fs = data[5].d;
   if (Fs <= 0.0)
      Fs = 1.0;
   double Ts = 1.0 / Fs;

   // 1) Never allow a step larger than Ts
   if (*timestep > Ts)
      *timestep = Ts;

   // 2) If we are approaching the next sample time, force the step to land on it
   double dtNext = inst.nextT - t;
   if (dtNext > 0.0 && dtNext < *timestep)
      *timestep = dtNext;

   // 3) Optional minimum step to avoid crazy tiny steps
   const double dtMin = 1e-12;
   if (*timestep < dtMin)
      *timestep = dtMin;
}

Maybe my implementations are also contributing to this trapezoidal ringing.

Well… sometimes, a current spike is not trapezoidal ringing but a correctly calculated result, especially during transitions. I simplified your schematic by removing as many components as possible to demonstrate how an ideal simulation can produce a significant current spike during a transition due to the high dV/dt across the capacitor. If you zoom into the waveform, it looks like a spike at one simulation timestep but it is correct calculation but not trap ringing.

full-bridge.qsch (12.8 KB)

Yes this is the effect of ideal components I guess. Do you have a suggestion for “better” simulation without these effects?

This exists even in piecewise linear simulation software (refer to this example screenshot). Just consider this scenario: you have a capacitor, and suddenly an ideal switch opens or closes, causing an immediate change in voltage across this capacitor. According to the formula Ic = C dV/dt, while dV is finite, with dt -> 0, leading to Ic → infinity. This is basic electronics, where an ideal switch is employed. You may not be aware of this effect sometime in simulation, as if the simulation setup with wider timesteps to increase dt, making the effect appear less significant.

As you said this is basic electronics and I am aware of this effect. My initial post was about the trapezoidal ringing and I can say that with the trtol=0.1 I can get better results.

Now, since we are talking about ideal devices, I was wondering how we can get such a waveform using QSPICE. What kind of device can we use or what technique (if any).

Beautiful ZVS waveform from PLECS.

Oh, I understand now. You believe the issue is related to the switch model. But trapezoidal ringing is not about the device but about the simulation timestep. That’s why all my focus is on the timestep. An ideal device is the source of trapezoidal ringing in SPICE-based simulators! The more ideal the device, the easier it is to encounter trapezoidal ringing.
Integration (IDT) problem. Why? - QSPICE - Qorvo Tech Forum

An ideal switch allows an immediate voltage change across it. Any capacitor across the switch will experience infinite dV/dt and result in infinite current at the moment of transition (dt depends on simulation timestep now). However, in practice, we have inductance in series with any connection, which limits the rate of change of voltage. What you observe in SPICE generally more closely resembles the real-life situation. Please refer to this reference from Mike. Trap ringing is not an error, but telling you something can happen if you build such circuit.
Apparent Kirchhoff’s law violation - QSPICE - Qorvo Tech Forum

PLECS and PSIM are linear piecewise simulators for nonlinear devices. They are designed to handle discontinuities in the I-V curve, which are present in textbooks but not in real life. SPICE, on the other hand, focuses on real-life characteristics and addresses nonlinearity. That why you need some tweak in SPICE to work with near ideal model in general.

Here is my study of how trap ringing is present.
Stability of derivative function - QSPICE - Qorvo Tech Forum

1 Like

Hello good answer well come