Creating delay loop in C++

I am trying to generate a state machine which switches its state from state #1 to state#2 if V1 is >V2 for 500us. What is a good way to implement this delay?
The sleep() function does not work as it just stops the processing for specified time but does not create the delay.
I used the following code but this is only slowing the simulation and not creating the delay.

switch(state)
{
case 1:
if ((vdd - vss) > vdet1)
{

                clock_t endwait;
                endwait = clock () + (0.5 * CLOCKS_PER_SEC) ;
                while (clock() < endwait) {}
                state = 2;
             }

           else if ((vdd - vss) < vdet2)
              {
                 state = 3;
              }
           break;

     }

Hi, gabbar.

You will need to use the t parameter passed into the evaluation function to get the current simulation timepoint. The CBlock_Doc folder of my GitHub QSpice repository might get you started.

–robert

This is Qspice C++ code I can think of to implement your request. I assume during delay (500us), the comparator won’t need to trigger again (if it does, possibly the code is very complicated).
The most difficult part in your request is to switch state with a delay after main triggering event. This means you have to play with variable t (i.e. simulation time).
Another challenge is that, Qspice is adaptive time step, and we have to ensure timestep is reduced (i.e. use TTOL in Trunc() template) at 500us after main triggering event, or your delay event will not happen at exactly 500us.
In the code, you can see I added inst->lastOUT and inst->lastDelayOut, which is for above purpose.
In short, you have to fully understand purpose of these two template, as this is essential to achieve what you need.
image

Parent.ComparatorWithDelay.qsch (2.6 KB)
delaycompare.cpp (4.5 KB)

image

1 Like

If you need more information about .DLL block, I also have a guideline for basic of .DLL block. In this device guideline, search section Ø-Device.DLL.
Qspice/Guideline/Qspice - Device Reference Guide by KSKelvin.pdf at main · KSKelvin-Github/Qspice

I am not expert in C++, I learn a lot from @RDunn and @physicboy, my guideline also quoted the work from physicboy in explaining Ø-Device (Template).

In summary, here are their work

@RDunn : CBlock_Doc Folder
robdunn4/QSpice: QSpice tools, components, symbols, code, etc. (github.com)

@physicboy : Page 3 is a one-page summary
QSPICE/PWM_example_SRbuck/Three Complexity Levels of PWM Implementation for Digital Controller Simulation.pdf at main · physicboy/QSPICE (github.com)

Thanks Kelvin for your mention

@gabbar, 1 key part to improve your understanding in Cblock simulation, in Qspice (and also Simulink, PSIM, SIMBA, NL5, etc), everything you write in your Cpp code will be executed in 1 simulation step instance, thus any delay or timing related things you write may not work as you expect.

In MCU, when you call sleep or delay, the MCU will move out from the current instruction and wasting cycle for your described duration, then return to the original instruction.

Thanks @RDunn , @KSKelvin @physicboy .
The circuit I am trying to model requires to monitor the value V1 for 500u and if V1 stays above V2 for all 500us then only state will change. In analog domain, I would realize it using a RC circuit which states to charge when V1 goes above V2. In between if V1 drops below V2 (before 500us) , the timer will reset.

Do you have an overall diagram of what you want to achieve? Is it part of a bigger system?

Because the answer will be different as I dont believe in analog or digital are superior. Whatever fit best is the superior solutio

Hi, @gabbar.

@KSKelvin provided some code but I thought that I might provide a somewhat simpler approach.

Not exactly sure that this matches your requirements. It delays 500us of Vin > Vref time before setting output high. Output goes low as soon as Vin < Vref. Anyway, maybe it will help.

–robert


delay.qsch (3.7 KB)
delay_x1.cpp (2.5 KB)

Oops. @gabbar, I forgot to change the default ttol to a large value so it runs slowly. Try the attached instead.

delay_x1.cpp (2.6 KB)

Thanks @KSKelvin for pointing this out.

–robert

Hi Rob,

May I confirm, that the parameter t in Trunc() is the proposed future timestep?
I always thought that future timestep is t + *timestep. Though honestly I never make a validation on this.

extern "C" __declspec(dllexport) void Trunc(struct sDELAY_X1 *inst, double t,
    union uData *data,
    double *timestep) {   // limit the timestep to a tolerance if the circuit
                          // causes a change in struct sDELAY_X1
  double ttol = 1e-9;

  // if proposed next simulation time (t) > next timeout, shorten timestep to
  // next timeout
  if (t > inst->timeout_t) ttol = t - inst->timeout_t;
  *timestep = ttol;
}

@RDunn Sorry… I made a big misunderstanding for so long

Of course, the parameter “t” passed to Trunc should be the future timestep already.

As the logic goes by, in trunc() the main_code() is called and fed by the parameter “t”, and it just does not make sense to use old “t”. And *timestep simply means the delta_t from “t” and the “t_prev”

Thus, I need to modify my trunc() in all of my C-block and put this note…

1 Like

@physicboy @RDunn
After your comment, let compare our understanding

  1. time (t) in Trunc() is next timestep (no happened yet)
  2. This time (t) is sent into main function to get its output and store into tmp.
  3. if (tmp != *inst) is equivalent to compare next step output (tmp.) and current output (inst->), this compare should be discrete, if they are different, a state change is confirmed, and should reduce timestep
    ** can use any kind of logic to detect a change event, not limit to if(tmp != *inst)
  4. if condition trigger, set *timestep = ttol to reduce timestep temporary
  5. ** Remark : time (t) in #1 may or may not be used for an actual next step. If no *timestep changes, this time (t) and its output data is next time step data. But if *timestep changes, simulator goes back in time and reduce timestep at this region to calculate the result (if not go back in time, I cannot explain why timestep can reduce before state change).

1 Like

Lets first say there are two functions, compute(t,data,…) and trunc(t,timestep,data,…)

Inside trunc() the function is as following

trunc(t,timestep,data,…)
{
buffer = data
compute(t,data,…)
if(data !=buffer)timestep=ttol
data=buffer
}

So, we can see here that the default example from Mike for trunc is to test new timestep and the result of compute() thats called from within trunc() is just discarded.

Another information from my past test was

if in compute() you add print(t) then add print(marker) in trunc() as following
trunc(t,timestep,data,…)
{
print(marker)
buffer = data
compute(t,data,…)
if(data !=buffer)timestep=ttol
data=buffer
}

In console you will see this

marker
t0
t0
marker
t1
t1
marker
t2
t2

Which means, the Qspice engine always do this call sequence
trunc(t1_propose)->compute(t1_corrected)->trunc(t2_propose)->compute(t2_corrected)->trunc(t3_propose)->compute(t3_corrected)->…

The tx_propose == tx_corrected only if
there data == buffer or no direct timestep manipulation (my example) is performed.

1 Like

Yes, Kelvin. To clarify, in #5 the temporary output data calculated in Trunc() is “undone” and gets recalculated when the simulation calls the evaluation function with the new timepoint/shorter timestep…

To take this a step further, there are two ways to use Trunc(). The way that I’m doing it forces the simulation to produce a timestep at a particular time. I could do that because I wanted to trigger at a specific time (per the user’s requirements).

The way that Mike’s ACME Semi (or whatever) example does it (and, I think, you also) is conceptually different. In the example, well, the circuit is complicated and the component doesn’t know exactly when it would need to change state. So…

The example checks to see if the proposed t (in Trunc()) would produce a change in component state. If so, it “sneaks up” on the state change by setting the *timestep to a small “tolerance value.” This may cause multiple extra unnecessary simulation steps (where the state doesn’t change). But that’s required because of the uncertainty about when the state would actually need to change. In this case, the *timestep is truly a “tolerance.”

Anyway, that’s how I understand things. I have a half-finished “C-Block Basics #4” that focuses on Trunc(). I guess I need to complete and upload it to my repo.

–robert

1 Like

@physicboy @KSKelvin

I’ll add one more piece to this puzzle. Unless something changed since I looked deeply into this a while back, Trunc() is not called until after the first timestep. That is, it is not called when t=0 (which makes sense). QSpice will call the evaluation function for t=0 and the first t>0 before Trunc() is called.

This would be a problem if the first step is, for some reason, too long/large. The MaxExtStepSize() does get called before the first t>0 and can be used to ensure that the first step isn’t too large. This could, of course, be done with a schematic MAXSTEP option but MaxExtStepSize() can be adjusted on the fly to avoid keeping it too small for too long.

–robert

2 Likes