1

What I'd really like is to be able to parameterize an impedance like a resistor against frequency with a table and interpolation in AC mode, as in Rf=table(freq,1meg,10,10meg,50)

I remember seeing that freq could sometimes be referenced so I used astrogrep on a windows system to search for 'table(freq' and found it in a few places where it was actually used to give an En noise density value vs freq (presumably in AC mode) in a device model constructed around A devices.

So if we can have that, why exactly can't we have the straightforward extension of that for resistors, inductors and capacitors?

I'm not too interested in the discussions that I have seen about using laplace expressions because I actually need the values to be interpolated on a table vs frequency.

So it would appear that you can do interpolation against frequency in a specific internal method that is specific to "A" devices.

Is there a FOSS simulator that can be used instead of LTspice? I love the program as much as anybody but the closed source limitations are really a big drag.

ocrdu
  • 8,705
  • 21
  • 30
  • 42
  • Why complicate things this way; if I have a resistor and want to model it more like a real part I'll add a touch of series inductance and a little splash of parallel capacitance. Those parasitics exactly express what would be found in a data sheet for the part and are as clear as anything in whatever sim tool you might use. Of course, the way I use sims may not be the way in which you do. – Andy aka Mar 08 '23 at 10:27
  • Because what I am really trying to do is input data from a file of s22 parameters. I use the real and imaginary to generate component values that I want to substitute. – steven suddarth Mar 09 '23 at 15:51
  • s22 parameters won't yield unambiguous values of complex impedance so I'm unsure what you are trying to do. – Andy aka Mar 09 '23 at 15:54
  • Have to tried `s2spice`? Latest version is maintained here: https://github.com/transmitterdan/s2spice – Ste Kulov Mar 09 '23 at 18:48
  • Just to answer Andy, I read my table as freq,real,imag. Then I model the source as a resistor in series with a parallel cap and inductor. I convert my table so that when reactance is negative I set the inductor to 1H and when reactance is positive I set my capacitor to 1fF. Otherwise I convert the part I'm using with the reactance formula against the current frequency. – steven suddarth Mar 10 '23 at 06:16

4 Answers4

2

Your premise is flawed. LTspice has a feature called a "FREQ table", which was mainly included for PSpice compatibility. The syntax and explanation of its basic use can be found here:

https://ltwiki.org/?title=Undocumented_LTspice#B-Sources

https://ltwiki.org/index.php?title=B_sources_(complete_reference)

The jist is you can define a voltage/current source using a linearly interpolated table of (frequency, magnitude, phase) triplets. Think of it as a frequency domain PWL source. Using it to define a resistor (or more generally, an impedance) is a little tricky. But if you know the tricks, it can be done.

I'm going to show the first example using the most generalized form because it makes the most sense in terms of the syntax. The component we're going to use is the current form of the B-source (bi in symbol library). We're going to set its value to I=V(out,0)/1 which is the voltage across it V(out,0) divided by 1 (it'll make sense why I included this 1 later). If you step back now, you'll see that this component represents a 1Ω resistor. Now, the way the FREQ table works is it'll multiply this expression by each value (magnitude/phase) at each frequency. So if we want the resistance to change at 1k, 10k, and 100k we can tack this onto the end of the I=V(out,0)/1 expression, or I personally prefer to add it to the next value line, i.e. Value2.

MAG FREQ=(1k,{1/1k},0) (10k,{1/2k},0) (100k,{1/3k},0)

The default units for the magnitude are in dB, so the MAG keyword sets it to non-dB. The magnitudes are defined in 1/Z form because we want the Z to multiply with V(out,0)/1 to result in V(out,0)/Z. An alternative to using {1/Z} is using {Z**-1} but I think the former is easier to look at. The curly braces are needed to force LTspice to evaluate the expression to a number. All the phases are zero for this "resistor". Simulating, we get this which is exactly what we wanted:

enter image description here NOTE: Keep in mind that values outside of the table get extrapolated to the left and right, which is why you get horizontal lines there.


As many people know, LTspice includes something called a "behavioral resistor" which is used whenever you define a resistance in the form R=<expression>. Actually, what LTspice does under the hood is converts this to a behavioral current source, exactly in the same way we did above. So a behavioral resistor with R=1 is exactly the same as our I=V(out,0)/1 current source. The upside is that you don't have to manually define the voltage across it. The downside is that the syntax is goofy when using the FREQ table with a behavioral resistor, and it's because the FREQ table applies its values to the expression AFTER it's converted to a current source. So even though you define the resistor as R=1, you still need to put your values in the form 1/Z like we did before. Long story short, if you want to do the same exactly thing we did above but use a behavioral resistor component, it will look like this:

enter image description here


Since this can be used to define any kind of impedance, you can do capacitors and inductors too. Here is an example defining a capacitor in FREQ table form in order to match an actual capacitor of 100nF. Just as before, you would define the magnitude of the impedance as 1/Z. Since |Z|=2*π*f*C for a capacitor, we put the magnitudes in the form of 2*π*f*C. Phase of a capacitor is 90º, so we put 90 for all the phases. Notice with just 4 points defined, the plots overlap almost completely within the frequency decades containing defined values.

enter image description here


One big caveat that should be known is that LTspice treats the FREQ table much like its Laplace feature. Therefore, this blurb from the LTspice Help file mostly applies to FREQ tables, especially the part regarding time domain solutions (which applies to .tran simulations):

If an optional Laplace transform is defined, that transform is applied to the result of the behavioral current or voltage. The Laplace transform must be a function solely of s. The Boolean XOR operator, ^, is understood to mean exponentiation, **, when used in a Laplace expression. The frequency response at frequency f is found by substituting s with sqrt(-1)2pi*f. The time domain behavior is found from the sum of the instantaneous current(or voltage) with the convolution of the history of this current(or voltage) with the impulse response. Numerical inversion of a Laplace transfer function to the time domain impulse response is a potentially compute-bound process and a topic of current numerical research. In LTspice, the impulse response is found from the FFT of a discrete set points in frequency domain response. This process is prone to the usual artifacts of FFT's such as spectral leakage and picket fencing that is common to discrete FFT's. LTspice uses a proprietary algorithm that exploits that it has an exact analytical expression for the frequency domain response and chooses points and windows to cause such artifacts to diffract precisely to zero. However, LTspice must guess an appropriate frequency range and resolution. It is recommended that the LTspice first be allowed to make a guess at this. The length of the window and number of FFT data points used will be reported in the .log file. You can then adjust the algorithm's choices by explicitly setting nfft and window length. The reciprocal of the value of the window is the frequency resolution. The value of nfft times this resolution is the highest frequency considered. Note that the convolution of the impulse response with the behavioral source is also potentially a compute bound process.

Basically, what this means is that any .tran simulations containing FREQ tables and/or Laplace functions can result in numerical artifacts and funky results. LTspice also gives you two parameters nfft and window which you can tweak to try and get better behavior from the source in question.


Is there a FOSS simulator that can be used instead of LTspice? I love the program as much as anybody but the closed source limitations are really a big drag.

I really like ngspice, but it does not have the FREQ table feature implemented yet. There is a post in this thread by an ngspice developer asking for someone's contribution in creating the code. If you like, you can give it a shot.

https://forum.kicad.info/t/import-target-function-for-ngspice/26907/5


Last thing I want to point out. Usually the reason why people ask the type question you are is because they are intending to import S-parameters into SPICE. There is a command line program called s2spice created by a user on the LTspice Google Groups (accessing files requires an account) which creates a subcircuit from an S-parameters file. A bunch of the RF-heads on the group use this program for their work.

https://groups.io/g/LTspice/files/z_yahoo/Tut/S-Parameter/s2spice.doc

https://groups.io/g/LTspice/files/z_yahoo/Tut/S-Parameter/S-Parameter%20to%20SPICE/s2spice101.exe


More recently, someone on the groups ported it into a GUI program. That version can be found here:

https://github.com/transmitterdan/s2spice

Ste Kulov
  • 3,771
  • 10
  • 22
  • I tried the s2spice GUI. I am using files from the company Mxxx-Cxxx which come as Excel spreadsheets that I convert into CSV.txt for import into my version of the s2spice as explained above. I came here not because my program didn't work but because the implementation and use were such a ghetto experience and I thought there must be something better. Indeed there is and it is good for me because I use .AC for much of what I do anyway. I'd be happy to put the source of my program here. I asked an AI to write the bones of it for me in an old language from the 80's. – steven suddarth Mar 10 '23 at 06:43
  • 1
    Clearly I need to hide the table ugliness away in a subckt rather than trashing my parent asc file that way. But I hated it in stepped .TRAN anyway so I didn't bother working on it after getting it going. I went here and to the AD group and the ngspice users. – steven suddarth Mar 10 '23 at 06:47
  • @stevensuddarth Agreed. I typically put large tables in subcircuits too. It's much easier to work with. You can use the `+` sign to start new lines, and you can use the ALT+mouseclick feature of text editors to edit multiple lines at once. The other answer does a good job showing this. Only thing I'll add is you don't need to make a custom symbol. Just CTRL+rightclick an existing 2-port symbol and change the prefix to `X` and the `Value` to the subcircuit name. Custom symbol would be a good choice if you want to auto-include a specific `.lib` file with it every time you use it. – Ste Kulov Mar 10 '23 at 06:54
2

You can model a complex impedance (actually, admittance) versus frequency using a table method. Fortunately, LTspice allows PSpice syntax when using tables in voltage dependent current sources (G). If you want more information on this, you can find the syntax in the "PSpice A/D Reference Guide".

This will only work in .AC analysis.

Create a subcircuit with Frequency and Admittance values.
File name: XdcrZ.sub

*Subckt simulates xdcr admittance
*Data format: Freq(Hz)  G(mhos)  B(mhos)
.SUBCKT XDCRZ 1 2
G1 2 1 FREQ {V(2,1)}= R_I (
+ 100000     2.680520E-04    1.017132E-03
+ 100250     2.711300E-04    1.019504E-03
+ 100500     2.741840E-04    1.022642E-03
+ 100750     2.775840E-04    1.027532E-03
+ 101000     2.807740E-04    1.030796E-03
+ )
.ENDS XDCRZ

Create a part.
File name: XdcrZ.asy

Version 4
SymbolType CELL
LINE Normal -32 32 -32 -32
LINE Normal 32 32 -32 32
LINE Normal 32 -32 32 32
LINE Normal -32 -32 32 -32
LINE Normal 0 -32 0 -48
LINE Normal 0 32 0 48
TEXT 0 0 Center 2 Z
SYMATTR Prefix X
SYMATTR SpiceModel XdcrZ
SYMATTR Description Complex admittance subcircuit
SYMATTR ModelFile XdcrZ.sub
PIN 0 -48 LEFT 8
PINATTR PinName 1
PINATTR SpiceOrder 1
PIN 0 48 LEFT 8
PINATTR PinName 2
PINATTR SpiceOrder 2

Use above in a simulation.

enter image description here

Use Cartesian coordinates if you want to see the complex impedance as done in this example (set by right-clicking on the Y-axis).

Complex admittance is the reciprocal of complex impedance. Many calculators can perform this function, or, use a maths program. $$ {1 \over {R + jX}} = {G + jB} $$

qrk
  • 7,474
  • 1
  • 5
  • 20
  • Many thanks! I'm certainly glad I came here! I do wonder how people come to know what goes on in LTSpice (specifically) under the hood. It looks like I might have written something similar to s2spice which I have used in a .TRAN setting with stepped frequency. The details of what that program does are given in outline in my answer to Andy above. In my case I studied the asc format and I place a marker that is an .OP text block, then I add my table into the asc file and if the table is long I get something that makes the graphical schematic tiny. But it works and is stable in .TRAN. – steven suddarth Mar 10 '23 at 06:23
1

So if we can have that, why exactly can't we have the straightforward extension of that for resistors, inductors and capacitors?

Simply put, it would seize to be a resistor, inductor or capacitor. We have pretty clear ideas of what is a theoretical resistor, inductor or capacitor. Modeling real components in practice is usually using a combination of these.

Note that what you are trying to do is fairly computation-intensive and possibly prone to accuracy errors, for example for a transient analysis, LTSpice will do an inverse FFT to get the impulse response. It then has to convolve the voltage and current using it. This is a relatively expensive operation with many multiply-add operations and interpolations, which makes it unsuitable for a circuit with many devices.

Is there a FOSS simulator that can be used instead of LTspice? I love the program as much as anybody but the closed source limitations are really a big drag.

There is ngSpice which I would say is the main one to look at if you're looking for an open source Spice simulator. Xyce is more focused on multithreaded simulation and also seems to allow downloading/viewing the source code if you register.

Then you also have the Falstad simulator (github) which is fairly inaccurate but is open source. Then I also wrote one in C# called Spice#, but it is missing the more complex models (BSIM transistor models, specialized LTspice features, etc.).

Sven B
  • 5,072
  • 8
  • 24
0

I used the method taught by qrk for my problem. I don't necessarily care about the question of generalizing this to TRAN and indeed my original question about passives was more about convenience and conceptual clarity than anything else. For instance if you had an inductor where you wanted to model skin depth losses or proximity effect losses vs frequency and you actually had some sparse empirical data you could encapsulate that in a series resistor and it would make visual and conceptual sense in one way, just as decorating a box component with a G source inside of it makes sense in another way. It is less obvious what to put in the G source function than it would be for a frequency dependent resistor because for the latter it is just a table of resistance VS frequency whereas for the G source you have to do complex math.

So now I have this procedure:

  1. sequester the 3 columns with my data from the excel spreadsheet freq,dBmag,phase
  2. save them as a CSV txt file in Windows
  3. run the program below to make a G source and get that into a 2 terminal box
  4. be happy

I found that even after all this time using complex number math in C was kind of a mess. I didn't trust that operator overloading was happening just because there was a complex data type and I couldn't get openlibm to make so I just went with self-rolled complex functions. (Most of the code was actually written by chatGPT).

Here is the code. My apologies!

/***********************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
//#include <complex.h>

#define MAX_COLS 3
#define MAX_LINE_LEN 1024
#define MAX_POINTS 10000


// Define a struct to represent a complex number
typedef struct {
    double real;
    double imag;
} complex;

// Function to divide two complex numbers
complex cdiv(complex a, complex b) {
    complex result;
    double denom = b.real * b.real + b.imag * b.imag;

    result.real = (a.real * b.real + a.imag * b.imag) / denom;
    result.imag = (a.imag * b.real - a.real * b.imag) / denom;

    return result;
}

complex cmul(complex z1, complex z2 ) {
    complex result;
    result.real = z1.real * z2.real - z1.imag * z2.imag;
    result.imag = z1.real * z2.imag + z1.imag * z2.real;
    return result;
}

complex cadd(complex z1, complex z2 ) {
    complex result;
    result.real = z1.real + z2.real;
    result.imag = z1.imag + z2.imag;
    return result;
}

complex csub(complex z1, complex z2 ) {
    complex result;
    result.real = z1.real - z2.real;
    result.imag = z1.imag - z2.imag;
    return result;
}


complex cmplx(double a, double b) {
    complex result;
    result.real = a;
    result.imag = b;
    return result;
}



int main(int argc, char *argv[]) {

    if (argc < 3 || argc > 4) {
        printf("Usage: %s input_file output_file [impedance: 50 by default]\n", argv[0]);
        return 1;
    }

    // Open input file 1 for reading
    FILE *inFile1 = fopen(argv[1], "r");
    if (!inFile1) {
        fprintf(stderr, "Error opening input file 1: %s\n", argv[1]);
        exit(1);
    }

    // Open output file for writing
    FILE *outFile = fopen(argv[2], "w");
    if (!outFile) {
        fprintf(stderr, "Error opening output file: %s\n", argv[2]);
        fclose(inFile1);
        exit(1);
    }
        // Get the zee value if provided, otherwise use default
    double zee = 50.0;
    complex czee,c_one;
    c_one = cmplx(1.0,0.0);

    if (argc == 4) {
        zee = atof(argv[3]);
    }
    czee.real = zee;
    czee.imag = 0.0;


    // Read input file 1 and parse it into an array of doubles
    double data[MAX_COLS];
    double resistance[MAX_POINTS];
    double reactance[MAX_POINTS];
    double frequency[MAX_POINTS];
    complex my_x[MAX_POINTS];
    complex myrecip;
    double mymag;

    char line[MAX_LINE_LEN];
    int mycount = 0;
    int i=0;
    while (fgets(line, MAX_LINE_LEN, inFile1)) {
        char *token = strtok(line, ",\t\n");
        int col = 0;
        while (token != NULL && col < MAX_COLS) {
            data[col++] = (double)atof(token);
            token = strtok(NULL, ",\t\n");
        }

        // Do something with the data (for example, print it to the console)

        frequency[i] = data[0];

        mymag = pow(10.0,(data[1]/20.0));
        resistance[i] = mymag*cos(M_PI*data[2]/180.0);
        reactance[i] = mymag*sin(M_PI*data[2]/180.0);
        my_x[i] = cmplx(resistance[i],reactance[i]);


        my_x[i] = cmul(czee , cdiv(cadd(c_one,my_x[i]),csub(c_one,my_x[i])));
        myrecip = cdiv(c_one,my_x[i]);
        resistance[i] = myrecip.real;
        reactance[i] = myrecip.imag;

        printf("%f %f %f %f %f\n", data[0], data[1], data[2], resistance[i],reactance[i]);

        //BURN 3 LINES
//        fgets(line, MAX_LINE_LEN, inFile1);
//        fgets(line, MAX_LINE_LEN, inFile1);
//        fgets(line, MAX_LINE_LEN, inFile1);
        mycount++; i++;
        if(mycount>MAX_POINTS){
        fprintf(stderr, "TOO MANY POINTS \n");
        fclose(inFile1);
        fclose(outFile);
        exit(1);
        }
        }
/*
*Subckt simulates xdcr admittance
*Data format: Freq(Hz)  G(mhos)  B(mhos)
.SUBCKT XDCRZ 1 2
G1 2 1 FREQ {V(2,1)}= R_I (
+ 100000     2.680520E-04    1.017132E-03
+ 100250     2.711300E-04    1.019504E-03
+ 100500     2.741840E-04    1.022642E-03
+ 100750     2.775840E-04    1.027532E-03
+ 101000     2.807740E-04    1.030796E-03
+ )
.ENDS XDCRZ
*/



    fputs(";*Subckt simulates xdcr admittance\n", outFile);
    fputs(";*Data format: Freq(Hz)  G(mhos)  B(mhos)\n", outFile);

    fputs(".SUBCKT XDCRZ 1 2\n", outFile);
    fputs("G1 2 1 FREQ {V(2,1)}= R_I (\n", outFile);

//    mycount = 300;

    for (i=0;i<mycount;i++){
            fprintf(outFile,"+ %fmeg\t%f\t%f\n", frequency[i],resistance[i],reactance[i]);

    }
    fputs("+ )\n", outFile);
    fputs(".ENDS XDCRZ\n", outFile);


    // Close all files
    fclose(inFile1);
    fclose(outFile);

    return 0;

    }
ocrdu
  • 8,705
  • 21
  • 30
  • 42