KiCad's ngspice Integration: OPAx322 Model Compatibility

Hi everyone,

I’m trying to build a Python application for interactive circuit simulation using ngspice as the backend, and I’ve run into an issue with a specific op-amp model.

My project works perfectly with a generic op-amp model. When I use the official PSpice model for the OPAx322 from TI the simulation fails when I run ngspice directly.The strange part is that the exact same circuit and OPAx322 model file work inside the KiCad simulator

When I try to run it outside of KiCad using the ngspice library I consistently get a set of parsing errors:

  • Error: no such function 'if'
  • Error: unknown subckt: ...
  • Error: Undefined parameter [flw]
  • MIF-ERROR - unable to find definition of model ...

This makes me think that KiCad isn’t just calling ngspice but is also doing some pre-processing on the netlist or model files to make them compatible.

Can anyone shed some light on this? Does KiCad have a built-in compatibility layer that automatically fixes PSpice syntaxbefore sending it to the ngspice engine that i need to implement in my code? Any insights would be very much appreciated!

Thanks!

KiCad sets the compatibility mode to PSPICE and LTSPICE (see the Simulation Tab).

With standad ngspice you have to set this mode as well. It is done by putting
set ngbehavior=ltpsa
into .spiceinit.

About .spiceinit: you may have a look into the ngspice manual, chapter 12.6.

1 Like

On a side note, are you aware of SKiDL?

1 Like

Nope, I wasn’t. Thank you for pointing that out. Will definately look into it and see if it helps me with my workflow.

Very helful, thank you! That solved the first half of my issue and the second half was that ngspice couldnt find the spinit location and i had to point to it. Those were the fixes i needed.

I was also wondering if there is a way in ngspice to run an infinite simulation (or just very long) that I can manually inject currents into. Previously in Kicad I used a filesource but now I’d like to do that “live” and monitor the outcome in the simulation as it happens. Anything available in that regard?

With standard ngspice: no.
The (transient) simulation is initated with TSTART and TSTOP, and then it runs from beginning to end. You may predefine some stopping breakpoints before you start. But making TSTOP large, you may overload your memory available, as result data are continuously saved.

With shared ngspice (ngspice as a shared library, see chapter 15 of the ngspice manual): yes.
In a transient simulation (with very large TSTOP), at every time step accepted, a callback function in the caller is called by ngspice. You may use this function to interrupt the simulation, read the data, issue commands to change device parameters etc. No data need to be saved by ngspice itself.

I don’t know if one of the third-party Python-ngspice interfaces (see Ngspice, the open source Spice circuit simulator - Schematic entry and GUIs, Simulation Environments) already supports these actions. Otherwise some programming know-how is required (calling shared libraries from Python, accessing callbacks, multi-thread programming etc.).

Btw. KiCad uses the ngspice shared library. So you might watch the source code (C++, see eeschema/sim · master · KiCad / KiCad Source Code / develop · GitLab) to learn something about interfacing.

Okay, i’m slowly progressing… So I’ve been trying to use libngspice and an External current source

Iinject PD GND dc 0 external for live injection.

Usign ngSpice_Init_Sync I register a callback to GetIsrcData to feed current values into the simulation and run it with a background thread with bg_run.

This does initialize correctly, finds the DC operating point and calls the py_send_init_data correctly.

BUT i fail with a segmentation fault immediatly after the py_send_init_data callback, specifically when i try to loop through the vecs array to read the vector names. I suspect that my Python ctypes definitions for the vecinfo or vecinfoall structures might not fit the definitions in ngspice, but I can’t find any documentation on this issue.

The ctypes i used are

class vecvalues(ctypes.Structure):
    _fields_ = [("name", ctypes.c_char_p),
                ("creal", ctypes.c_double),
                ("cimag", ctypes.c_double),
                ("is_scale", ctypes.c_bool),
                ("is_complex", ctypes.c_bool)]

class vecvaluesall(ctypes.Structure):
    _fields_ = [("veccount", ctypes.c_int),
                ("vecindex", ctypes.c_int),
                ("vecsa", ctypes.POINTER(ctypes.POINTER(vecvalues)))]

class vecinfo(ctypes.Structure):
    _fields_ = [("number", ctypes.c_int),
                ("vecname", ctypes.c_char_p),
                ("is_real", ctypes.c_bool),
                ("pdvec", ctypes.c_void_p),
                ("pdvecscale", ctypes.c_void_p)]

class vecinfoall(ctypes.Structure):
     _fields_ = [("veccount", ctypes.c_int),
                 ("vecindex", ctypes.c_int),
                 ("vecs", ctypes.POINTER(ctypes.POINTER(vecinfo)))]

Unfortunately I have never used Python, so I cannot be of help here. You might need to have a look at the above cited existing interfaces.

When you ahve the interface up-and-running, it might be wise to publish it as a starter for a documentation.